refactor(alert): update alert api

* alert poc

* refactor(app): add in app public api

* refactor(nav): implement a public interface and correct types to nav

* feature(helpers): promisify the animation's play method to make it play nicely with promise flows

* test(alert): fix test to ensure alert element is hydrated before using it

* refactor(index): expose the Nav public api types
This commit is contained in:
Dan Bucholtz
2017-11-20 15:51:20 -06:00
committed by GitHub
parent 5c214c3c46
commit ea003350b8
8 changed files with 157 additions and 116 deletions

View File

@ -1,5 +1,5 @@
import { Component, Listen, Method } from '@stencil/core';
import { Alert, AlertEvent, AlertOptions } from '../../index';
import { AlertEvent, AlertOptions } from '../../index';
@Component({
@ -8,10 +8,10 @@ import { Alert, AlertEvent, AlertOptions } from '../../index';
export class AlertController {
private ids = 0;
private alertResolves: { [alertId: string]: Function } = {};
private alerts: Alert[] = [];
private alerts: HTMLIonAlertElement[] = [];
@Method()
create(opts?: AlertOptions): Promise<Alert> {
create(opts?: AlertOptions): Promise<HTMLIonAlertElement> {
// create ionic's wrapping ion-alert component
const alert = document.createElement('ion-alert');
@ -30,14 +30,14 @@ export class AlertController {
appRoot.appendChild(alert as any);
// store the resolve function to be called later up when the action sheet loads
return new Promise<Alert>(resolve => {
return new Promise((resolve) => {
this.alertResolves[alert.alertId] = resolve;
});
}
@Listen('body:ionAlertDidLoad')
protected didLoad(ev: AlertEvent) {
const alert = ev.detail.alert;
const alert = ev.target as HTMLIonAlertElement;
const alertResolve = this.alertResolves[alert.alertId];
if (alertResolve) {
alertResolve(alert);
@ -46,13 +46,15 @@ export class AlertController {
}
@Listen('body:ionAlertWillPresent')
protected willPresent(ev: AlertEvent) {
this.alerts.push(ev.detail.alert);
protected willPresent(event: AlertEvent) {
console.log('willPresent: ', event);
this.alerts.push(event.target as HTMLIonAlertElement);
}
@Listen('body:ionAlertWillDismiss, body:ionAlertDidUnload')
protected willDismiss(ev: AlertEvent) {
const index = this.alerts.indexOf(ev.detail.alert);
protected willDismiss(event: AlertEvent) {
console.log('willDismiss: ', event);
const index = this.alerts.indexOf(event.target as HTMLIonAlertElement);
if (index > -1) {
this.alerts.splice(index, 1);
}

View File

@ -1,6 +1,7 @@
import { Component, CssClassMap, Element, Event, EventEmitter, Listen, Prop } from '@stencil/core';
import { Component, CssClassMap, Element, Event, EventEmitter, Listen, Method, Prop } from '@stencil/core';
import { Animation, AnimationBuilder, AnimationController, Config } from '../../index';
import { playAnimationAsync } from '../../utils/helpers';
import iOSEnterAnimation from './animations/ios.enter';
import iOSLeaveAnimation from './animations/ios.leave';
@ -68,22 +69,15 @@ export class Alert {
@Prop() exitAnimation: AnimationBuilder;
@Prop() alertId: string;
present() {
return new Promise<void>(resolve => {
this._present(resolve);
});
}
private _present(resolve: Function) {
@Method() present() {
if (this.animation) {
this.animation.destroy();
this.animation = null;
}
this.ionAlertWillPresent.emit({ alert: this });
this.ionAlertWillPresent.emit();
// get the user's animation fn if one was provided
let animationBuilder = this.enterAnimation;
if (!animationBuilder) {
// user did not provide a custom animation fn
// decide from the config which animation to use
@ -91,30 +85,26 @@ export class Alert {
}
// build the animation and kick it off
this.animationCtrl.create(animationBuilder, this.el).then(animation => {
return this.animationCtrl.create(animationBuilder, this.el).then(animation => {
this.animation = animation;
animation.onFinish((a: any) => {
a.destroy();
return playAnimationAsync(animation);
}).then((animation) => {
animation.destroy();
const firstInput = this.el.querySelector('[tabindex]') as HTMLElement;
if (firstInput) {
firstInput.focus();
}
this.componentDidEnter();
resolve();
}).play();
this.ionAlertDidPresent.emit();
});
}
dismiss() {
@Method() dismiss() {
if (this.animation) {
this.animation.destroy();
this.animation = null;
}
return new Promise(resolve => {
this.ionAlertWillDismiss.emit({ alert: this });
this.ionAlertWillDismiss.emit();
// get the user's animation fn if one was provided
let animationBuilder = this.exitAnimation;
@ -124,21 +114,16 @@ export class Alert {
animationBuilder = iOSLeaveAnimation;
}
// build the animation and kick it off
this.animationCtrl.create(animationBuilder, this.el).then(animation => {
return this.animationCtrl.create(animationBuilder, this.el).then(animation => {
this.animation = animation;
animation.onFinish((a: any) => {
a.destroy();
this.ionAlertDidDismiss.emit({ alert: this });
return playAnimationAsync(animation);
}).then((animation) => {
animation.destroy();
this.ionAlertDidDismiss.emit();
Context.dom.write(() => {
this.el.parentNode.removeChild(this.el);
});
resolve();
}).play();
});
});
}
@ -470,9 +455,6 @@ export interface AlertButton {
}
export interface AlertEvent extends Event {
detail: {
alert: Alert;
};
}
export { iOSEnterAnimation, iOSLeaveAnimation };

View File

@ -33,55 +33,57 @@
</ion-app>
<script>
var alertController = document.querySelector('ion-alert-controller');
function presentAlert() {
alertController.create({
async function presentAlert() {
var alertController = document.querySelector('ion-alert-controller');
await alertController.componentOnReady();
const alert = await alertController.create({
title: 'Alert',
subTitle: 'Subtitle',
message: 'This is an alert message.',
buttons: ['OK']
})
.then(alert => {
alert.present()
});
return await alert.present();
}
function presentAlertLongMessage() {
alertController.create({
async function presentAlertLongMessage() {
var alertController = document.querySelector('ion-alert-controller');
await alertController.componentOnReady();
const alert = await alertController.create({
title: 'Alert',
message: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum hendrerit diam lorem, a faucibus turpis sagittis eu. In finibus augue in dui varius convallis. Donec vulputate nibh gravida odio vulputate commodo. Suspendisse imperdiet consequat egestas. Nulla feugiat consequat urna eu tincidunt. Cras nec blandit turpis, eu auctor nunc. Pellentesque finibus, magna eu vestibulum imperdiet, arcu ex lacinia massa, eget volutpat quam leo a orci. Etiam mauris est, elementum at feugiat at, dictum in sapien. Mauris efficitur eros sodales convallis egestas. Phasellus eu faucibus nisl. In eu diam vitae libero egestas lacinia. Integer sed convallis metus, nec commodo felis. Duis libero augue, ornare at tempus non, posuere vel augue. Cras mattis dui at tristique aliquam. Phasellus fermentum nibh ligula, porta hendrerit ligula elementum eu. Suspendisse sollicitudin enim at libero iaculis pulvinar. Donec ac massa id purus laoreet rutrum quis eu urna. Mauris luctus erat vel magna porttitor, vel varius erat rhoncus. Donec eu turpis vestibulum, feugiat urna id, gravida mauris. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer at lobortis tortor. Nam ultrices volutpat elit, sed pharetra nulla suscipit at. Nunc eu accumsan eros, id auctor libero. Suspendisse potenti. Nam vitae dapibus metus. Maecenas nisi dui, sagittis et condimentum eu, bibendum vel eros. Vivamus malesuada, tortor in accumsan iaculis, urna velit consectetur ante, nec semper sem diam a diam. In et semper ante. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus blandit, velit vel porttitor euismod, neque risus blandit nulla, non laoreet libero dolor et odio. Nulla enim risus, feugiat eu urna sed, ultrices semper felis. Sed blandit mi diam. Nunc quis mi ligula. Pellentesque a elit eu orci volutpat egestas. Aenean fermentum eleifend quam, ut tincidunt eros tristique et. Nam dapibus tincidunt ligula, id faucibus felis sodales quis. Donec tincidunt lectus ipsum, ac semper tellus cursus ac. Vestibulum nec dui a lectus accumsan vestibulum quis et velit. Aliquam finibus justo et odio euismod, viverra condimentum eros tristique. Sed eget luctus risus. Pellentesque lorem magna, dictum non congue sodales, laoreet eget quam. In sagittis vulputate dolor a ultricies. Donec viverra leo sed ex maximus, in finibus elit gravida. Aliquam posuere vulputate mi. Suspendisse potenti. Nunc consectetur congue arcu, at pharetra dui varius non. Etiam vestibulum congue felis, id ullamcorper neque convallis ultrices. Aenean congue, diam a iaculis mollis, nisl eros maximus arcu, nec hendrerit purus felis porta diam. Nullam vitae ultrices dui, ac dictum sapien. Phasellus eu magna luctus, varius urna id, molestie quam. Nulla in semper tellus. Curabitur lacinia tellus sit amet lacinia dapibus. Sed id condimentum tellus, nec aliquam sapien. Vivamus luctus at ante a tincidunt.',
buttons: ['Cancel', 'OK']
})
.then(alert => {
alert.present()
});
return await alert.present();
}
function presentAlertMultipleButtons() {
alertController.create({
async function presentAlertMultipleButtons() {
var alertController = document.querySelector('ion-alert-controller');
await alertController.componentOnReady();
const alert = await alertController.create({
title: 'Alert',
subTitle: 'Subtitle',
message: 'This is an alert message.',
buttons: ['Cancel', 'Open Modal', 'Delete']
})
.then(alert => {
alert.present()
});
return await alert.present();
}
function presentAlertNoMessage() {
alertController.create({
async function presentAlertNoMessage() {
var alertController = document.querySelector('ion-alert-controller');
await alertController.componentOnReady();
const alert = await alertController.create({
title: 'Alert',
buttons: ['OK']
})
.then(alert => {
alert.present()
});
return await alert.present();
}
function presentAlertConfirm() {
alertController.create({
async function presentAlertConfirm() {
var alertController = document.querySelector('ion-alert-controller');
await alertController.componentOnReady();
const alert = await alertController.create({
title: 'Confirm!',
message: 'Message <strong>text</strong>!!!',
buttons: [
@ -89,8 +91,8 @@
text: 'Cancel',
role: 'cancel',
cssClass: 'secondary',
handler: () => {
console.log('Confirm Cancel')
handler: (blah) => {
console.log('Confirm Cancel: blah');
}
}, {
text: 'Okay',
@ -99,14 +101,14 @@
}
}
]
})
.then(alert => {
alert.present()
});
return await alert.present();
}
function presentAlertPrompt() {
alertController.create({
async function presentAlertPrompt() {
var alertController = document.querySelector('ion-alert-controller');
await alertController.componentOnReady();
const alert = await alertController.create({
title: 'Prompt!',
inputs: [
{
@ -162,14 +164,14 @@
}
}
]
})
.then(alert => {
alert.present()
});
return await alert.present();
}
function presentAlertRadio() {
alertController.create({
async function presentAlertRadio() {
var alertController = document.querySelector('ion-alert-controller');
await alertController.componentOnReady();
const alert = await alertController.create({
title: 'Radio',
inputs: [
{
@ -219,14 +221,16 @@
}
}
]
})
.then(alert => {
alert.present()
});
return await alert.present();
}
function presentAlertCheckbox() {
alertController.create({
async function presentAlertCheckbox() {
var alertController = document.querySelector('ion-alert-controller');
await alertController.componentOnReady();
const alert = await alertController.create({
title: 'Checkbox',
inputs: [
{
@ -281,10 +285,8 @@
}
}
]
})
.then(alert => {
alert.present()
});
return await alert.present();
}

View File

@ -1,8 +1,8 @@
import { Component, Element, Listen, Prop, State } from '@stencil/core';
import { Component, Element, Listen, Method, Prop, State } from '@stencil/core';
import { Config, Nav, NavContainer } from '../../index';
import { isReady } from '../../utils/helpers';
const rootNavs = new Map<number, Nav>();
const rootNavs = new Map<number, NavContainer>();
@Component({
tag: 'ion-app',
@ -24,7 +24,6 @@ export class App {
@Prop({ context: 'config' }) config: Config;
componentWillLoad() {
this.modeCode = this.config.get('mode');
this.useRouter = this.config.getBoolean('useRouter', false);
@ -36,7 +35,20 @@ export class App {
rootNavs.set((event.detail as Nav).navId, (event.detail as Nav));
}
getActiveNavs(rootNavId?: number): NavContainer[] {
@Method() getRootNavs(): NavContainer[] {
const navs: NavContainer[] = [];
rootNavs.forEach((rootNav: NavContainer) => {
navs.push(rootNav);
});
return navs;
}
@Method() isScrolling(): boolean {
// TODO - sync with Manu
return false;
}
@Method() getActiveNavs(rootNavId?: number): NavContainer[] {
/*const portal = portals.get(PORTAL_MODAL);
if (portal && portal.views && portal.views.length) {
return findTopNavs(portal);
@ -60,7 +72,7 @@ export class App {
return activeNavs;
}
getNavByIdOrName(nameOrId: number | string) {
@Method() getNavByIdOrName(nameOrId: number | string) {
const navs = Array.from(rootNavs.values());
for (const navContainer of navs) {
const match = getNavByIdOrNameImpl(navContainer, nameOrId);

View File

@ -0,0 +1,22 @@
/* it is very important to keep this interface in sync with ./nav */
import { NavOptions, ViewController } from '../../index';
export interface PublicNavController {
push(component: any, data?: any, opts?: NavOptions): Promise<any>;
pop(opts?: NavOptions): Promise<any>;
setRoot(component: any, data?: any, opts?: NavOptions): Promise<any>;
insert(insertIndex: number, page: any, params?: any, opts?: NavOptions): Promise<any>;
insertPages(insertIndex: number, insertPages: any[], opts?: NavOptions): Promise<any>;
popToRoot(opts?: NavOptions): Promise<any>;
popTo(indexOrViewCtrl: any, opts?: NavOptions): Promise<any>;
removeIndex(startIndex: number, removeCount?: number, opts?: NavOptions): Promise<any>;
removeView(viewController: ViewController, opts?: NavOptions): Promise<any>;
setPages(componentDataPairs: any[], opts?: NavOptions): Promise<any>;
getActive?(): ViewController;
getPrevious?(view?: ViewController): ViewController;
canGoBack?(nav: PublicNavController): boolean;
canSwipeBack?(): boolean;
getFirstView?(): ViewController;
}

View File

@ -1,13 +1,24 @@
import { Component, Element, Event, EventEmitter, Listen, Method, Prop } from '@stencil/core';
import { ComponentDataPair, Config, FrameworkDelegate, NavController, NavOptions, ViewController } from '../../index';
import { getActiveImpl, getFirstView, getNextNavId, getPreviousImpl, getViews, resolveRoute } from '../../navigation/nav-utils';
import { assert, isReady } from '../../utils/helpers';
import { NavState, RouterEntries, RouterEntry } from '../../index';
import {
ComponentDataPair,
Config,
FrameworkDelegate,
NavController,
NavState,
NavOptions,
PublicNavController,
RouterEntries,
RouterEntry
ViewController
} from '../../index';
import {getActiveImpl, getFirstView, getPreviousImpl, getViews, init } from '../../navigation/nav-utils';
import { isReady } from '../../utils/helpers';
/* it is very important to keep this class in sync with ./nav-interface interface */
@Component({
tag: 'ion-nav',
})
export class Nav {
export class Nav implements PublicNavController {
@Element() element: HTMLElement;
@Event() navInit: EventEmitter;
@ -126,17 +137,17 @@ export class Nav {
}
@Method()
canGoBack(nav: Nav) {
canGoBack(nav: Nav): boolean {
return nav.views && nav.views.length > 0;
}
@Method()
canSwipeBack() {
canSwipeBack(): boolean {
return true; // TODO, implement this for real
}
@Method()
getFirstView() {
getFirstView(): ViewController {
return getFirstView(this);
}

View File

@ -93,6 +93,7 @@ export {
} from './components/modal/modal';
export { ModalController } from './components/modal-controller/modal-controller';
export { Nav } from './components/nav/nav';
export { PublicNavController } from './components/nav/nav-interface';
export { NavController } from './components/nav-controller/nav-controller';
export { Note } from './components/note/note';
export { Page } from './components/page/page';

View File

@ -1,4 +1,4 @@
import { StencilElement } from '..';
import { Animation, StencilElement } from '../index';
export function clamp(min: number, n: number, max: number) {
return Math.max(min, Math.min(n, max));
@ -266,3 +266,12 @@ export function reorderArray(array: any[], indexes: {from: number, to: number}):
array.splice(indexes.to, 0, element);
return array;
}
export function playAnimationAsync(animation: Animation): Promise<Animation> {
return new Promise((resolve) => {
animation.onFinish((ani: Animation) => {
resolve(ani);
});
animation.play();
});
}