From 702330478cead8decf2a0a67b73efbe723f8b2c9 Mon Sep 17 00:00:00 2001 From: Dan Bucholtz Date: Thu, 2 Mar 2017 14:40:09 -0600 Subject: [PATCH] refactor(alert): restructure alert, move controller to separate module restructure alert, move controller to separate module --- src/components/alert/alert-component.ts | 6 +- src/components/alert/alert-controller.ts | 233 ++++++++++++++++++++++ src/components/alert/alert.ts | 244 +---------------------- 3 files changed, 246 insertions(+), 237 deletions(-) create mode 100644 src/components/alert/alert-controller.ts diff --git a/src/components/alert/alert-component.ts b/src/components/alert/alert-component.ts index 9199169853..3c17c7834d 100644 --- a/src/components/alert/alert-component.ts +++ b/src/components/alert/alert-component.ts @@ -4,7 +4,7 @@ import { Config } from '../../config/config'; import { NON_TEXT_INPUT_REGEX } from '../../util/dom'; import { GestureController, BlockerDelegate, BLOCK_ALL } from '../../gestures/gesture-controller'; import { isPresent, assert } from '../../util/util'; -import { Key } from '../../platform/key'; +import { KEY_ENTER, KEY_ESCAPE } from '../../platform/key'; import { NavParams } from '../../navigation/nav-params'; import { NavOptions } from '../../navigation/nav-util'; import { Platform } from '../../platform/platform'; @@ -222,7 +222,7 @@ export class AlertCmp { @HostListener('body:keyup', ['$event']) keyUp(ev: KeyboardEvent) { if (this.enabled && this._viewCtrl.isLast()) { - if (ev.keyCode === Key.ENTER) { + if (ev.keyCode === KEY_ENTER) { if (this.lastClick + 1000 < Date.now()) { // do not fire this click if there recently was already a click // this can happen when the button has focus and used the enter @@ -233,7 +233,7 @@ export class AlertCmp { this.btnClick(button); } - } else if (ev.keyCode === Key.ESCAPE) { + } else if (ev.keyCode === KEY_ESCAPE) { console.debug(`alert, escape button`); this.bdClick(); } diff --git a/src/components/alert/alert-controller.ts b/src/components/alert/alert-controller.ts new file mode 100644 index 0000000000..96d79bac16 --- /dev/null +++ b/src/components/alert/alert-controller.ts @@ -0,0 +1,233 @@ +import { Injectable } from '@angular/core'; + +import { Alert } from './alert'; +import { App } from '../app/app'; +import { AlertOptions } from './alert-options'; +import { Config } from '../../config/config'; + +/** + * @name AlertController + * @description + * An Alert is a dialog that presents users with information or collects + * information from the user using inputs. An alert appears on top + * of the app's content, and must be manually dismissed by the user before + * they can resume interaction with the app. It can also optionally have a + * `title`, `subTitle` and `message`. + * + * You can pass all of the alert's options in the first argument of + * the create method: `create(opts)`. Otherwise the alert's instance + * has methods to add options, such as `setTitle()` or `addButton()`. + * + * + * ### Alert Buttons + * + * In the array of `buttons`, each button includes properties for its `text`, + * and optionally a `handler`. If a handler returns `false` then the alert + * will not automatically be dismissed when the button is clicked. All + * buttons will show up in the order they have been added to the `buttons` + * array, from left to right. Note: The right most button (the last one in + * the array) is the main button. + * + * Optionally, a `role` property can be added to a button, such as `cancel`. + * If a `cancel` role is on one of the buttons, then if the alert is + * dismissed by tapping the backdrop, then it will fire the handler from + * the button with a cancel role. + * + * + * ### Alert Inputs + * + * Alerts can also include several different inputs whose data can be passed + * back to the app. Inputs can be used as a simple way to prompt users for + * information. Radios, checkboxes and text inputs are all accepted, but they + * cannot be mixed. For example, an alert could have all radio button inputs, + * or all checkbox inputs, but the same alert cannot mix radio and checkbox + * inputs. Do note however, different types of "text"" inputs can be mixed, + * such as `url`, `email`, `text`, etc. If you require a complex form UI + * which doesn't fit within the guidelines of an alert then we recommend + * building the form within a modal instead. + * + * + * @usage + * ```ts + * constructor(private alertCtrl: AlertController) { + * + * } + * + * presentAlert() { + * let alert = this.alertCtrl.create({ + * title: 'Low battery', + * subTitle: '10% of battery remaining', + * buttons: ['Dismiss'] + * }); + * alert.present(); + * } + * + * presentConfirm() { + * let alert = this.alertCtrl.create({ + * title: 'Confirm purchase', + * message: 'Do you want to buy this book?', + * buttons: [ + * { + * text: 'Cancel', + * role: 'cancel', + * handler: () => { + * console.log('Cancel clicked'); + * } + * }, + * { + * text: 'Buy', + * handler: () => { + * console.log('Buy clicked'); + * } + * } + * ] + * }); + * alert.present(); + * } + * + * presentPrompt() { + * let alert = this.alertCtrl.create({ + * title: 'Login', + * inputs: [ + * { + * name: 'username', + * placeholder: 'Username' + * }, + * { + * name: 'password', + * placeholder: 'Password', + * type: 'password' + * } + * ], + * buttons: [ + * { + * text: 'Cancel', + * role: 'cancel', + * handler: data => { + * console.log('Cancel clicked'); + * } + * }, + * { + * text: 'Login', + * handler: data => { + * if (User.isValid(data.username, data.password)) { + * // logged in! + * } else { + * // invalid login + * return false; + * } + * } + * } + * ] + * }); + * alert.present(); + * } + * ``` + * @advanced + * + * + * Alert options + * + * | Property | Type | Description | + * |-----------------------|-----------|---------------------------------------------------------------------------| + * | title | `string` | The title for the alert. | + * | subTitle | `string` | The subtitle for the alert. | + * | message | `string` | The message for the alert. | + * | cssClass | `string` | Additional classes for custom styles, separated by spaces. | + * | inputs | `array` | An array of inputs for the alert. See input options. | + * | buttons | `array` | An array of buttons for the alert. See buttons options. | + * | enableBackdropDismiss | `boolean` | Whether the alert should be dismissed by tapping the backdrop. | + * + * + * Input options + * + * | Property | Type | Description | + * |-------------|-----------|-----------------------------------------------------------------| + * | type | `string` | The type the input should be: text, tel, number, etc. | + * | name | `string` | The name for the input. | + * | placeholder | `string` | The input's placeholder (for textual/numeric inputs) | + * | value | `string` | The input's value. | + * | label | `string` | The input's label (only for radio/checkbox inputs) | + * | checked | `boolean` | Whether or not the input is checked. | + * | id | `string` | The input's id. | + * + * Button options + * + * | Property | Type | Description | + * |----------|----------|-----------------------------------------------------------------| + * | text | `string` | The buttons displayed text. | + * | handler | `any` | Emitted when the button is pressed. | + * | cssClass | `string` | An additional CSS class for the button. | + * | role | `string` | The buttons role, null or `cancel`. | + * + * ### Dismissing And Async Navigation + * + * After an alert has been dismissed, the app may need to also transition + * to another page depending on the handler's logic. However, because multiple + * transitions were fired at roughly the same time, it's difficult for the + * nav controller to cleanly animate multiple transitions that may + * have been kicked off asynchronously. This is further described in the + * [`Nav Transition Promises`](../../nav/NavController) section. For alerts, + * this means it's best to wait for the alert to finish its transition + * out before starting a new transition on the same nav controller. + * + * In the example below, after the alert button has been clicked, its handler + * waits on async operation to complete, *then* it uses `pop` to navigate + * back a page in the same stack. The potential problem is that the async operation + * may have been completed before the alert has even finished its transition + * out. In this case, it's best to ensure the alert has finished its transition + * out first, *then* start the next transition. + * + * ```ts + * let alert = this.alertCtrl.create({ + * title: 'Hello', + * buttons: [{ + * text: 'Ok', + * handler: () => { + * // user has clicked the alert button + * // begin the alert's dismiss transition + * let navTransition = alert.dismiss(); + * + * // start some async method + * someAsyncOperation().then(() => { + * // once the async operation has completed + * // then run the next nav transition after the + * // first transition has finished animating out + * + * navTransition.then(() => { + * this.nav.pop(); + * }); + * }); + * return false; + * } + * }] + * }); + * + * alert.present(); + * ``` + * + * It's important to note that the handler returns `false`. A feature of + * button handlers is that they automatically dismiss the alert when their button + * was clicked, however, we'll need more control regarding the transition. Because + * the handler returns `false`, then the alert does not automatically dismiss + * itself. Instead, you now have complete control of when the alert has finished + * transitioning, and the ability to wait for the alert to finish transitioning + * out before starting a new transition. + * + * + * @demo /docs/v2/demos/src/alert/ + */ +@Injectable() +export class AlertController { + + constructor(private _app: App, public config: Config) { } + + /** + * Display an alert with a title, inputs, and buttons + * @param {AlertOptions} opts Alert. See the table below + */ + create(opts: AlertOptions = {}): Alert { + return new Alert(this._app, opts, this.config); + } + +} diff --git a/src/components/alert/alert.ts b/src/components/alert/alert.ts index 7fa69656a3..d084858031 100644 --- a/src/components/alert/alert.ts +++ b/src/components/alert/alert.ts @@ -1,8 +1,8 @@ -import { Injectable } from '@angular/core'; - import { App } from '../app/app'; import { AlertCmp } from './alert-component'; import { AlertOptions, AlertInputOptions } from './alert-options'; +import { AlertPopIn, AlertPopOut, AlertMdPopIn, AlertMdPopOut, AlertWpPopIn, AlertWpPopOut } from './alert-transitions'; +import { Config } from '../../config/config'; import { isPresent } from '../../util/util'; import { NavOptions } from '../../navigation/nav-util'; import { ViewController } from '../../navigation/view-controller'; @@ -14,7 +14,7 @@ import { ViewController } from '../../navigation/view-controller'; export class Alert extends ViewController { private _app: App; - constructor(app: App, opts: AlertOptions = {}) { + constructor(app: App, opts: AlertOptions = {}, config: Config) { opts.inputs = opts.inputs || []; opts.buttons = opts.buttons || []; opts.enableBackdropDismiss = isPresent(opts.enableBackdropDismiss) ? !!opts.enableBackdropDismiss : true; @@ -22,6 +22,13 @@ export class Alert extends ViewController { super(AlertCmp, opts, null); this._app = app; this.isOverlay = true; + + config.setTransition('alert-pop-in', AlertPopIn); + config.setTransition('alert-pop-out', AlertPopOut); + config.setTransition('alert-md-pop-in', AlertMdPopIn); + config.setTransition('alert-md-pop-out', AlertMdPopOut); + config.setTransition('alert-wp-pop-in', AlertWpPopIn); + config.setTransition('alert-wp-pop-out', AlertWpPopOut); } /** @@ -93,234 +100,3 @@ export class Alert extends ViewController { } } - - -/** - * @name AlertController - * @description - * An Alert is a dialog that presents users with information or collects - * information from the user using inputs. An alert appears on top - * of the app's content, and must be manually dismissed by the user before - * they can resume interaction with the app. It can also optionally have a - * `title`, `subTitle` and `message`. - * - * You can pass all of the alert's options in the first argument of - * the create method: `create(opts)`. Otherwise the alert's instance - * has methods to add options, such as `setTitle()` or `addButton()`. - * - * - * ### Alert Buttons - * - * In the array of `buttons`, each button includes properties for its `text`, - * and optionally a `handler`. If a handler returns `false` then the alert - * will not automatically be dismissed when the button is clicked. All - * buttons will show up in the order they have been added to the `buttons` - * array, from left to right. Note: The right most button (the last one in - * the array) is the main button. - * - * Optionally, a `role` property can be added to a button, such as `cancel`. - * If a `cancel` role is on one of the buttons, then if the alert is - * dismissed by tapping the backdrop, then it will fire the handler from - * the button with a cancel role. - * - * - * ### Alert Inputs - * - * Alerts can also include several different inputs whose data can be passed - * back to the app. Inputs can be used as a simple way to prompt users for - * information. Radios, checkboxes and text inputs are all accepted, but they - * cannot be mixed. For example, an alert could have all radio button inputs, - * or all checkbox inputs, but the same alert cannot mix radio and checkbox - * inputs. Do note however, different types of "text"" inputs can be mixed, - * such as `url`, `email`, `text`, etc. If you require a complex form UI - * which doesn't fit within the guidelines of an alert then we recommend - * building the form within a modal instead. - * - * - * @usage - * ```ts - * - * import { AlertController } from 'ionic-angular'; - * - * constructor(private alertCtrl: AlertController) { - * - * } - * - * presentAlert() { - * let alert = this.alertCtrl.create({ - * title: 'Low battery', - * subTitle: '10% of battery remaining', - * buttons: ['Dismiss'] - * }); - * alert.present(); - * } - * - * presentConfirm() { - * let alert = this.alertCtrl.create({ - * title: 'Confirm purchase', - * message: 'Do you want to buy this book?', - * buttons: [ - * { - * text: 'Cancel', - * role: 'cancel', - * handler: () => { - * console.log('Cancel clicked'); - * } - * }, - * { - * text: 'Buy', - * handler: () => { - * console.log('Buy clicked'); - * } - * } - * ] - * }); - * alert.present(); - * } - * - * presentPrompt() { - * let alert = this.alertCtrl.create({ - * title: 'Login', - * inputs: [ - * { - * name: 'username', - * placeholder: 'Username' - * }, - * { - * name: 'password', - * placeholder: 'Password', - * type: 'password' - * } - * ], - * buttons: [ - * { - * text: 'Cancel', - * role: 'cancel', - * handler: data => { - * console.log('Cancel clicked'); - * } - * }, - * { - * text: 'Login', - * handler: data => { - * if (User.isValid(data.username, data.password)) { - * // logged in! - * } else { - * // invalid login - * return false; - * } - * } - * } - * ] - * }); - * alert.present(); - * } - * ``` - * @advanced - * - * - * Alert options - * - * | Property | Type | Description | - * |-----------------------|-----------|---------------------------------------------------------------------------| - * | title | `string` | The title for the alert. | - * | subTitle | `string` | The subtitle for the alert. | - * | message | `string` | The message for the alert. | - * | cssClass | `string` | Additional classes for custom styles, separated by spaces. | - * | mode | `string` | Set alert mode (ios, md, wp). | - * | inputs | `array` | An array of inputs for the alert. See input options. | - * | buttons | `array` | An array of buttons for the alert. See buttons options. | - * | enableBackdropDismiss | `boolean` | Whether the alert should be dismissed by tapping the backdrop. | - * - * - * Input options - * - * | Property | Type | Description | - * |-------------|-----------|-----------------------------------------------------------------| - * | type | `string` | The type the input should be: text, tel, number, etc. | - * | name | `string` | The name for the input. | - * | placeholder | `string` | The input's placeholder (for textual/numeric inputs) | - * | value | `string` | The input's value. | - * | label | `string` | The input's label (only for radio/checkbox inputs) | - * | checked | `boolean` | Whether or not the input is checked. | - * | id | `string` | The input's id. | - * - * Button options - * - * | Property | Type | Description | - * |----------|----------|-----------------------------------------------------------------| - * | text | `string` | The buttons displayed text. | - * | handler | `any` | Emitted when the button is pressed. | - * | cssClass | `string` | An additional CSS class for the button. | - * | role | `string` | The buttons role, null or `cancel`. | - * - * ### Dismissing And Async Navigation - * - * After an alert has been dismissed, the app may need to also transition - * to another page depending on the handler's logic. However, because multiple - * transitions were fired at roughly the same time, it's difficult for the - * nav controller to cleanly animate multiple transitions that may - * have been kicked off asynchronously. This is further described in the - * [`Nav Transition Promises`](../../nav/NavController) section. For alerts, - * this means it's best to wait for the alert to finish its transition - * out before starting a new transition on the same nav controller. - * - * In the example below, after the alert button has been clicked, its handler - * waits on async operation to complete, *then* it uses `pop` to navigate - * back a page in the same stack. The potential problem is that the async operation - * may have been completed before the alert has even finished its transition - * out. In this case, it's best to ensure the alert has finished its transition - * out first, *then* start the next transition. - * - * ```ts - * let alert = this.alertCtrl.create({ - * title: 'Hello', - * buttons: [{ - * text: 'Ok', - * handler: () => { - * // user has clicked the alert button - * // begin the alert's dismiss transition - * let navTransition = alert.dismiss(); - * - * // start some async method - * someAsyncOperation().then(() => { - * // once the async operation has completed - * // then run the next nav transition after the - * // first transition has finished animating out - * - * navTransition.then(() => { - * this.nav.pop(); - * }); - * }); - * return false; - * } - * }] - * }); - * - * alert.present(); - * ``` - * - * It's important to note that the handler returns `false`. A feature of - * button handlers is that they automatically dismiss the alert when their button - * was clicked, however, we'll need more control regarding the transition. Because - * the handler returns `false`, then the alert does not automatically dismiss - * itself. Instead, you now have complete control of when the alert has finished - * transitioning, and the ability to wait for the alert to finish transitioning - * out before starting a new transition. - * - * - * @demo /docs/v2/demos/src/alert/ - */ -@Injectable() -export class AlertController { - - constructor(private _app: App) {} - /** - * Display an alert with a title, inputs, and buttons - * @param {AlertOptions} opts Alert. See the table below - */ - create(opts: AlertOptions = {}): Alert { - return new Alert(this._app, opts); - } - -}