diff --git a/demos/infinite-scroll/main.html b/demos/infinite-scroll/main.html index a901e2adde1..72859491ac6 100644 --- a/demos/infinite-scroll/main.html +++ b/demos/infinite-scroll/main.html @@ -10,7 +10,7 @@ - + diff --git a/demos/popover/index.ts b/demos/popover/index.ts index 7175dd77cc1..f64da678d19 100644 --- a/demos/popover/index.ts +++ b/demos/popover/index.ts @@ -4,7 +4,7 @@ import {App, Page, Popover, NavController, Content, NavParams} from 'ionic-angul @Page({ template: ` - + diff --git a/demos/radio/main.html b/demos/radio/main.html index 86b432503fc..01272a0470d 100644 --- a/demos/radio/main.html +++ b/demos/radio/main.html @@ -36,7 +36,7 @@ Enable "Never" - + diff --git a/demos/refresher/main.html b/demos/refresher/main.html index e9d6be46c3a..f6646871e11 100644 --- a/demos/refresher/main.html +++ b/demos/refresher/main.html @@ -4,7 +4,7 @@ - + diff --git a/demos/select/main.html b/demos/select/main.html index 8003bc43898..291b83607c9 100644 --- a/demos/select/main.html +++ b/demos/select/main.html @@ -29,7 +29,7 @@ Date - + January February March @@ -43,7 +43,7 @@ November December - + 1989 1990 1991 diff --git a/src/components/checkbox/checkbox.ts b/src/components/checkbox/checkbox.ts index 47063287d28..5e3d086e9a7 100644 --- a/src/components/checkbox/checkbox.ts +++ b/src/components/checkbox/checkbox.ts @@ -83,7 +83,7 @@ export class Checkbox { /** * @output {Checkbox} expression to evaluate when the checkbox value changes */ - @Output() change: EventEmitter = new EventEmitter(); + @Output() ionChange: EventEmitter = new EventEmitter(); constructor( private _form: Form, @@ -129,7 +129,7 @@ export class Checkbox { if (isChecked !== this._checked) { this._checked = isChecked; if (this._init) { - this.change.emit(this); + this.ionChange.emit(this); } this._item && this._item.setCssClass('item-checkbox-checked', isChecked); } diff --git a/src/components/checkbox/test/basic/main.html b/src/components/checkbox/test/basic/main.html index ca55c839eaa..9a011009166 100644 --- a/src/components/checkbox/test/basic/main.html +++ b/src/components/checkbox/test/basic/main.html @@ -29,13 +29,13 @@ - Kiwi, (change) Secondary color - + Kiwi, (ionChange) Secondary color + - Strawberry, (change) [checked]="true" - + Strawberry, (ionChange) [checked]="true" + diff --git a/src/components/datetime/datetime.ts b/src/components/datetime/datetime.ts index 379d2b4872c..b07b5acef34 100644 --- a/src/components/datetime/datetime.ts +++ b/src/components/datetime/datetime.ts @@ -407,12 +407,12 @@ export class DateTime { /** * @output {any} Any expression to evaluate when the datetime selection has changed. */ - @Output() change: EventEmitter = new EventEmitter(); + @Output() ionChange: EventEmitter = new EventEmitter(); /** * @output {any} Any expression to evaluate when the datetime selection was cancelled. */ - @Output() cancel: EventEmitter = new EventEmitter(); + @Output() ionCancel: EventEmitter = new EventEmitter(); constructor( private _form: Form, @@ -469,7 +469,7 @@ export class DateTime { text: this.cancelText, role: 'cancel', handler: () => { - this.cancel.emit(null); + this.ionCancel.emit(null); } }, { @@ -477,7 +477,7 @@ export class DateTime { handler: (data) => { console.log('datetime, done', data); this.onChange(data); - this.change.emit(data); + this.ionChange.emit(data); } } ]; @@ -485,7 +485,7 @@ export class DateTime { this.generate(picker); this.validate(picker); - picker.change.subscribe(() => { + picker.ionChange.subscribe(() => { this.validate(picker); }); diff --git a/src/components/datetime/test/basic/index.ts b/src/components/datetime/test/basic/index.ts index 886b0a632d1..65ec4a4c072 100644 --- a/src/components/datetime/test/basic/index.ts +++ b/src/components/datetime/test/basic/index.ts @@ -26,6 +26,14 @@ class E2EPage { 'l\u00f8r' ]; + onChange(ev) { + console.log("Changed", ev); + } + + onCancel(ev) { + console.log("Canceled", ev); + } + } diff --git a/src/components/datetime/test/basic/main.html b/src/components/datetime/test/basic/main.html index f5f7f496c42..9d145657c13 100644 --- a/src/components/datetime/test/basic/main.html +++ b/src/components/datetime/test/basic/main.html @@ -44,7 +44,7 @@ h:mm a - + diff --git a/src/components/infinite-scroll/infinite-scroll.ts b/src/components/infinite-scroll/infinite-scroll.ts index 11e5bb47158..f06e8ae083b 100644 --- a/src/components/infinite-scroll/infinite-scroll.ts +++ b/src/components/infinite-scroll/infinite-scroll.ts @@ -22,7 +22,7 @@ import {Content} from '../content/content'; * {% raw %}{{i}}{% endraw %} * * - * + * * * * @@ -67,7 +67,7 @@ import {Content} from '../content/content'; * ```html * * - * + * * @@ -137,7 +137,7 @@ export class InfiniteScroll { * you must call the infinite scroll's `complete()` method when * your async operation has completed. */ - @Output() infinite: EventEmitter = new EventEmitter(); + @Output() ionInfinite: EventEmitter = new EventEmitter(); constructor( @Host() private _content: Content, @@ -179,7 +179,7 @@ export class InfiniteScroll { if (distanceFromInfinite < 0) { this._zone.run(() => { this.state = STATE_LOADING; - this.infinite.emit(this); + this.ionInfinite.emit(this); }); return 5; } diff --git a/src/components/infinite-scroll/test/basic/main.html b/src/components/infinite-scroll/test/basic/main.html index 6194cef1fa3..374b6d26eb8 100644 --- a/src/components/infinite-scroll/test/basic/main.html +++ b/src/components/infinite-scroll/test/basic/main.html @@ -16,7 +16,7 @@ - + diff --git a/src/components/infinite-scroll/test/short-list/main.html b/src/components/infinite-scroll/test/short-list/main.html index 67aa9c5d617..9a33fe0ccd6 100644 --- a/src/components/infinite-scroll/test/short-list/main.html +++ b/src/components/infinite-scroll/test/short-list/main.html @@ -8,7 +8,7 @@ - + diff --git a/src/components/input/input.wp.scss b/src/components/input/input.wp.scss index 37cdecd5b61..7f991f4e79b 100644 --- a/src/components/input/input.wp.scss +++ b/src/components/input/input.wp.scss @@ -107,8 +107,8 @@ ion-input[clearInput] { .text-input-clear-icon { @include svg-background-image($text-input-wp-input-clear-icon-svg); + top: $item-wp-padding-top; right: ($item-wp-padding-right / 2); - bottom: 7px; width: $text-input-wp-input-clear-icon-width; diff --git a/src/components/menu/menu.ts b/src/components/menu/menu.ts index e72833cb69c..91ab505d379 100644 --- a/src/components/menu/menu.ts +++ b/src/components/menu/menu.ts @@ -283,17 +283,17 @@ export class Menu extends Ion { /** * @output {event} When the menu is being dragged open. */ - @Output() opening: EventEmitter = new EventEmitter(); + @Output() ionDrag: EventEmitter = new EventEmitter(); /** * @output {event} When the menu has been opened. */ - @Output() opened: EventEmitter = new EventEmitter(); + @Output() ionOpen: EventEmitter = new EventEmitter(); /** * @output {event} When the menu has been closed. */ - @Output() closed: EventEmitter = new EventEmitter(); + @Output() ionClose: EventEmitter = new EventEmitter(); constructor( private _menuCtrl: MenuController, @@ -444,7 +444,7 @@ export class Menu extends Ion { if (this._isEnabled && this._isSwipeEnabled) { this._prevent(); this._getType().setProgessStep(stepValue); - this.opening.next(stepValue); + this.ionDrag.emit(stepValue); } } @@ -490,12 +490,12 @@ export class Menu extends Ion { if (isOpen) { this._cntEle.addEventListener('click', this.onContentClick); - this.opened.emit(true); + this.ionOpen.emit(true); } else { this.getNativeElement().classList.remove('show-menu'); this.getBackdropElement().classList.remove('show-backdrop'); - this.closed.emit(true); + this.ionClose.emit(true); } } } diff --git a/src/components/menu/test/basic/index.ts b/src/components/menu/test/basic/index.ts index 3e5830c3108..d5ca2e21623 100644 --- a/src/components/menu/test/basic/index.ts +++ b/src/components/menu/test/basic/index.ts @@ -67,8 +67,16 @@ class E2EApp { }); } - onMenuOpening(ev) { - console.log('onMenuOpening', ev); + onDrag(ev) { + console.log('Menu is being dragged', ev); + } + + onOpen(ev) { + console.log('Menu is open', ev); + } + + onClose(ev) { + console.log('Menu is closed', ev); } isChangeDetecting() { diff --git a/src/components/menu/test/basic/main.html b/src/components/menu/test/basic/main.html index d607c74b63e..e77d77771b8 100644 --- a/src/components/menu/test/basic/main.html +++ b/src/components/menu/test/basic/main.html @@ -1,4 +1,4 @@ - + Left Menu @@ -140,4 +140,4 @@ -
\ No newline at end of file +
diff --git a/src/components/modal/modal.ios.scss b/src/components/modal/modal.ios.scss index 68e67f4aa46..cbdd55e7fc3 100644 --- a/src/components/modal/modal.ios.scss +++ b/src/components/modal/modal.ios.scss @@ -6,14 +6,7 @@ $modal-ios-background-color: $background-ios-color !default; $modal-ios-border-radius: 5px !default; -.modal ion-page { - background-color: $modal-ios-background-color; -} .modal-wrapper { - @media only screen and (min-width: 768px) and (min-height: 600px) { - overflow: hidden; - - border-radius: $modal-ios-border-radius; - } + background-color: $modal-ios-background-color; } diff --git a/src/components/modal/modal.md.scss b/src/components/modal/modal.md.scss index 33fafdf095d..479bc69f338 100644 --- a/src/components/modal/modal.md.scss +++ b/src/components/modal/modal.md.scss @@ -6,6 +6,6 @@ $modal-md-background-color: $background-md-color !default; -.modal ion-page { +.modal-wrapper { background-color: $modal-md-background-color; } diff --git a/src/components/modal/modal.ts b/src/components/modal/modal.ts index f83c99e04ef..63a87b5b812 100644 --- a/src/components/modal/modal.ts +++ b/src/components/modal/modal.ts @@ -1,5 +1,7 @@ -import {Component, DynamicComponentLoader, ViewChild, ViewContainerRef} from '@angular/core'; +import {Component, ComponentRef, DynamicComponentLoader, ElementRef, ViewChild, ViewContainerRef} from '@angular/core'; +import {windowDimensions} from '../../util/dom'; +import {pascalCaseToDashCase} from '../../util/util'; import {NavParams} from '../nav/nav-params'; import {ViewController} from '../nav/view-controller'; import {Animation} from '../../animations/animation'; @@ -106,9 +108,12 @@ import {Transition, TransitionOptions} from '../../transitions/transition'; */ export class Modal extends ViewController { + public modalViewType: string; + constructor(componentType, data: any = {}) { data.componentType = componentType; super(ModalCmp, data); + this.modalViewType = componentType.name; this.viewType = 'modal'; this.isOverlay = true; } @@ -129,6 +134,21 @@ export class Modal extends ViewController { return new Modal(componentType, data); } + // Override the load method and load our child component + loaded(done) { + // grab the instance, and proxy the ngAfterViewInit method + let originalNgAfterViewInit = this.instance.ngAfterViewInit; + + this.instance.ngAfterViewInit = () => { + if ( originalNgAfterViewInit ) { + originalNgAfterViewInit(); + } + this.instance.loadComponent().then( (componentRef: ComponentRef) => { + this.setInstance(componentRef.instance); + done(); + }); + }; + } } @Component({ @@ -139,20 +159,22 @@ export class Modal extends ViewController { '
' + '' }) -class ModalCmp { +export class ModalCmp { @ViewChild('viewport', {read: ViewContainerRef}) viewport: ViewContainerRef; - constructor(private _loader: DynamicComponentLoader, private _navParams: NavParams, private _viewCtrl: ViewController) {} - - onPageWillEnter() { - this._loader.loadNextToLocation(this._navParams.data.componentType, this.viewport).then(componentRef => { - this._viewCtrl.setInstance(componentRef.instance); + constructor(protected _eleRef: ElementRef, protected _loader: DynamicComponentLoader, protected _navParams: NavParams, protected _viewCtrl: ViewController) { + } - // manually fire onPageWillEnter() since ModalCmp's onPageWillEnter already happened - this._viewCtrl.willEnter(); + loadComponent(): Promise> { + return this._loader.loadNextToLocation(this._navParams.data.componentType, this.viewport).then(componentRef => { + return componentRef; }); } + + ngAfterViewInit() { + // intentionally kept empty + } } /** @@ -166,6 +188,13 @@ class ModalSlideIn extends Transition { let backdrop = new Animation(ele.querySelector('.backdrop')); backdrop.fromTo('opacity', 0.01, 0.4); let wrapper = new Animation(ele.querySelector('.modal-wrapper')); + let page = ele.querySelector('ion-page'); + page.classList.add('show-page'); + + // auto-add page css className created from component JS class name + let cssClassName = pascalCaseToDashCase((enteringView).modalViewType); + page.classList.add(cssClassName); + wrapper.fromTo('translateY', '100%', '0%'); this .element(enteringView.pageRef()) @@ -191,10 +220,17 @@ class ModalSlideOut extends Transition { super(opts); let ele = leavingView.pageRef().nativeElement; + let backdrop = new Animation(ele.querySelector('.backdrop')); backdrop.fromTo('opacity', 0.4, 0.0); - let wrapper = new Animation(ele.querySelector('.modal-wrapper')); - wrapper.fromTo('translateY', '0%', '100%'); + let wrapperEle = ele.querySelector('.modal-wrapper'); + let wrapperEleRect = wrapperEle.getBoundingClientRect(); + let wrapper = new Animation(wrapperEle); + + // height of the screen - top of the container tells us how much to scoot it down + // so it's off-screen + let screenDimensions = windowDimensions(); + wrapper.fromTo('translateY', '0px', `${screenDimensions.height - wrapperEleRect.top}px`); this .element(leavingView.pageRef()) @@ -216,6 +252,12 @@ class ModalMDSlideIn extends Transition { backdrop.fromTo('opacity', 0.01, 0.4); let wrapper = new Animation(ele.querySelector('.modal-wrapper')); wrapper.fromTo('translateY', '40px', '0px'); + let page = ele.querySelector('ion-page'); + page.classList.add('show-page'); + + // auto-add page css className created from component JS class name + let cssClassName = pascalCaseToDashCase((enteringView).modalViewType); + page.classList.add(cssClassName); this .element(enteringView.pageRef()) diff --git a/src/components/modal/modal.wp.scss b/src/components/modal/modal.wp.scss index 11f8daa08c0..bc5fd336948 100644 --- a/src/components/modal/modal.wp.scss +++ b/src/components/modal/modal.wp.scss @@ -6,6 +6,6 @@ $modal-wp-background-color: $background-wp-color !default; -.modal ion-page { +.modal-wrapper { background-color: $modal-wp-background-color; } diff --git a/src/components/modal/test/basic/index.ts b/src/components/modal/test/basic/index.ts index 7a23ef620f1..1c7ee9e2e8e 100644 --- a/src/components/modal/test/basic/index.ts +++ b/src/components/modal/test/basic/index.ts @@ -68,9 +68,54 @@ class E2EPage { animation: 'my-fade-in' }); } + + presentNavigableModal(){ + let modal = Modal.create(NavigableModal); + this.nav.present(modal); + //this.nav.push(NavigableModal); + } +} + +@Page({ + template: ` + + Page One + + + + + ` +}) +class NavigableModal{ + constructor(private navController:NavController){ + } + + submit(){ + this.navController.push(NavigableModal2); + } +} + +@Page({ + template: ` + + Page Two + + + + + ` +}) +class NavigableModal2{ + constructor(private navController:NavController){ + } + + submit(){ + this.navController.pop(); + } } + @Page({ template: ` @@ -105,6 +150,10 @@ class ModalPassData { this.viewCtrl.dismiss(this.data); } + onPageLoaded(){ + console.log("ModalPassData onPageLoaded fired"); + } + onPageWillEnter(){ console.log("ModalPassData onPagewillEnter fired"); } @@ -280,15 +329,26 @@ class ModalFirstPage { push() { let page = ModalSecondPage; let params = { id: 8675309, myData: [1,2,3,4] }; - let opts = { animation: 'ios-transition' }; - this.nav.push(page, params, opts); + this.nav.push(page, params); } dismiss() { this.nav.rootNav.pop(); } + onPageLoaded(){ + console.log("ModalFirstPage OnPageLoaded fired"); + } + + onPageWillEnter(){ + console.log("ModalFirstPage onPageWillEnter fired"); + } + + onPageDidEnter(){ + console.log("ModalFirstPage onPageDidEnter fired"); + } + openActionSheet() { let actionSheet = ActionSheet.create({ buttons: [ @@ -352,12 +412,21 @@ class ModalFirstPage { ` }) class ModalSecondPage { - constructor( - private nav: NavController, - params: NavParams - ) { + constructor(private nav: NavController, params: NavParams) { console.log('Second page params:', params); } + + onPageLoaded(){ + console.log("ModalSecondPage onPageLoaded"); + } + + onPageWillEnter(){ + console.log("ModalSecondPage onPageWillEnter"); + } + + onPageDidEnter(){ + console.log("ModalSecondPage onPageDidEnter"); + } } @@ -398,4 +467,4 @@ class FadeOut extends Transition { .before.addClass('show-page'); } } -Transition.register('my-fade-out', FadeOut); +Transition.register('my-fade-out', FadeOut); \ No newline at end of file diff --git a/src/components/modal/test/basic/main.html b/src/components/modal/test/basic/main.html index 693650afe4d..7ce7623491d 100644 --- a/src/components/modal/test/basic/main.html +++ b/src/components/modal/test/basic/main.html @@ -1,4 +1,3 @@ - Modals @@ -7,6 +6,9 @@

+

+ +

diff --git a/src/components/modal/test/modal.spec.ts b/src/components/modal/test/modal.spec.ts new file mode 100644 index 00000000000..a7fa498d341 --- /dev/null +++ b/src/components/modal/test/modal.spec.ts @@ -0,0 +1,100 @@ +import {Modal, ModalCmp, Page, NavController, ViewController} from '../../../../src'; + +export function run() { + describe('Modal', () => { + + describe('create', () => { + + it('should have the correct properties on modal view controller instance', () => { + let modalViewController = Modal.create(ComponentToPresent); + expect(modalViewController.modalViewType).toEqual("ComponentToPresent"); + expect(modalViewController.componentType).toEqual(ModalCmp); + expect(modalViewController.viewType).toEqual("modal"); + expect(modalViewController.isOverlay).toEqual(true); + expect(modalViewController instanceof ViewController).toEqual(true); + }); + }); + + describe('loaded', () => { + it('should call done after loading component and call original ngAfterViewInit method', (done) => { + // arrange + let modal = new Modal({}, {}); + let mockInstance = { + ngAfterViewInit: () => {}, + loadComponent: () => {} + }; + let mockComponentRef = { + instance: "someData" + }; + modal.instance = mockInstance; + + let ngAfterViewInitSpy = spyOn(mockInstance, "ngAfterViewInit"); + spyOn(mockInstance, "loadComponent").and.returnValue(Promise.resolve(mockComponentRef)); + + let doneCallback = () => { + // assert + expect(ngAfterViewInitSpy).toHaveBeenCalled(); + expect(modal.instance).toEqual("someData"); + done(); + }; + + // act + modal.loaded(doneCallback); + // (angular calls ngAfterViewInit, we're not testing angular so manually call it) + mockInstance.ngAfterViewInit(); + + }, 5000); + }); + }); + + describe('ModalCmp', () => { + + it('should return a componentRef object after loading component', (done) => { + // arrange + let mockLoader = { + loadNextToLocation: () => {} + }; + let mockNavParams = { + data: { + componentType: "myComponentType" + } + }; + let mockComponentRef = {}; + + spyOn(mockLoader, "loadNextToLocation").and.returnValue(Promise.resolve(mockComponentRef)); + let modalCmp = new ModalCmp(null, mockLoader, mockNavParams, null); + modalCmp.viewport = "mockViewport"; + + // act + modalCmp.loadComponent().then(loadedComponentRef => { + // assert + expect(loadedComponentRef).toEqual(mockComponentRef); + expect(mockLoader.loadNextToLocation).toHaveBeenCalledWith(mockNavParams.data.componentType, modalCmp.viewport); + done(); + }); + }, 5000); + }); +} + +const STATE_ACTIVE = 'active'; +const STATE_INACTIVE = 'inactive'; +const STATE_INIT_ENTER = 'init_enter'; +const STATE_INIT_LEAVE = 'init_leave'; +const STATE_TRANS_ENTER = 'trans_enter'; +const STATE_TRANS_LEAVE = 'trans_leave'; +const STATE_REMOVE = 'remove'; +const STATE_REMOVE_AFTER_TRANS = 'remove_after_trans'; +const STATE_FORCE_ACTIVE = 'force_active'; + + +let componentToPresentSpy = { + _ionicProjectContent: () => {}, +}; + +@Page({ + template: `
` +}) +class ComponentToPresent{ + constructor(){ + } +} diff --git a/src/components/nav/nav-controller.ts b/src/components/nav/nav-controller.ts index 3d2ac2d12ae..629d13e650d 100644 --- a/src/components/nav/nav-controller.ts +++ b/src/components/nav/nav-controller.ts @@ -505,7 +505,8 @@ export class NavController extends Ion { enteringView.setLeavingOpts({ keyboardClose: false, direction: 'back', - animation: enteringView.getTransitionName('back') + animation: enteringView.getTransitionName('back'), + ev: opts.ev }); // start the transition @@ -818,10 +819,10 @@ export class NavController extends Ion { if (!parentNav['_tabs']) { // Tabs can be a parent, but it is not a collection of views // only we're looking for an actual NavController w/ stack of views - leavingView.willLeave(); + leavingView.fireWillLeave(); return parentNav.pop(opts).then((rtnVal: boolean) => { - leavingView.didLeave(); + leavingView.fireDidLeave(); return rtnVal; }); } @@ -918,7 +919,7 @@ export class NavController extends Ion { // set that it is the init leaving view // the first view to be removed, it should init leave view.state = STATE_INIT_LEAVE; - view.willUnload(); + view.fireWillUnload(); // from the index of the leaving view, go backwards and // find the first view that is inactive so it can be the entering @@ -951,8 +952,8 @@ export class NavController extends Ion { // remove views that have been set to be removed, but not // apart of any transitions that will eventually happen this._views.filter(v => v.state === STATE_REMOVE).forEach(view => { - view.willLeave(); - view.didLeave(); + view.fireWillLeave(); + view.fireDidLeave(); this._views.splice(this.indexOf(view), 1); view.destroy(); }); @@ -986,7 +987,7 @@ export class NavController extends Ion { if (!enteringView) { // if no entering view then create a bogus one enteringView = new ViewController(); - enteringView.loaded(); + enteringView.fireLoaded(); } /* Async steps to complete a transition @@ -1042,19 +1043,8 @@ export class NavController extends Ion { this.setTransitioning(true, 500); this.loadPage(enteringView, null, opts, () => { - if (enteringView.onReady) { - // this entering view needs to wait for it to be ready - // this is used by Tabs to wait for the first page of - // the first selected tab to be loaded - enteringView.onReady(() => { - enteringView.loaded(); - this._postRender(transId, enteringView, leavingView, isAlreadyTransitioning, opts, done); - }); - - } else { - enteringView.loaded(); - this._postRender(transId, enteringView, leavingView, isAlreadyTransitioning, opts, done); - } + enteringView.fireLoaded(); + this._postRender(transId, enteringView, leavingView, isAlreadyTransitioning, opts, done); }); } } @@ -1112,13 +1102,13 @@ export class NavController extends Ion { if (leavingView.fireOtherLifecycles) { // only fire entering lifecycle if the leaving // view hasn't explicitly set not to - enteringView.willEnter(); + enteringView.fireWillEnter(); } if (enteringView.fireOtherLifecycles) { // only fire leaving lifecycle if the entering // view hasn't explicitly set not to - leavingView.willLeave(); + leavingView.fireWillLeave(); } } else { @@ -1224,13 +1214,13 @@ export class NavController extends Ion { if (leavingView.fireOtherLifecycles) { // only fire entering lifecycle if the leaving // view hasn't explicitly set not to - enteringView.didEnter(); + enteringView.fireDidEnter(); } if (enteringView.fireOtherLifecycles) { // only fire leaving lifecycle if the entering // view hasn't explicitly set not to - leavingView.didLeave(); + leavingView.fireDidLeave(); } } @@ -1440,56 +1430,61 @@ export class NavController extends Ion { // load the page component inside the nav this._loader.loadNextToLocation(view.componentType, this._viewport, providers).then(component => { - // the ElementRef of the actual ion-page created - let pageElementRef = component.location; // a new ComponentRef has been created // set the ComponentRef's instance to its ViewController view.setInstance(component.instance); - // remember the ChangeDetectorRef for this ViewController - view.setChangeDetector(component.changeDetectorRef); + // the component has been loaded, so call the view controller's loaded method to load any dependencies into the dom + view.loaded( () => { - // remember the ElementRef to the ion-page elementRef that was just created - view.setPageRef(pageElementRef); + // the ElementRef of the actual ion-page created + let pageElementRef = component.location; - // auto-add page css className created from component JS class name - let cssClassName = pascalCaseToDashCase(view.componentType['name']); - this._renderer.setElementClass(pageElementRef.nativeElement, cssClassName, true); + // remember the ChangeDetectorRef for this ViewController + view.setChangeDetector(component.changeDetectorRef); - view.onDestroy(() => { - // ensure the element is cleaned up for when the view pool reuses this element - this._renderer.setElementAttribute(pageElementRef.nativeElement, 'class', null); - this._renderer.setElementAttribute(pageElementRef.nativeElement, 'style', null); - component.destroy(); - }); + // remember the ElementRef to the ion-page elementRef that was just created + view.setPageRef(pageElementRef); - if (!navbarContainerRef) { - // there was not a navbar container ref already provided - // so use the location of the actual navbar template - navbarContainerRef = view.getNavbarViewRef(); - } - - // find a navbar template if one is in the page - let navbarTemplateRef = view.getNavbarTemplateRef(); - - // check if we have both a navbar ViewContainerRef and a template - if (navbarContainerRef && navbarTemplateRef) { - // let's now create the navbar view - let navbarViewRef = navbarContainerRef.createEmbeddedView(navbarTemplateRef); + // auto-add page css className created from component JS class name + let cssClassName = pascalCaseToDashCase(view.componentType['name']); + this._renderer.setElementClass(pageElementRef.nativeElement, cssClassName, true); view.onDestroy(() => { - // manually destroy the navbar when the page is destroyed - navbarViewRef.destroy(); + // ensure the element is cleaned up for when the view pool reuses this element + this._renderer.setElementAttribute(pageElementRef.nativeElement, 'class', null); + this._renderer.setElementAttribute(pageElementRef.nativeElement, 'style', null); + component.destroy(); }); - } - // options may have had a postLoad method - // used mainly by tabs - opts.postLoad && opts.postLoad(view); + if (!navbarContainerRef) { + // there was not a navbar container ref already provided + // so use the location of the actual navbar template + navbarContainerRef = view.getNavbarViewRef(); + } + + // find a navbar template if one is in the page + let navbarTemplateRef = view.getNavbarTemplateRef(); + + // check if we have both a navbar ViewContainerRef and a template + if (navbarContainerRef && navbarTemplateRef) { + // let's now create the navbar view + let navbarViewRef = navbarContainerRef.createEmbeddedView(navbarTemplateRef); + + view.onDestroy(() => { + // manually destroy the navbar when the page is destroyed + navbarViewRef.destroy(); + }); + } - // our job is done here - done(view); + // options may have had a postLoad method + // used mainly by tabs + opts.postLoad && opts.postLoad(view); + + // our job is done here + done(view); + }); }); } diff --git a/src/components/nav/test/nav-controller.spec.ts b/src/components/nav/test/nav-controller.spec.ts index 109f5955d4e..2b2bf7a43c3 100644 --- a/src/components/nav/test/nav-controller.spec.ts +++ b/src/components/nav/test/nav-controller.spec.ts @@ -269,20 +269,20 @@ export function run() { view4.state = STATE_ACTIVE; nav._views = [view1, view2, view3, view4]; - spyOn(view1, 'willLeave'); - spyOn(view1, 'didLeave'); + spyOn(view1, 'fireWillLeave'); + spyOn(view1, 'fireDidLeave'); spyOn(view1, 'destroy'); - spyOn(view2, 'willLeave'); - spyOn(view2, 'didLeave'); + spyOn(view2, 'fireWillLeave'); + spyOn(view2, 'fireDidLeave'); spyOn(view2, 'destroy'); - spyOn(view3, 'willLeave'); - spyOn(view3, 'didLeave'); + spyOn(view3, 'fireWillLeave'); + spyOn(view3, 'fireDidLeave'); spyOn(view3, 'destroy'); - spyOn(view4, 'willLeave'); - spyOn(view4, 'didLeave'); + spyOn(view4, 'fireWillLeave'); + spyOn(view4, 'fireDidLeave'); spyOn(view4, 'destroy'); nav._remove(1, 3); @@ -292,20 +292,20 @@ export function run() { expect(view3.state).toBe(STATE_REMOVE); expect(view4.state).toBe(STATE_INIT_LEAVE); - expect(view1.willLeave).not.toHaveBeenCalled(); - expect(view1.didLeave).not.toHaveBeenCalled(); + expect(view1.fireWillLeave).not.toHaveBeenCalled(); + expect(view1.fireDidLeave).not.toHaveBeenCalled(); expect(view1.destroy).not.toHaveBeenCalled(); - expect(view2.willLeave).toHaveBeenCalled(); - expect(view2.didLeave).toHaveBeenCalled(); + expect(view2.fireWillLeave).toHaveBeenCalled(); + expect(view2.fireDidLeave).toHaveBeenCalled(); expect(view2.destroy).toHaveBeenCalled(); - expect(view3.willLeave).toHaveBeenCalled(); - expect(view3.didLeave).toHaveBeenCalled(); + expect(view3.fireWillLeave).toHaveBeenCalled(); + expect(view3.fireDidLeave).toHaveBeenCalled(); expect(view3.destroy).toHaveBeenCalled(); - expect(view4.willLeave).not.toHaveBeenCalled(); - expect(view4.didLeave).not.toHaveBeenCalled(); + expect(view4.fireWillLeave).not.toHaveBeenCalled(); + expect(view4.fireDidLeave).not.toHaveBeenCalled(); expect(view4.destroy).not.toHaveBeenCalled(); }); }); @@ -412,11 +412,11 @@ export function run() { var done = () => {}; nav._beforeTrans = () => {}; //prevent running beforeTrans for tests - spyOn(enteringView, 'willEnter'); + spyOn(enteringView, 'fireWillEnter'); nav._postRender(1, enteringView, leavingView, false, navOptions, done); - expect(enteringView.willEnter).toHaveBeenCalled(); + expect(enteringView.fireWillEnter).toHaveBeenCalled(); }); it('should not call willEnter on entering view when it is being preloaded', () => { @@ -428,11 +428,11 @@ export function run() { var done = () => {}; nav._beforeTrans = () => {}; //prevent running beforeTrans for tests - spyOn(enteringView, 'willEnter'); + spyOn(enteringView, 'fireWillEnter'); nav._postRender(1, enteringView, leavingView, false, navOptions, done); - expect(enteringView.willEnter).not.toHaveBeenCalled(); + expect(enteringView.fireWillEnter).not.toHaveBeenCalled(); }); it('should call willLeave on leaving view', () => { @@ -442,11 +442,11 @@ export function run() { var done = () => {}; nav._beforeTrans = () => {}; //prevent running beforeTrans for tests - spyOn(leavingView, 'willLeave'); + spyOn(leavingView, 'fireWillLeave'); nav._postRender(1, enteringView, leavingView, false, navOptions, done); - expect(leavingView.willLeave).toHaveBeenCalled(); + expect(leavingView.fireWillLeave).toHaveBeenCalled(); }); it('should not call willEnter when the leaving view has fireOtherLifecycles not true', () => { @@ -456,15 +456,15 @@ export function run() { var done = () => {}; nav._beforeTrans = () => {}; //prevent running beforeTrans for tests - spyOn(enteringView, 'willEnter'); - spyOn(leavingView, 'willLeave'); + spyOn(enteringView, 'fireWillEnter'); + spyOn(leavingView, 'fireWillLeave'); leavingView.fireOtherLifecycles = false; nav._postRender(1, enteringView, leavingView, false, navOptions, done); - expect(enteringView.willEnter).not.toHaveBeenCalled(); - expect(leavingView.willLeave).toHaveBeenCalled(); + expect(enteringView.fireWillEnter).not.toHaveBeenCalled(); + expect(leavingView.fireWillLeave).toHaveBeenCalled(); }); it('should not call willLeave when the entering view has fireOtherLifecycles not true', () => { @@ -474,15 +474,15 @@ export function run() { var done = () => {}; nav._beforeTrans = () => {}; //prevent running beforeTrans for tests - spyOn(enteringView, 'willEnter'); - spyOn(leavingView, 'willLeave'); + spyOn(enteringView, 'fireWillEnter'); + spyOn(leavingView, 'fireWillLeave'); enteringView.fireOtherLifecycles = false; nav._postRender(1, enteringView, leavingView, false, navOptions, done); - expect(enteringView.willEnter).toHaveBeenCalled(); - expect(leavingView.willLeave).not.toHaveBeenCalled(); + expect(enteringView.fireWillEnter).toHaveBeenCalled(); + expect(leavingView.fireWillLeave).not.toHaveBeenCalled(); }); it('should not call willLeave on leaving view when it is being preloaded', () => { @@ -494,11 +494,11 @@ export function run() { var done = () => {}; nav._beforeTrans = () => {}; //prevent running beforeTrans for tests - spyOn(leavingView, 'willLeave'); + spyOn(leavingView, 'fireWillLeave'); nav._postRender(1, enteringView, leavingView, false, navOptions, done); - expect(leavingView.willLeave).not.toHaveBeenCalled(); + expect(leavingView.fireWillLeave).not.toHaveBeenCalled(); }); it('should set animate false when preloading', () => { @@ -725,13 +725,13 @@ export function run() { let doneCalled = false; let done = () => {doneCalled = true;} - spyOn(enteringView, 'didEnter'); - spyOn(leavingView, 'didLeave'); + spyOn(enteringView, 'fireDidEnter'); + spyOn(leavingView, 'fireDidLeave'); nav._afterTrans(enteringView, leavingView, navOpts, hasCompleted, done); - expect(enteringView.didEnter).toHaveBeenCalled(); - expect(leavingView.didLeave).toHaveBeenCalled(); + expect(enteringView.fireDidEnter).toHaveBeenCalled(); + expect(leavingView.fireDidLeave).toHaveBeenCalled(); expect(doneCalled).toBe(true); }); @@ -745,13 +745,13 @@ export function run() { let doneCalled = false; let done = () => {doneCalled = true;} - spyOn(enteringView, 'didEnter'); - spyOn(leavingView, 'didLeave'); + spyOn(enteringView, 'fireDidEnter'); + spyOn(leavingView, 'fireDidLeave'); nav._afterTrans(enteringView, leavingView, navOpts, hasCompleted, done); - expect(enteringView.didEnter).not.toHaveBeenCalled(); - expect(leavingView.didLeave).not.toHaveBeenCalled(); + expect(enteringView.fireDidEnter).not.toHaveBeenCalled(); + expect(leavingView.fireDidLeave).not.toHaveBeenCalled(); expect(doneCalled).toBe(true); }); @@ -765,13 +765,13 @@ export function run() { enteringView.fireOtherLifecycles = false; - spyOn(enteringView, 'didEnter'); - spyOn(leavingView, 'didLeave'); + spyOn(enteringView, 'fireDidEnter'); + spyOn(leavingView, 'fireDidLeave'); nav._afterTrans(enteringView, leavingView, navOpts, hasCompleted, done); - expect(enteringView.didEnter).toHaveBeenCalled(); - expect(leavingView.didLeave).not.toHaveBeenCalled(); + expect(enteringView.fireDidEnter).toHaveBeenCalled(); + expect(leavingView.fireDidLeave).not.toHaveBeenCalled(); expect(doneCalled).toBe(true); }); @@ -785,13 +785,13 @@ export function run() { leavingView.fireOtherLifecycles = false; - spyOn(enteringView, 'didEnter'); - spyOn(leavingView, 'didLeave'); + spyOn(enteringView, 'fireDidEnter'); + spyOn(leavingView, 'fireDidLeave'); nav._afterTrans(enteringView, leavingView, navOpts, hasCompleted, done); - expect(enteringView.didEnter).not.toHaveBeenCalled(); - expect(leavingView.didLeave).toHaveBeenCalled(); + expect(enteringView.fireDidEnter).not.toHaveBeenCalled(); + expect(leavingView.fireDidLeave).toHaveBeenCalled(); expect(doneCalled).toBe(true); }); @@ -803,13 +803,13 @@ export function run() { let doneCalled = false; let done = () => {doneCalled = true;} - spyOn(enteringView, 'didEnter'); - spyOn(leavingView, 'didLeave'); + spyOn(enteringView, 'fireDidEnter'); + spyOn(leavingView, 'fireDidLeave'); nav._afterTrans(enteringView, leavingView, navOpts, hasCompleted, done); - expect(enteringView.didEnter).not.toHaveBeenCalled(); - expect(leavingView.didLeave).not.toHaveBeenCalled(); + expect(enteringView.fireDidEnter).not.toHaveBeenCalled(); + expect(leavingView.fireDidLeave).not.toHaveBeenCalled(); expect(doneCalled).toBe(true); }); diff --git a/src/components/nav/test/view-controller.spec.ts b/src/components/nav/test/view-controller.spec.ts new file mode 100644 index 00000000000..680e29997f7 --- /dev/null +++ b/src/components/nav/test/view-controller.spec.ts @@ -0,0 +1,133 @@ +import {LifeCycleEvent, ViewController} from '../../../../src'; + +export function run() { + describe('ViewController', () => { + + afterEach(() => { + if ( subscription ){ + subscription.unsubscribe(); + } + }); + + describe('loaded', () => { + it('should emit LifeCycleEvent when called with component data', (done) => { + // arrange + let viewController = new ViewController(FakePage); + subscription = viewController.didLoad.subscribe((event:LifeCycleEvent) => { + // assert + expect(event).toEqual(null); + done(); + }, err => { + done(err); + }); + + // act + viewController.fireLoaded(); + }, 10000); + }); + + describe('willEnter', () => { + it('should emit LifeCycleEvent when called with component data', (done) => { + // arrange + let viewController = new ViewController(FakePage); + subscription = viewController.willEnter.subscribe((event:LifeCycleEvent) => { + // assert + expect(event).toEqual(null); + done(); + }, err => { + done(err); + }); + + // act + viewController.fireWillEnter(); + }, 10000); + }); + + describe('didEnter', () => { + it('should emit LifeCycleEvent when called with component data', (done) => { + // arrange + let viewController = new ViewController(FakePage); + subscription = viewController.didEnter.subscribe((event:LifeCycleEvent) => { + // assert + expect(event).toEqual(null); + done(); + }, err => { + done(err); + }); + + // act + viewController.fireDidEnter(); + }, 10000); + }); + + describe('willLeave', () => { + it('should emit LifeCycleEvent when called with component data', (done) => { + // arrange + let viewController = new ViewController(FakePage); + subscription = viewController.willLeave.subscribe((event:LifeCycleEvent) => { + // assert + expect(event).toEqual(null); + done(); + }, err => { + done(err); + }); + + // act + viewController.fireWillLeave(); + }, 10000); + }); + + describe('didLeave', () => { + it('should emit LifeCycleEvent when called with component data', (done) => { + // arrange + let viewController = new ViewController(FakePage); + subscription = viewController.didLeave.subscribe((event:LifeCycleEvent) => { + // assert + expect(event).toEqual(null); + done(); + }, err => { + done(err); + }); + + // act + viewController.fireDidLeave(); + }, 10000); + }); + + describe('willUnload', () => { + it('should emit LifeCycleEvent when called with component data', (done) => { + // arrange + let viewController = new ViewController(FakePage); + subscription = viewController.willUnload.subscribe((event:LifeCycleEvent) => { + expect(event).toEqual(null); + done(); + }, err => { + done(err); + }); + + // act + viewController.fireWillUnload(); + }, 10000); + }); + + describe('destroy', () => { + it('should emit LifeCycleEvent when called with component data', (done) => { + // arrange + let viewController = new ViewController(FakePage); + subscription = viewController.didUnload.subscribe((event:LifeCycleEvent) => { + // assert + expect(event).toEqual(null); + done(); + }, err => { + done(err); + }); + + // act + viewController.destroy(); + }, 10000); + }); + }); + + let subscription = null; + class FakePage{} +} diff --git a/src/components/nav/view-controller.ts b/src/components/nav/view-controller.ts index 6936e2be5d6..53659708177 100644 --- a/src/components/nav/view-controller.ts +++ b/src/components/nav/view-controller.ts @@ -37,6 +37,14 @@ export class ViewController { private _cd: ChangeDetectorRef; protected _nav: NavController; + didLoad: EventEmitter; + willEnter: EventEmitter; + didEnter: EventEmitter; + willLeave: EventEmitter; + didLeave: EventEmitter; + willUnload: EventEmitter; + didUnload: EventEmitter; + /** * @private */ @@ -62,11 +70,6 @@ export class ViewController { */ viewType: string = ''; - /** - * @private - */ - onReady: Function; - /** * @private * If this is currently the active view, then set to false @@ -97,6 +100,14 @@ export class ViewController { constructor(public componentType?: Type, data?: any) { // passed in data could be NavParams, but all we care about is its data object this.data = (data instanceof NavParams ? data.data : (isPresent(data) ? data : {})); + + this.didLoad = new EventEmitter(); + this.willEnter = new EventEmitter(); + this.didEnter = new EventEmitter(); + this.willLeave = new EventEmitter(); + this.didLeave = new EventEmitter(); + this.willUnload = new EventEmitter(); + this.didUnload = new EventEmitter(); } subscribe(generatorOrNext?: any): any { @@ -472,6 +483,16 @@ export class ViewController { isLoaded(): boolean { return this._loaded; } + /** + * The loaded method is used to load any dynamic content/components + * into the dom before proceeding with the transition. If a component needs + * dynamic component loading, extending ViewController and overriding + * this method is a good option + * @param {function} done is a callback that must be called when async loading/actions are completed + */ + loaded(done: (() => any)) { + done(); + } /** * @private @@ -481,8 +502,9 @@ export class ViewController { * to put your setup code for the view; however, it is not the * recommended method to use when a view becomes active. */ - loaded() { + fireLoaded() { this._loaded = true; + this.didLoad.emit(null); ctrlFn(this, 'onPageLoaded'); } @@ -490,7 +512,7 @@ export class ViewController { * @private * The view is about to enter and become the active view. */ - willEnter() { + fireWillEnter() { if (this._cd) { // ensure this has been re-attached to the change detector this._cd.reattach(); @@ -498,7 +520,7 @@ export class ViewController { // detect changes before we run any user code this._cd.detectChanges(); } - + this.willEnter.emit(null); ctrlFn(this, 'onPageWillEnter'); } @@ -507,9 +529,10 @@ export class ViewController { * The view has fully entered and is now the active view. This * will fire, whether it was the first load or loaded from the cache. */ - didEnter() { + fireDidEnter() { let navbar = this.getNavbar(); navbar && navbar.didEnter(); + this.didEnter.emit(null); ctrlFn(this, 'onPageDidEnter'); } @@ -517,7 +540,8 @@ export class ViewController { * @private * The view has is about to leave and no longer be the active view. */ - willLeave() { + fireWillLeave() { + this.willLeave.emit(null); ctrlFn(this, 'onPageWillLeave'); } @@ -526,7 +550,8 @@ export class ViewController { * The view has finished leaving and is no longer the active view. This * will fire, whether it is cached or unloaded. */ - didLeave() { + fireDidLeave() { + this.didLeave.emit(null); ctrlFn(this, 'onPageDidLeave'); // when this is not the active page @@ -538,7 +563,8 @@ export class ViewController { * @private * The view is about to be destroyed and have its elements removed. */ - willUnload() { + fireWillUnload() { + this.willUnload.emit(null); ctrlFn(this, 'onPageWillUnload'); } @@ -553,6 +579,7 @@ export class ViewController { * @private */ destroy() { + this.didUnload.emit(null); ctrlFn(this, 'onPageDidUnload'); for (var i = 0; i < this._destroys.length; i++) { @@ -564,6 +591,10 @@ export class ViewController { } +export interface LifeCycleEvent { + componentType?: any; +} + function ctrlFn(viewCtrl: ViewController, fnName: string) { if (viewCtrl.instance && viewCtrl.instance[fnName]) { try { diff --git a/src/components/option/option.ts b/src/components/option/option.ts index a5abdf83541..d85c2b6aaaa 100644 --- a/src/components/option/option.ts +++ b/src/components/option/option.ts @@ -17,9 +17,9 @@ export class Option { private _value; /** - * @input {any} Event to evaluate when option has changed + * @input {any} Event to evaluate when option is selected */ - @Output() select: EventEmitter = new EventEmitter(); + @Output() ionSelect: EventEmitter = new EventEmitter(); constructor(private _elementRef: ElementRef) {} diff --git a/src/components/picker/picker.ts b/src/components/picker/picker.ts index 6bbd5a95738..ec43364ff42 100644 --- a/src/components/picker/picker.ts +++ b/src/components/picker/picker.ts @@ -17,7 +17,7 @@ import {raf, cancelRaf, CSS, pointerCoord} from '../../util/dom'; */ export class Picker extends ViewController { - @Output() change: EventEmitter; + @Output() ionChange: EventEmitter; constructor(opts: PickerOptions = {}) { opts.columns = opts.columns || []; @@ -28,7 +28,7 @@ export class Picker extends ViewController { this.viewType = 'picker'; this.isOverlay = true; - this.change = new EventEmitter(); + this.ionChange = new EventEmitter(); // by default, pickers should not fire lifecycle events of other views // for example, when an picker enters, the current active view should @@ -122,7 +122,7 @@ class PickerColumnCmp { maxY: number; rotateFactor: number; lastIndex: number; - @Output() change: EventEmitter = new EventEmitter(); + @Output() ionChange: EventEmitter = new EventEmitter(); constructor(config: Config, private _sanitizer: DomSanitizationService) { this.rotateFactor = config.getNumber('pickerRotateFactor', 0); @@ -382,7 +382,7 @@ class PickerColumnCmp { // new selected index has changed from the last index // update the lastIndex and emit that it has changed this.lastIndex = this.col.selectedIndex; - this.change.emit(this.col.options[this.col.selectedIndex]); + this.ionChange.emit(this.col.options[this.col.selectedIndex]); } } } @@ -445,7 +445,7 @@ class PickerColumnCmp { '' + '
' + '
' + - '
(change)="_colChange($event)"
' + + '
(ionChange)="_colChange($event)"
' + '
' + '
' + '', @@ -538,7 +538,7 @@ class PickerDisplayCmp { private _colChange(selectedOption: PickerColumnOption) { // one of the columns has changed its selected index var picker = this._viewCtrl; - picker.change.emit(this.getSelected()); + picker.ionChange.emit(this.getSelected()); } @HostListener('body:keyup', ['$event']) diff --git a/src/components/popover/popover.ts b/src/components/popover/popover.ts index 5f18b2bd000..39d0ea600d2 100644 --- a/src/components/popover/popover.ts +++ b/src/components/popover/popover.ts @@ -10,7 +10,8 @@ import {isPresent, isUndefined, isDefined} from '../../util/util'; import {nativeRaf, CSS} from '../../util/dom'; import {ViewController} from '../nav/view-controller'; -const POPOVER_BODY_PADDING = 2; +const POPOVER_IOS_BODY_PADDING = 2; +const POPOVER_MD_BODY_PADDING = 12; /** * @name Popover @@ -194,7 +195,7 @@ class PopoverCmp { this._viewCtrl.setInstance(componentRef.instance); // manually fire onPageWillEnter() since PopoverCmp's onPageWillEnter already happened - this._viewCtrl.willEnter(); + this._viewCtrl.fireWillEnter(); }); } @@ -240,8 +241,7 @@ class PopoverTransition extends Transition { super(opts); } - - positionView(nativeEle: HTMLElement, ev) { + mdPositionView(nativeEle: HTMLElement, ev) { let originY = 'top'; let originX = 'left'; @@ -257,20 +257,74 @@ class PopoverTransition extends Transition { let bodyWidth = window.innerWidth; let bodyHeight = window.innerHeight; - let targetTop = (bodyHeight / 2) - (popoverHeight / 2); - let targetLeft = bodyWidth / 2; - let targetWidth = 0; - let targetHeight = 0; - // If ev was passed, use that for target element - if (ev && ev.target) { - let targetDim = ev.target.getBoundingClientRect(); - targetTop = targetDim.top; - targetLeft = targetDim.left; - targetWidth = targetDim.width; - targetHeight = targetDim.height; + let targetDim = ev && ev.target && ev.target.getBoundingClientRect(); + + let targetTop = targetDim && targetDim.top || (bodyHeight / 2) - (popoverHeight / 2); + let targetLeft = targetDim && targetDim.left || bodyWidth / 2 - (popoverWidth / 2); + let targetWidth = targetDim && targetDim.width || 0; + let targetHeight = targetDim && targetDim.height || 0; + + let popoverCSS = { + top: targetTop, + left: targetLeft + }; + + // If the popover left is less than the padding it is off screen + // to the left so adjust it, else if the width of the popover + // exceeds the body width it is off screen to the right so adjust + if (popoverCSS.left < POPOVER_MD_BODY_PADDING) { + popoverCSS.left = POPOVER_MD_BODY_PADDING; + } else if (popoverWidth + POPOVER_MD_BODY_PADDING + popoverCSS.left > bodyWidth) { + popoverCSS.left = bodyWidth - popoverWidth - POPOVER_MD_BODY_PADDING; + originX = 'right'; + } + + // If the popover when popped down stretches past bottom of screen, + // make it pop up if there's room above + if (targetTop + targetHeight + popoverHeight > bodyHeight && targetTop - popoverHeight > 0) { + popoverCSS.top = targetTop - popoverHeight; + nativeEle.className = nativeEle.className + ' popover-bottom'; + originY = 'bottom'; + // If there isn't room for it to pop up above the target cut it off + } else if (targetTop + targetHeight + popoverHeight > bodyHeight) { + popoverEle.style.bottom = POPOVER_MD_BODY_PADDING + 'px'; } + popoverEle.style.top = popoverCSS.top + 'px'; + popoverEle.style.left = popoverCSS.left + 'px'; + + popoverEle.style[CSS.transformOrigin] = originY + ' ' + originX; + + // Since the transition starts before styling is done we + // want to wait for the styles to apply before showing the wrapper + popoverWrapperEle.style.opacity = '1'; + } + + iosPositionView(nativeEle: HTMLElement, ev) { + let originY = 'top'; + let originX = 'left'; + + let popoverWrapperEle = nativeEle.querySelector('.popover-wrapper'); + + // Popover content width and height + let popoverEle = nativeEle.querySelector('.popover-content'); + let popoverDim = popoverEle.getBoundingClientRect(); + let popoverWidth = popoverDim.width; + let popoverHeight = popoverDim.height; + + // Window body width and height + let bodyWidth = window.innerWidth; + let bodyHeight = window.innerHeight; + + // If ev was passed, use that for target element + let targetDim = ev && ev.target && ev.target.getBoundingClientRect(); + + let targetTop = targetDim && targetDim.top || (bodyHeight / 2) - (popoverHeight / 2); + let targetLeft = targetDim && targetDim.left || bodyWidth / 2; + let targetWidth = targetDim && targetDim.width || 0; + let targetHeight = targetDim && targetDim.height || 0; + // The arrow that shows above the popover on iOS var arrowEle = nativeEle.querySelector('.popover-arrow'); let arrowDim = arrowEle.getBoundingClientRect(); @@ -290,10 +344,10 @@ class PopoverTransition extends Transition { // If the popover left is less than the padding it is off screen // to the left so adjust it, else if the width of the popover // exceeds the body width it is off screen to the right so adjust - if (popoverCSS.left < POPOVER_BODY_PADDING) { - popoverCSS.left = POPOVER_BODY_PADDING; - } else if (popoverWidth + POPOVER_BODY_PADDING + popoverCSS.left > bodyWidth) { - popoverCSS.left = bodyWidth - popoverWidth - POPOVER_BODY_PADDING; + if (popoverCSS.left < POPOVER_IOS_BODY_PADDING) { + popoverCSS.left = POPOVER_IOS_BODY_PADDING; + } else if (popoverWidth + POPOVER_IOS_BODY_PADDING + popoverCSS.left > bodyWidth) { + popoverCSS.left = bodyWidth - popoverWidth - POPOVER_IOS_BODY_PADDING; originX = 'right'; } @@ -306,7 +360,7 @@ class PopoverTransition extends Transition { originY = 'bottom'; // If there isn't room for it to pop up above the target cut it off } else if (targetTop + targetHeight + popoverHeight > bodyHeight) { - popoverEle.style.bottom = POPOVER_BODY_PADDING + '%'; + popoverEle.style.bottom = POPOVER_IOS_BODY_PADDING + '%'; } arrowEle.style.top = arrowCSS.top + 'px'; @@ -315,7 +369,7 @@ class PopoverTransition extends Transition { popoverEle.style.top = popoverCSS.top + 'px'; popoverEle.style.left = popoverCSS.left + 'px'; - popoverEle.style[CSS.transformOrigin] = originY + " " + originX; + popoverEle.style[CSS.transformOrigin] = originY + ' ' + originX; // Since the transition starts before styling is done we // want to wait for the styles to apply before showing the wrapper @@ -344,7 +398,7 @@ class PopoverPopIn extends PopoverTransition { play() { nativeRaf(() => { - this.positionView(this.enteringView.pageRef().nativeElement, this.opts.ev); + this.iosPositionView(this.enteringView.pageRef().nativeElement, this.opts.ev); super.play(); }); } @@ -394,7 +448,7 @@ class PopoverMdPopIn extends PopoverTransition { play() { nativeRaf(() => { - this.positionView(this.enteringView.pageRef().nativeElement, this.opts.ev); + this.mdPositionView(this.enteringView.pageRef().nativeElement, this.opts.ev); super.play(); }); } diff --git a/src/components/popover/test/basic/index.ts b/src/components/popover/test/basic/index.ts index 9c41e395a14..15a28ccb047 100644 --- a/src/components/popover/test/basic/index.ts +++ b/src/components/popover/test/basic/index.ts @@ -4,7 +4,7 @@ import {App, Page, Popover, NavController, Content, NavParams, ViewController} f @Page({ template: ` - + diff --git a/src/components/popover/test/basic/main.html b/src/components/popover/test/basic/main.html index d1893649aa5..3ebe3c85fc2 100644 --- a/src/components/popover/test/basic/main.html +++ b/src/components/popover/test/basic/main.html @@ -19,6 +19,11 @@
+ + + diff --git a/src/components/slides/test/intro/index.ts b/src/components/slides/test/intro/index.ts index 32161fddaa4..cd626b8e5bf 100644 --- a/src/components/slides/test/intro/index.ts +++ b/src/components/slides/test/intro/index.ts @@ -19,6 +19,7 @@ class IntroPage { continueText: string = "Skip"; startingIndex: number = 1; mySlideOptions; + showSlide: boolean = true; constructor(private nav: NavController) { this.mySlideOptions = { @@ -42,6 +43,10 @@ class IntroPage { console.log("Slide move", slider); } + toggleLastSlide() { + this.showSlide = !this.showSlide; + } + skip() { this.nav.push(MainPage); } diff --git a/src/components/slides/test/intro/main.html b/src/components/slides/test/intro/main.html index f2a1e4d8884..8f810f8714d 100644 --- a/src/components/slides/test/intro/main.html +++ b/src/components/slides/test/intro/main.html @@ -8,7 +8,7 @@ - +

Thank you for choosing the Awesome App!

+
- +

Any questions?

diff --git a/src/components/slides/test/loop/main.html b/src/components/slides/test/loop/main.html index b6a9af77fa5..920523baad3 100644 --- a/src/components/slides/test/loop/main.html +++ b/src/components/slides/test/loop/main.html @@ -1,5 +1,5 @@
- + Loop {{ slide.name }} @@ -8,7 +8,7 @@
- + Don't Loop {{ slide.name }} diff --git a/src/components/tabs/tab-button.ts b/src/components/tabs/tab-button.ts index 5c44f9a3899..2d91a4c8b0d 100644 --- a/src/components/tabs/tab-button.ts +++ b/src/components/tabs/tab-button.ts @@ -32,7 +32,7 @@ export class TabButton extends Ion { private _layout: string; @Input() tab: Tab; - @Output() select: EventEmitter = new EventEmitter(); + @Output() ionSelect: EventEmitter = new EventEmitter(); constructor(config: Config, elementRef: ElementRef) { super(elementRef); @@ -53,6 +53,6 @@ export class TabButton extends Ion { @HostListener('click') private onClick() { - this.select.emit(this.tab); + this.ionSelect.emit(this.tab); } } diff --git a/src/components/tabs/tab.ts b/src/components/tabs/tab.ts index 58b32f0f2ca..0e655bef588 100644 --- a/src/components/tabs/tab.ts +++ b/src/components/tabs/tab.ts @@ -85,13 +85,13 @@ import {TabButton} from './tab-button'; * ``` * * Sometimes you may want to call a method instead of navigating to a new - * page. You can use the `(select)` event to call a method on your class when + * page. You can use the `(ionSelect)` event to call a method on your class when * the tab is selected. Below is an example of presenting a modal from one of * the tabs. * * ```html * - * + * * * ``` * @@ -204,7 +204,7 @@ export class Tab extends NavController { /** * @output {Tab} Method to call when the current tab is selected */ - @Output() select: EventEmitter = new EventEmitter(); + @Output() ionSelect: EventEmitter = new EventEmitter(); constructor( @Inject(forwardRef(() => Tabs)) parentTabs: Tabs, diff --git a/src/components/tabs/tabs.ts b/src/components/tabs/tabs.ts index 65eecf694e8..eecb7207c03 100644 --- a/src/components/tabs/tabs.ts +++ b/src/components/tabs/tabs.ts @@ -203,7 +203,7 @@ export class Tabs extends Ion { /** * @input {any} Expression to evaluate when the tab changes. */ - @Output() change: EventEmitter = new EventEmitter(); + @Output() ionChange: EventEmitter = new EventEmitter(); /** * @private @@ -252,7 +252,7 @@ export class Tabs extends Ion { viewCtrl.setContent(this); viewCtrl.setContentRef(_elementRef); - viewCtrl.onReady = (done) => { + viewCtrl.loaded = (done) => { this._onReady = done; }; } @@ -272,7 +272,7 @@ export class Tabs extends Ion { } this._btns.toArray().forEach((tabButton: TabButton) => { - tabButton.select.subscribe((tab: Tab) => { + tabButton.ionSelect.subscribe((tab: Tab) => { this.select(tab); }); }); @@ -357,16 +357,16 @@ export class Tabs extends Ion { let deselectedPage; if (deselectedTab) { deselectedPage = deselectedTab.getActive(); - deselectedPage && deselectedPage.willLeave(); + deselectedPage && deselectedPage.fireWillLeave(); } let selectedPage = selectedTab.getActive(); - selectedPage && selectedPage.willEnter(); + selectedPage && selectedPage.fireWillEnter(); selectedTab.load(opts, () => { - selectedTab.select.emit(selectedTab); - this.change.emit(selectedTab); + selectedTab.ionSelect.emit(selectedTab); + this.ionChange.emit(selectedTab); if (selectedTab.root) { // only show the selectedTab if it has a root @@ -382,8 +382,8 @@ export class Tabs extends Ion { } } - selectedPage && selectedPage.didEnter(); - deselectedPage && deselectedPage.didLeave(); + selectedPage && selectedPage.fireDidEnter(); + deselectedPage && deselectedPage.fireDidLeave(); if (this._onReady) { this._onReady(); @@ -445,8 +445,8 @@ export class Tabs extends Ion { let instance = active.instance; // If they have a custom tab selected handler, call it - if (instance.tabSelected) { - return instance.tabSelected(); + if (instance.ionSelected) { + return instance.ionSelected(); } // If we're a few pages deep, pop to root diff --git a/src/components/tabs/test/advanced/index.ts b/src/components/tabs/test/advanced/index.ts index b4a1f76ccb8..b98d6b8dcdf 100644 --- a/src/components/tabs/test/advanced/index.ts +++ b/src/components/tabs/test/advanced/index.ts @@ -73,14 +73,14 @@ class TabsPage { constructor(private nav: NavController, private params: NavParams) {} ngAfterViewInit() { - this.tabs.change.subscribe(tab => { - console.log('tabs.change.subscribe', tab.index); + this.tabs.ionChange.subscribe(tab => { + console.log('tabs.ionChange.subscribe', tab.index); }); } onTabChange() { // wired up through the template - // + // console.log('onTabChange'); } diff --git a/src/components/tabs/test/advanced/tabs.html b/src/components/tabs/test/advanced/tabs.html index aba5d9491cf..338c2b32fff 100644 --- a/src/components/tabs/test/advanced/tabs.html +++ b/src/components/tabs/test/advanced/tabs.html @@ -1,7 +1,7 @@ - + - + diff --git a/src/components/tabs/test/basic/index.ts b/src/components/tabs/test/basic/index.ts index a88daac05a4..b1c691879ed 100644 --- a/src/components/tabs/test/basic/index.ts +++ b/src/components/tabs/test/basic/index.ts @@ -171,8 +171,8 @@ export class Tab3 { - - + + @@ -182,6 +182,14 @@ export class TabsPage { root1 = Tab1; root2 = Tab2; root3 = Tab3; + + onChange(ev) { + console.log("Changed tab", ev); + } + + onSelect(ev) { + console.log("Selected tab", ev); + } } @App({ diff --git a/src/components/toggle/test/basic/main.html b/src/components/toggle/test/basic/main.html index 0cb8d0d4665..2ab708e6d04 100644 --- a/src/components/toggle/test/basic/main.html +++ b/src/components/toggle/test/basic/main.html @@ -19,12 +19,12 @@ Apple, ngControl - + Banana, ngControl - + @@ -38,13 +38,13 @@ - Kiwi, (change) Secondary color - + Kiwi, (ionChange) Secondary color + - Strawberry, (change) [checked]="true" - + Strawberry, (ionChange) [checked]="true" + diff --git a/src/components/toggle/toggle.ts b/src/components/toggle/toggle.ts index 26e92830850..662de1335c2 100644 --- a/src/components/toggle/toggle.ts +++ b/src/components/toggle/toggle.ts @@ -96,7 +96,7 @@ export class Toggle implements ControlValueAccessor { /** * @output {Toggle} expression to evaluate when the toggle value changes */ - @Output() change: EventEmitter = new EventEmitter(); + @Output() ionChange: EventEmitter = new EventEmitter(); constructor( private _form: Form, @@ -195,7 +195,7 @@ export class Toggle implements ControlValueAccessor { if (isChecked !== this._checked) { this._checked = isChecked; if (this._init) { - this.change.emit(this); + this.ionChange.emit(this); } this._item && this._item.setCssClass('item-toggle-checked', isChecked); }