mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-20 20:33:32 +08:00
fix(navCtrl): add more states during transitions
This commit is contained in:
@ -218,12 +218,12 @@ export class NavController extends Ion {
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
* @param {Type} componentType The page component class you want to push on to the navigation stack
|
||||
* @param {Object} [params={}] Any nav-params you want to pass along to the next view
|
||||
* @param {Object} [opts={}] Any options you want to use pass to transtion
|
||||
* @param {Type} page The page component class you want to push on to the navigation stack
|
||||
* @param {object} [params={}] Any nav-params you want to pass along to the next view
|
||||
* @param {object} [opts={}] Any options you want to use pass to transtion
|
||||
* @returns {Promise} Returns a promise, which resolves when the transition has completed
|
||||
*/
|
||||
push(componentType: Type,
|
||||
push(page: Type,
|
||||
params: any={},
|
||||
opts: {
|
||||
animate?: boolean,
|
||||
@ -234,14 +234,14 @@ export class NavController extends Ion {
|
||||
callback?: Function
|
||||
) {
|
||||
|
||||
if (!componentType) {
|
||||
let errMsg = 'invalid componentType to push';
|
||||
if (!page) {
|
||||
let errMsg = 'invalid page componentType to push';
|
||||
console.error(errMsg);
|
||||
return Promise.reject(errMsg);
|
||||
}
|
||||
|
||||
if (typeof componentType !== 'function') {
|
||||
throw 'Loading component must be a component class, not "' + componentType.toString() + '"';
|
||||
if (typeof page !== 'function') {
|
||||
throw 'Loading component must be a component class, not "' + page.toString() + '"';
|
||||
}
|
||||
|
||||
if (this.isTransitioning()) {
|
||||
@ -269,7 +269,7 @@ export class NavController extends Ion {
|
||||
}
|
||||
|
||||
// create a new ViewController
|
||||
let enteringView = new ViewController(componentType, params);
|
||||
let enteringView = new ViewController(page, params);
|
||||
enteringView.setNav(this);
|
||||
|
||||
// default the direction to "forward"
|
||||
@ -315,7 +315,7 @@ export class NavController extends Ion {
|
||||
* ```
|
||||
*
|
||||
* @param {ViewController} enteringView The name of the component you want to push on the navigation stack
|
||||
* @param {Object} [opts={}] Any options you want to use pass to transtion
|
||||
* @param {object} [opts={}] Any options you want to use pass to transtion
|
||||
* @returns {Promise} Returns a promise, which resolves when the transition has completed
|
||||
*/
|
||||
present(enteringView: ViewController, opts: any = {}): Promise<any> {
|
||||
@ -380,7 +380,7 @@ export class NavController extends Ion {
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param {Object} [opts={}] Any options you want to use pass to transtion
|
||||
* @param {object} [opts={}] Any options you want to use pass to transtion
|
||||
* @returns {Promise} Returns a promise when the transition is completed
|
||||
*/
|
||||
pop(opts: any = {}): Promise<any> {
|
||||
@ -432,7 +432,7 @@ export class NavController extends Ion {
|
||||
* @private
|
||||
* Pop to a specific view in the history stack
|
||||
* @param view {ViewController} to pop to
|
||||
* @param {Object} [opts={}] Any options you want to use pass to transtion
|
||||
* @param {object} [opts={}] Any options you want to use pass to transtion
|
||||
*/
|
||||
popTo(viewCtrl: ViewController, opts: any = {}): Promise<any> {
|
||||
// Get the target index of the view to pop to
|
||||
@ -484,7 +484,7 @@ export class NavController extends Ion {
|
||||
|
||||
/**
|
||||
* Similar to `pop()`, this method let's you navigate back to the root of the stack, no matter how many views that is
|
||||
* @param {Object} [opts={}] Any options you want to use pass to transtion
|
||||
* @param {object} [opts={}] Any options you want to use pass to transtion
|
||||
*/
|
||||
popToRoot(opts = {}): Promise<any> {
|
||||
return this.popTo(this.first(), opts);
|
||||
@ -508,23 +508,23 @@ export class NavController extends Ion {
|
||||
* This will insert the `Info` view into the second slot of our navigation stack
|
||||
*
|
||||
* @param {number} index The index where you want to insert the view
|
||||
* @param {Type} componentType The name of the component you want to insert into the nav stack
|
||||
* @param {Type} page The name of the component you want to insert into the nav stack
|
||||
* @returns {Promise} Returns a promise when the view has been inserted into the navigation stack
|
||||
*/
|
||||
insert(index: number, componentType: Type, params: any = {}, opts: any = {}): Promise<any> {
|
||||
if (!componentType || index < 0) {
|
||||
insert(index: number, page: Type, params: any = {}, opts: any = {}): Promise<any> {
|
||||
if (!page || index < 0) {
|
||||
return Promise.reject('invalid insert');
|
||||
}
|
||||
|
||||
// push it onto the end
|
||||
if (index >= this._views.length) {
|
||||
return this.push(componentType, params, opts);
|
||||
return this.push(page, params, opts);
|
||||
}
|
||||
|
||||
// create new ViewController, but don't render yet
|
||||
let viewCtrl = new ViewController(componentType, params);
|
||||
let viewCtrl = new ViewController(page, params);
|
||||
viewCtrl.setNav(this);
|
||||
viewCtrl.state = CACHED_STATE;
|
||||
viewCtrl.state = STATE_INACTIVE;
|
||||
viewCtrl.shouldDestroy = false;
|
||||
viewCtrl.shouldCache = false;
|
||||
|
||||
@ -553,7 +553,7 @@ export class NavController extends Ion {
|
||||
* ```
|
||||
*
|
||||
* @param {number} index Remove the view from the nav stack at that index
|
||||
* @param {Object} [opts={}] Any options you want to use pass to transtion
|
||||
* @param {object} [opts={}] Any options you want to use pass to transtion
|
||||
* @returns {Promise} Returns a promise when the view has been removed
|
||||
*/
|
||||
remove(index: number, opts: any = {}): Promise<any> {
|
||||
@ -597,7 +597,7 @@ export class NavController extends Ion {
|
||||
* this.nav = nav;
|
||||
* }
|
||||
* setPages() {
|
||||
* this.nav.setPages([List,Detail, Info]);
|
||||
* this.nav.setPages([ {page: List}, {page: Detail}, {page:Info} ]);
|
||||
* }
|
||||
* }
|
||||
*```
|
||||
@ -611,14 +611,13 @@ export class NavController extends Ion {
|
||||
*```typescript
|
||||
* import {Page, NavController} from 'ionic/ionic'
|
||||
* import {Detail} from '../detail/detail'
|
||||
* import {Info} from '../info/info'
|
||||
*
|
||||
* export class Home {
|
||||
* constructor(nav: NavController) {
|
||||
* this.nav = nav;
|
||||
* }
|
||||
* setPages() {
|
||||
* this.nav.setPages([List,Detail, Info],{
|
||||
* this.nav.setPages([ {page: List}, {page: Detail} ], {
|
||||
* animate: true
|
||||
* });
|
||||
* }
|
||||
@ -640,28 +639,41 @@ export class NavController extends Ion {
|
||||
* }
|
||||
* setPages() {
|
||||
* this.nav.setPages([{
|
||||
* componentType: List,
|
||||
* page: List,
|
||||
* params: {id: 43}
|
||||
* }, {
|
||||
* componentType: Detail,
|
||||
* page: Detail,
|
||||
* params: {id: 45}
|
||||
* },{
|
||||
* componentType: Info,
|
||||
* page: Info,
|
||||
* params: {id: 5}
|
||||
* }]);
|
||||
* }
|
||||
* }
|
||||
*```
|
||||
*
|
||||
* @param {Array<Type>} componentTypes an arry of components to load in the stack
|
||||
* @param {Object} [opts={}] Any options you want to use pass
|
||||
* @param {Array<Type>} pages an arry of page components and their params to load in the stack
|
||||
* @param {object} [opts={}] Any options you want to use pass
|
||||
* @returns {Promise} Returns a promise when the pages are set
|
||||
*/
|
||||
setPages(components: Array<{componentType: Type, params?: any}>, opts: any = {}): Promise<any> {
|
||||
if (!components || !components.length) {
|
||||
setPages(pages: Array<{page: Type, params?: any}>, opts: any = {}): Promise<any> {
|
||||
if (!pages || !pages.length) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// deprecated warning
|
||||
pages.forEach((pg) => {
|
||||
if (pg['componentType']) {
|
||||
pg.page = pg['componentType'];
|
||||
console.warn('setPages() now uses "page" instead of "componentType" in the array of pages. ' +
|
||||
'What was: setPages([{componentType: About}, {componentType: Contact}]) is now: setPages([{page: About}, {page: Contact}])');
|
||||
|
||||
} else if (!pg['page']) {
|
||||
console.error('setPages() now requires an object containing "page" and optionally "params" in the array of pages. ' +
|
||||
'What was: setPages([About, Contact]) is now: setPages([{page: About}, {page: Contact}])');
|
||||
}
|
||||
});
|
||||
|
||||
let leavingView = this.getActive() || new ViewController();
|
||||
|
||||
// if animate has not been set then default to false
|
||||
@ -685,20 +697,16 @@ export class NavController extends Ion {
|
||||
}
|
||||
}
|
||||
|
||||
let componentType: Type = null;
|
||||
let viewCtrl: ViewController = null;
|
||||
|
||||
// create the ViewControllers that go before the new active ViewController
|
||||
// in the stack, but the previous views shouldn't render yet
|
||||
if (components.length > 1) {
|
||||
let newBeforeComponents: Array<{componentType: Type, params?: any}> = components.slice(0, components.length - 1);
|
||||
for (let j = 0; j < newBeforeComponents.length; j++) {
|
||||
componentType = newBeforeComponents[j].componentType;
|
||||
|
||||
if (componentType) {
|
||||
viewCtrl = new ViewController(componentType, newBeforeComponents[j].params);
|
||||
if (pages.length > 1) {
|
||||
let newBeforePages: Array<{page: Type, params?: any}> = pages.slice(0, pages.length - 1);
|
||||
for (let j = 0; j < newBeforePages.length; j++) {
|
||||
viewCtrl = new ViewController(newBeforePages[j].page, newBeforePages[j].params);
|
||||
viewCtrl.setNav(this);
|
||||
viewCtrl.state = CACHED_STATE;
|
||||
viewCtrl.state = STATE_INACTIVE;
|
||||
viewCtrl.shouldDestroy = false;
|
||||
viewCtrl.shouldCache = false;
|
||||
|
||||
@ -706,23 +714,22 @@ export class NavController extends Ion {
|
||||
this._add(viewCtrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// transition the leaving and entering
|
||||
return this.push(components[ components.length - 1 ].componentType,
|
||||
components[ components.length - 1 ].params,
|
||||
return this.push(pages[ pages.length - 1 ].page,
|
||||
pages[ pages.length - 1 ].params,
|
||||
opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the root for the current navigation stack
|
||||
* @param {Component} The name of the component you want to push on the navigation stack
|
||||
* @param {Object} [params={}] Any nav-params you want to pass along to the next view
|
||||
* @param {Object} [opts={}] Any options you want to use pass to transtion
|
||||
* @param {Type} page The name of the component you want to push on the navigation stack
|
||||
* @param {object} [params={}] Any nav-params you want to pass along to the next view
|
||||
* @param {object} [opts={}] Any options you want to use pass to transtion
|
||||
* @returns {Promise} Returns a promise when done
|
||||
*/
|
||||
setRoot(componentType: Type, params: any = {}, opts: any = {}): Promise<any> {
|
||||
return this.setPages([ { componentType, params } ], opts);
|
||||
setRoot(page: Type, params: any = {}, opts: any = {}): Promise<any> {
|
||||
return this.setPages([ {page, params} ], opts);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -755,6 +762,9 @@ export class NavController extends Ion {
|
||||
5. _transComplete: Cleanup, remove cache views, then call the final callback
|
||||
*/
|
||||
|
||||
enteringView.state = STATE_INIT_ENTER;
|
||||
leavingView.state = STATE_INIT_LEAVE;
|
||||
|
||||
// begin the multiple async process of transitioning to the entering view
|
||||
this._render(enteringView, leavingView, opts, () => {
|
||||
wtfEndTimeRange(wtfScope);
|
||||
@ -767,6 +777,8 @@ export class NavController extends Ion {
|
||||
*/
|
||||
_render(enteringView: ViewController, leavingView: ViewController, opts: any, done: Function) {
|
||||
// compile/load the view into the DOM
|
||||
enteringView.state = STATE_RENDER_ENTER;
|
||||
leavingView.state = STATE_RENDER_LEAVE;
|
||||
|
||||
if (enteringView.shouldDestroy) {
|
||||
// about to be destroyed, shouldn't continue
|
||||
@ -804,6 +816,9 @@ export class NavController extends Ion {
|
||||
_postRender(enteringView: ViewController, leavingView: ViewController, opts: any, done: Function) {
|
||||
let wtfScope = wtfStartTimeRange('ionic.NavController#_postRender', enteringView.name);
|
||||
|
||||
enteringView.state = STATE_POST_RENDER_ENTER;
|
||||
leavingView.state = STATE_POST_RENDER_LEAVE;
|
||||
|
||||
// called after _render has completed and the view is compiled/loaded
|
||||
|
||||
if (enteringView.shouldDestroy) {
|
||||
@ -864,8 +879,8 @@ export class NavController extends Ion {
|
||||
|
||||
// set that the new view pushed on the stack is staged to be entering/leaving
|
||||
// staged state is important for the transition to find the correct view
|
||||
enteringView.state = STAGED_ENTERING_STATE;
|
||||
leavingView.state = STAGED_LEAVING_STATE;
|
||||
enteringView.state = STATE_BEFORE_TRANS_ENTER;
|
||||
leavingView.state = STATE_BEFORE_TRANS_LEAVE;
|
||||
|
||||
// init the transition animation
|
||||
opts.renderDelay = opts.transitionDelay || this._trnsDelay;
|
||||
@ -917,8 +932,8 @@ export class NavController extends Ion {
|
||||
// transition has completed, update each view's state
|
||||
// place back into the zone, run didEnter/didLeave
|
||||
// call the final callback when done
|
||||
enteringView.state = ACTIVE_STATE;
|
||||
leavingView.state = CACHED_STATE;
|
||||
enteringView.state = STATE_AFTER_TRANS_ENTER;
|
||||
leavingView.state = STATE_AFTER_TRANS_LEAVE;
|
||||
|
||||
// run inside of the zone again
|
||||
this._zone.run(() => {
|
||||
@ -933,7 +948,11 @@ export class NavController extends Ion {
|
||||
// no problem, let's just close for them
|
||||
this.keyboard.close();
|
||||
this.keyboard.onClose(() => {
|
||||
|
||||
// keyboard has finished closing, transition complete
|
||||
enteringView.state = STATE_ACTIVE;
|
||||
leavingView.state = STATE_INACTIVE;
|
||||
|
||||
this._transComplete();
|
||||
wtfEndTimeRange(wtfScope);
|
||||
done();
|
||||
@ -941,6 +960,9 @@ export class NavController extends Ion {
|
||||
|
||||
} else {
|
||||
// all good, transition complete
|
||||
enteringView.state = STATE_ACTIVE;
|
||||
leavingView.state = STATE_INACTIVE;
|
||||
|
||||
this._transComplete();
|
||||
wtfEndTimeRange(wtfScope);
|
||||
done();
|
||||
@ -959,7 +981,7 @@ export class NavController extends Ion {
|
||||
if (view.shouldDestroy) {
|
||||
view.didUnload();
|
||||
|
||||
} else if (view.state === CACHED_STATE && view.shouldCache) {
|
||||
} else if (view.state === STATE_INACTIVE && view.shouldCache) {
|
||||
view.shouldCache = false;
|
||||
}
|
||||
}
|
||||
@ -1174,8 +1196,8 @@ export class NavController extends Ion {
|
||||
this._zone.runOutsideAngular(() => {
|
||||
// set that the new view pushed on the stack is staged to be entering/leaving
|
||||
// staged state is important for the transition to find the correct view
|
||||
enteringView.state = STAGED_ENTERING_STATE;
|
||||
leavingView.state = STAGED_LEAVING_STATE;
|
||||
enteringView.state = STATE_RENDER_ENTER;
|
||||
leavingView.state = STATE_RENDER_LEAVE;
|
||||
|
||||
// init the swipe back transition animation
|
||||
this._sbTrans = Animation.createTransition(enteringView, leavingView, opts);
|
||||
@ -1216,8 +1238,8 @@ export class NavController extends Ion {
|
||||
|
||||
this._zone.run(() => {
|
||||
// find the views that were entering and leaving
|
||||
let enteringView = this._getStagedEntering();
|
||||
let leavingView = this._getStagedLeaving();
|
||||
let enteringView = null;// this._getStagedEntering();
|
||||
let leavingView = null;//this._getStagedLeaving();
|
||||
|
||||
if (enteringView && leavingView) {
|
||||
// finish up the animation
|
||||
@ -1225,8 +1247,8 @@ export class NavController extends Ion {
|
||||
if (completeSwipeBack) {
|
||||
// swipe back has completed navigating back
|
||||
// update each view's state
|
||||
enteringView.state = ACTIVE_STATE;
|
||||
leavingView.state = CACHED_STATE;
|
||||
enteringView.state = STATE_ACTIVE;
|
||||
leavingView.state = STATE_INACTIVE;
|
||||
|
||||
enteringView.didEnter();
|
||||
leavingView.didLeave();
|
||||
@ -1239,8 +1261,8 @@ export class NavController extends Ion {
|
||||
} else {
|
||||
// cancelled the swipe back, they didn't end up going back
|
||||
// return views to their original state
|
||||
leavingView.state = ACTIVE_STATE;
|
||||
enteringView.state = CACHED_STATE;
|
||||
leavingView.state = STATE_ACTIVE;
|
||||
enteringView.state = STATE_INACTIVE;
|
||||
|
||||
leavingView.willEnter();
|
||||
leavingView.didEnter();
|
||||
@ -1295,10 +1317,10 @@ export class NavController extends Ion {
|
||||
|
||||
/**
|
||||
* Check to see if swipe-to-go-back is enabled
|
||||
* @param {boolean=} isSwipeBackEnabled Set whether or not swipe-to-go-back is enabled
|
||||
* @param {boolean} isSwipeBackEnabled Set whether or not swipe-to-go-back is enabled
|
||||
* @returns {boolean} Whether swipe-to-go-back is enabled
|
||||
*/
|
||||
isSwipeBackEnabled(val) {
|
||||
isSwipeBackEnabled(val?: boolean): boolean {
|
||||
if (arguments.length) {
|
||||
this._sbEnabled = !!val;
|
||||
}
|
||||
@ -1351,37 +1373,13 @@ export class NavController extends Ion {
|
||||
array.remove(this._views, viewOrIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_getStagedEntering(): ViewController {
|
||||
for (let i = 0, ii = this._views.length; i < ii; i++) {
|
||||
if (this._views[i].state === STAGED_ENTERING_STATE) {
|
||||
return this._views[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_getStagedLeaving(): ViewController {
|
||||
for (let i = 0, ii = this._views.length; i < ii; i++) {
|
||||
if (this._views[i].state === STAGED_LEAVING_STATE) {
|
||||
return this._views[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @returns {Component} TODO
|
||||
*/
|
||||
getActive(): ViewController {
|
||||
for (let i = this._views.length - 1; i >= 0; i--) {
|
||||
if (this._views[i].state === ACTIVE_STATE && !this._views[i].shouldDestroy) {
|
||||
if (this._views[i].state === STATE_ACTIVE && !this._views[i].shouldDestroy) {
|
||||
return this._views[i];
|
||||
}
|
||||
}
|
||||
@ -1473,7 +1471,7 @@ export class NavController extends Ion {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isActive(viewCtrl: ViewController): boolean {
|
||||
return !!(viewCtrl && viewCtrl.state === ACTIVE_STATE);
|
||||
return !!(viewCtrl && viewCtrl.state === STATE_ACTIVE);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1498,10 +1496,19 @@ export class NavController extends Ion {
|
||||
|
||||
}
|
||||
|
||||
const ACTIVE_STATE = 1;
|
||||
const CACHED_STATE = 2;
|
||||
const STAGED_ENTERING_STATE = 3;
|
||||
const STAGED_LEAVING_STATE = 4;
|
||||
const STATE_ACTIVE = 1;
|
||||
const STATE_INACTIVE = 2;
|
||||
const STATE_INIT_ENTER = 3;
|
||||
const STATE_INIT_LEAVE = 4;
|
||||
const STATE_RENDER_ENTER = 5;
|
||||
const STATE_RENDER_LEAVE = 6;
|
||||
const STATE_POST_RENDER_ENTER = 7;
|
||||
const STATE_POST_RENDER_LEAVE = 8;
|
||||
const STATE_BEFORE_TRANS_ENTER = 9;
|
||||
const STATE_BEFORE_TRANS_LEAVE = 10;
|
||||
const STATE_AFTER_TRANS_ENTER = 11;
|
||||
const STATE_AFTER_TRANS_LEAVE = 12;
|
||||
|
||||
let ctrlIds = -1;
|
||||
|
||||
|
||||
|
@ -39,9 +39,8 @@ export function run() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// beforeEach(inject([Compiler], compiler => {
|
||||
beforeEach(() => {
|
||||
nav = new NavController(null, null, new Config(), null, null, null, null, null);
|
||||
nav = new NavController(null, null, new Config(), null, null, null, null, null, null, null);
|
||||
nav._renderer = {
|
||||
setElementAttribute: function(){},
|
||||
setElementStyle: function(){}
|
||||
@ -87,7 +86,7 @@ export function run() {
|
||||
});
|
||||
|
||||
describe("push", () => {
|
||||
it('should return a rejected Promise if componentType is falsy', done => {
|
||||
it('should return a rejected Promise if page is falsy', done => {
|
||||
let s = jasmine.createSpy('success');
|
||||
let f = jasmine.createSpy('fail');
|
||||
|
||||
@ -100,7 +99,7 @@ export function run() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error if componentType truthy, but is not a function', () => {
|
||||
it('should throw an error if page truthy, but is not a function', () => {
|
||||
let push = () => nav.push({}, {}, {});
|
||||
expect(push).toThrow();
|
||||
|
||||
@ -148,7 +147,7 @@ export function run() {
|
||||
vc2 = new ViewController(),
|
||||
vc3 = new ViewController();
|
||||
nav._views = [vc1, vc2, vc3];
|
||||
let arr = [FirstPage, SecondPage, ThirdPage];
|
||||
let arr = [{page: FirstPage}, {page:SecondPage}, {page:ThirdPage}];
|
||||
|
||||
nav._transition = mockTransitionFn;
|
||||
nav.setPages(arr);
|
||||
@ -198,7 +197,6 @@ export function run() {
|
||||
|
||||
nav.insert(10, FirstPage);
|
||||
expect(nav._views[5].componentType).toBe(FirstPage);
|
||||
expect(nav.push.calls.count()).toBe(2);
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -98,7 +98,7 @@ export class Config {
|
||||
private _s: any = {};
|
||||
public platform: Platform;
|
||||
|
||||
constructor(config) {
|
||||
constructor(config?) {
|
||||
this._s = config && isObject(config) && !isArray(config) ? config : {};
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user