refactor(nav): simplify ViewController

This commit is contained in:
Manu Mtz.-Almeida
2018-04-01 18:04:36 +02:00
parent ff06dab3c0
commit 853e55388b
6 changed files with 199 additions and 306 deletions

View File

@ -3517,23 +3517,21 @@ declare global {
interface HTMLIonNavElement extends HTMLStencilElement {
'animated': boolean;
'canGoBack': (view?: ViewController) => boolean;
'delegate': FrameworkDelegate;
'delegate': FrameworkDelegate|undefined;
'getActive': () => ViewController;
'getByIndex': (index: number) => ViewController;
'getPrevious': (view?: ViewController) => ViewController;
'getRouteId': () => RouteID;
'getViews': () => ViewController[];
'insert': (insertIndex: number, component: NavComponent, componentProps?: ComponentProps, opts?: NavOptions, done?: TransitionDoneFn) => Promise<boolean>;
'insertPages': (insertIndex: number, insertComponents: NavComponent[], opts?: NavOptions, done?: TransitionDoneFn) => Promise<boolean>;
'length': () => number;
'pop': (opts?: NavOptions, done?: TransitionDoneFn) => Promise<boolean>;
'popAll': () => Promise<boolean[]>;
'popTo': (indexOrViewCtrl: number | ViewController, opts?: NavOptions, done?: TransitionDoneFn) => Promise<boolean>;
'popToRoot': (opts?: NavOptions, done?: TransitionDoneFn) => Promise<boolean>;
'push': (component: NavComponent, componentProps?: ComponentProps, opts?: NavOptions, done?: TransitionDoneFn) => Promise<boolean>;
'removeIndex': (startIndex: number, removeCount?: number, opts?: NavOptions, done?: TransitionDoneFn) => Promise<boolean>;
'removeView': (viewController: ViewController, opts?: NavOptions, done?: TransitionDoneFn) => Promise<boolean>;
'root': NavComponent;
'rootParams': ComponentProps;
'root': NavComponent|undefined;
'rootParams': ComponentProps|undefined;
'setPages': (views: any[], opts?: NavOptions, done?: TransitionDoneFn) => Promise<boolean>;
'setRoot': (component: NavComponent, componentProps?: ComponentProps, opts?: NavOptions, done?: TransitionDoneFn) => Promise<boolean>;
'setRouteId': (id: string, params: any, direction: number) => Promise<RouteWrite>;
@ -3557,10 +3555,10 @@ declare global {
namespace JSXElements {
export interface IonNavAttributes extends HTMLAttributes {
'animated'?: boolean;
'delegate'?: FrameworkDelegate;
'delegate'?: FrameworkDelegate|undefined;
'onIonNavChanged'?: (event: CustomEvent<void>) => void;
'root'?: NavComponent;
'rootParams'?: ComponentProps;
'root'?: NavComponent|undefined;
'rootParams'?: ComponentProps|undefined;
'swipeBackEnabled'?: boolean;
}
}
@ -4714,7 +4712,7 @@ declare global {
'commit': (enteringEl: HTMLElement, leavingEl: HTMLElement, opts?: RouterOutletOptions) => Promise<boolean>;
'delegate': FrameworkDelegate;
'getRouteId': () => RouteID;
'setRoot': (component: string | HTMLElement, params?: { [key: string]: any; }, opts?: RouterOutletOptions) => Promise<boolean>;
'setRoot': (component: ComponentRef, params?: ComponentProps, opts?: RouterOutletOptions) => Promise<boolean>;
'setRouteId': (id: string, params: any, direction: number) => Promise<RouteWrite>;
}
var HTMLIonRouterOutletElement: {

View File

@ -1,11 +1,11 @@
import { ViewController, isViewController } from './view-controller';
import { ViewController } from './view-controller';
import { Animation, ComponentRef, FrameworkDelegate } from '../..';
export function convertToView(page: any, params: any): ViewController|null {
if (!page) {
return null;
}
if (isViewController(page)) {
if (page instanceof ViewController) {
return page;
}
return new ViewController(page, params);
@ -13,7 +13,7 @@ export function convertToView(page: any, params: any): ViewController|null {
export function convertToViews(pages: any[]): ViewController[] {
return pages.map(page => {
if (isViewController(page)) {
if (page instanceof ViewController) {
return page;
}
if ('page' in page) {
@ -23,10 +23,6 @@ export function convertToViews(pages: any[]): ViewController[] {
}).filter(v => v !== null) as ViewController[];
}
export function isPresent(val: any): val is any {
return val !== undefined && val !== null;
}
export const enum ViewState {
New = 1,
Attached,
@ -38,7 +34,7 @@ export const enum NavDirection {
Forward = 'forward'
}
export type NavComponent = ComponentRef | ViewController | Function;
export type NavComponent = ComponentRef | ViewController;
export interface NavResult {
hasCompleted: boolean;

View File

@ -8,10 +8,9 @@ import {
TransitionInstruction,
ViewState,
convertToViews,
isPresent,
} from './nav-util';
import { ViewController, isViewController } from './view-controller';
import { ViewController, matches } from './view-controller';
import { Animation, ComponentProps, Config, DomController, FrameworkDelegate, GestureDetail, NavOutlet } from '../..';
import { RouteID, RouteWrite, RouterDirection } from '../router/utils/interfaces';
import { AnimationOptions, ViewLifecycle, lifecycle, transition } from '../../utils/transition';
@ -25,13 +24,13 @@ import mdTransitionAnimation from './animations/md.transition';
})
export class Nav implements NavOutlet {
private _init = false;
private _queue: TransitionInstruction[] = [];
private _sbTrns: Animation;
private init = false;
private queue: TransitionInstruction[] = [];
private sbTrns: Animation|undefined;
private useRouter = false;
isTransitioning = false;
private _destroyed = false;
private _views: ViewController[] = [];
private isTransitioning = false;
private destroyed = false;
private views: ViewController[] = [];
mode: string;
@ -43,9 +42,9 @@ export class Nav implements NavOutlet {
@Prop({ connect: 'ion-animation-controller' }) animationCtrl: HTMLIonAnimationControllerElement;
@Prop({ mutable: true }) swipeBackEnabled: boolean;
@Prop({ mutable: true }) animated: boolean;
@Prop() delegate: FrameworkDelegate;
@Prop() rootParams: ComponentProps;
@Prop() root: NavComponent;
@Prop() delegate: FrameworkDelegate|undefined;
@Prop() rootParams: ComponentProps|undefined;
@Prop() root: NavComponent|undefined;
@Watch('root')
rootChanged() {
if (this.root) {
@ -74,24 +73,20 @@ export class Nav implements NavOutlet {
}
componentDidUnload() {
const views = this._views;
let view: ViewController;
for (let i = 0; i < views.length; i++) {
view = views[i];
for (const view of this.views) {
lifecycle(view.element, ViewLifecycle.WillUnload);
view._destroy();
}
// release swipe back gesture and transition
this._sbTrns && this._sbTrns.destroy();
this._queue = this._views = this._sbTrns = null;
this._destroyed = true;
this.sbTrns && this.sbTrns.destroy();
this.queue = this.views = this.sbTrns = null;
this.destroyed = true;
}
@Method()
push(component: NavComponent, componentProps?: ComponentProps, opts?: NavOptions, done?: TransitionDoneFn): Promise<boolean> {
return this._queueTrns({
return this.queueTrns({
insertStart: -1,
insertViews: [{ page: component, params: componentProps }],
opts: opts,
@ -100,7 +95,7 @@ export class Nav implements NavOutlet {
@Method()
insert(insertIndex: number, component: NavComponent, componentProps?: ComponentProps, opts?: NavOptions, done?: TransitionDoneFn): Promise<boolean> {
return this._queueTrns({
return this.queueTrns({
insertStart: insertIndex,
insertViews: [{ page: component, params: componentProps }],
opts: opts,
@ -109,7 +104,7 @@ export class Nav implements NavOutlet {
@Method()
insertPages(insertIndex: number, insertComponents: NavComponent[], opts?: NavOptions, done?: TransitionDoneFn): Promise<boolean> {
return this._queueTrns({
return this.queueTrns({
insertStart: insertIndex,
insertViews: insertComponents,
opts: opts,
@ -118,7 +113,7 @@ export class Nav implements NavOutlet {
@Method()
pop(opts?: NavOptions, done?: TransitionDoneFn): Promise<boolean> {
return this._queueTrns({
return this.queueTrns({
removeStart: -1,
removeCount: 1,
opts: opts,
@ -132,52 +127,33 @@ export class Nav implements NavOutlet {
removeCount: -1,
opts: opts
};
if (isViewController(indexOrViewCtrl)) {
if (indexOrViewCtrl instanceof ViewController) {
config.removeView = indexOrViewCtrl;
config.removeStart = 1;
} else if (typeof indexOrViewCtrl === 'number') {
config.removeStart = indexOrViewCtrl + 1;
}
return this._queueTrns(config, done);
return this.queueTrns(config, done);
}
@Method()
popToRoot(opts?: NavOptions, done?: TransitionDoneFn): Promise<boolean> {
return this._queueTrns({
return this.queueTrns({
removeStart: 1,
removeCount: -1,
opts: opts,
}, done);
}
@Method()
popAll(): Promise<boolean[]> {
const promises: Promise<boolean>[] = [];
for (let i = this._views.length - 1; i >= 0; i--) {
promises.push(this.pop(undefined));
}
return Promise.all(promises);
}
@Method()
removeIndex(startIndex: number, removeCount = 1, opts?: NavOptions, done?: TransitionDoneFn): Promise<boolean> {
return this._queueTrns({
return this.queueTrns({
removeStart: startIndex,
removeCount: removeCount,
opts: opts,
}, done);
}
@Method()
removeView(viewController: ViewController, opts?: NavOptions, done?: TransitionDoneFn): Promise<boolean> {
return this._queueTrns({
removeView: viewController,
removeStart: 0,
removeCount: 1,
opts: opts,
}, done);
}
@Method()
setRoot(component: NavComponent, componentProps?: ComponentProps, opts?: NavOptions, done?: TransitionDoneFn): Promise<boolean> {
return this.setPages([{ page: component, params: componentProps }], opts, done);
@ -192,7 +168,7 @@ export class Nav implements NavOutlet {
if (opts.animate !== true) {
opts.animate = false;
}
return this._queueTrns({
return this.queueTrns({
insertStart: 0,
insertViews: views,
removeStart: 0,
@ -204,11 +180,11 @@ export class Nav implements NavOutlet {
@Method()
setRouteId(id: string, params: any, direction: number): Promise<RouteWrite> {
const active = this.getActive();
if (active && active.matches(id, params)) {
if (matches(active, id, params)) {
return Promise.resolve({changed: false, element: active.element});
}
const viewController = this._views.find(v => v.matches(id, params));
const viewController = this.views.find(v => matches(v, id, params));
let resolve: (result: RouteWrite) => void;
const promise = new Promise<RouteWrite>((r) => resolve = r);
@ -246,7 +222,7 @@ export class Nav implements NavOutlet {
const active = this.getActive();
return active ? {
id: active.element.tagName,
params: active.data,
params: active.params,
element: active.element
} : undefined;
}
@ -258,32 +234,24 @@ export class Nav implements NavOutlet {
@Method()
getActive(): ViewController|undefined {
return this._views[this._views.length - 1];
return this.views[this.views.length - 1];
}
@Method()
getByIndex(index: number): ViewController|undefined {
return this._views[index];
return this.views[index];
}
@Method()
getPrevious(view = this.getActive()): ViewController|undefined {
const views = this._views;
const views = this.views;
const index = views.indexOf(view);
return (index > 0) ? views[index - 1] : undefined;
}
@Method()
getViews(): ViewController[] {
return this._views.slice();
}
indexOf(viewController: ViewController) {
return this._views.indexOf(viewController);
}
length() {
return this._views.length;
return this.views.length;
}
// _queueTrns() adds a navigation stack change to the queue and schedules it to run:
@ -296,7 +264,7 @@ export class Nav implements NavOutlet {
// 7. _transitionStart(): called once the transition actually starts, it initializes the Animation underneath.
// 8. _transitionFinish(): called once the transition finishes
// 9. _cleanup(): syncs the navigation internal state with the DOM. For example it removes the pages from the DOM or hides/show them.
private _queueTrns(ti: TransitionInstruction, done: TransitionDoneFn|undefined): Promise<boolean> {
private queueTrns(ti: TransitionInstruction, done: TransitionDoneFn|undefined): Promise<boolean> {
const promise = new Promise<boolean>((resolve, reject) => {
ti.resolve = resolve;
ti.reject = reject;
@ -309,21 +277,21 @@ export class Nav implements NavOutlet {
}
// Enqueue transition instruction
this._queue.push(ti);
this.queue.push(ti);
// if there isn't a transition already happening
// then this will kick off this transition
this._nextTrns();
this.nextTrns();
return promise;
}
private _success(result: NavResult, ti: TransitionInstruction) {
if (this._queue === null) {
this._fireError('nav controller was destroyed', ti);
private success(result: NavResult, ti: TransitionInstruction) {
if (this.queue === null) {
this.fireError('nav controller was destroyed', ti);
return;
}
this._init = true;
this.init = true;
if (ti.done) {
ti.done(
@ -349,27 +317,27 @@ export class Nav implements NavOutlet {
this.ionNavChanged.emit();
}
private _failed(rejectReason: any, ti: TransitionInstruction) {
if (this._queue === null) {
this._fireError('nav controller was destroyed', ti);
private failed(rejectReason: any, ti: TransitionInstruction) {
if (this.queue === null) {
this.fireError('nav controller was destroyed', ti);
return;
}
this._queue.length = 0;
this._fireError(rejectReason, ti);
this.queue.length = 0;
this.fireError(rejectReason, ti);
}
private _fireError(rejectReason: any, ti: TransitionInstruction) {
private fireError(rejectReason: any, ti: TransitionInstruction) {
if (ti.done) {
ti.done(false, false, rejectReason);
}
if (ti.reject && !this._destroyed) {
if (ti.reject && !this.destroyed) {
ti.reject(rejectReason);
} else {
ti.resolve(false);
}
}
private _nextTrns(): boolean {
private nextTrns(): boolean {
// this is the framework's bread 'n butta function
// only one transition is allowed at any given time
if (this.isTransitioning) {
@ -378,7 +346,7 @@ export class Nav implements NavOutlet {
// there is no transition happening right now
// get the next instruction
const ti = this._queue.shift();
const ti = this.queue.shift();
if (!ti) {
return false;
}
@ -391,10 +359,10 @@ export class Nav implements NavOutlet {
try {
// set that this nav is actively transitioning
this.isTransitioning = true;
this._prepareTI(ti);
this.prepareTI(ti);
const leavingView = this.getActive();
const enteringView = this._getEnteringView(ti, leavingView);
const enteringView = this.getEnteringView(ti, leavingView);
if (!leavingView && !enteringView) {
throw new Error('no views in the stack to be removed');
@ -403,23 +371,22 @@ export class Nav implements NavOutlet {
// Needs transition?
ti.requiresTransition = (ti.enteringRequiresTransition || ti.leavingRequiresTransition) && enteringView !== leavingView;
if (enteringView && enteringView._state === ViewState.New) {
if (enteringView && enteringView.state === ViewState.New) {
await enteringView.init(this.el);
}
this._postViewInit(enteringView, leavingView, ti);
const result = await this._transition(enteringView, leavingView, ti);
this.postViewInit(enteringView, leavingView, ti);
this._success(result, ti);
const result = await this.transition(enteringView, leavingView, ti);
this.success(result, ti);
} catch (rejectReason) {
this._failed(rejectReason, ti);
this.failed(rejectReason, ti);
}
this.isTransitioning = false;
this._nextTrns();
this.nextTrns();
}
private _prepareTI(ti: TransitionInstruction) {
const viewsLength = this._views.length;
private prepareTI(ti: TransitionInstruction) {
const viewsLength = this.views.length;
ti.opts = ti.opts || {};
@ -427,10 +394,10 @@ export class Nav implements NavOutlet {
ti.opts.delegate = this.delegate;
}
if (ti.removeView != null) {
assert(isPresent(ti.removeStart), 'removeView needs removeStart');
assert(isPresent(ti.removeCount), 'removeView needs removeCount');
assert(ti.removeStart != null, 'removeView needs removeStart');
assert(ti.removeCount != null, 'removeView needs removeCount');
const index = this._views.indexOf(ti.removeView);
const index = this.views.indexOf(ti.removeView);
if (index < 0) {
throw new Error('removeView was not found');
}
@ -467,21 +434,20 @@ export class Nav implements NavOutlet {
}
// Check all the inserted view are correct
for (let i = 0; i < viewControllers.length; i++) {
const view = viewControllers[i];
for (const view of viewControllers) {
view.delegate = ti.opts.delegate;
const nav = view.nav;
if (nav && nav !== this) {
throw new Error('inserted view was already inserted');
}
if (view._state === ViewState.Destroyed) {
if (view.state === ViewState.Destroyed) {
throw new Error('inserted view was already destroyed');
}
}
ti.insertViews = viewControllers;
}
private _getEnteringView(ti: TransitionInstruction, leavingView: ViewController): ViewController {
private getEnteringView(ti: TransitionInstruction, leavingView: ViewController): ViewController|undefined {
const insertViews = ti.insertViews;
if (insertViews) {
// grab the very last view of the views to be inserted
@ -490,8 +456,8 @@ export class Nav implements NavOutlet {
}
const removeStart = ti.removeStart;
if (isPresent(removeStart)) {
const views = this._views;
if (removeStart != null) {
const views = this.views;
const removeEnd = removeStart + ti.removeCount;
for (let i = views.length - 1; i >= 0; i--) {
const view = views[i];
@ -500,10 +466,10 @@ export class Nav implements NavOutlet {
}
}
}
return null;
return undefined;
}
private _postViewInit(enteringView: ViewController, leavingView: ViewController, ti: TransitionInstruction) {
private postViewInit(enteringView: ViewController, leavingView: ViewController, ti: TransitionInstruction) {
assert(leavingView || enteringView, 'Both leavingView and enteringView are null');
assert(ti.resolve, 'resolve must be valid');
assert(ti.reject, 'reject must be valid');
@ -515,13 +481,13 @@ export class Nav implements NavOutlet {
let destroyQueue: ViewController[] = undefined;
// there are views to remove
if (isPresent(removeStart)) {
if (removeStart != null) {
assert(removeStart >= 0, 'removeStart can not be negative');
assert(removeCount >= 0, 'removeCount can not be negative');
destroyQueue = [];
for (let i = 0; i < removeCount; i++) {
const view = this._views[i + removeStart];
const view = this.views[i + removeStart];
if (view && view !== enteringView && view !== leavingView) {
destroyQueue.push(view);
}
@ -530,7 +496,7 @@ export class Nav implements NavOutlet {
opts.direction = opts.direction || NavDirection.Back;
}
const finalBalance = this._views.length + (insertViews ? insertViews.length : 0) - (removeCount ? removeCount : 0);
const finalBalance = this.views.length + (insertViews ? insertViews.length : 0) - (removeCount ? removeCount : 0);
assert(finalBalance >= 0, 'final balance can not be negative');
if (finalBalance === 0) {
console.warn(`You can't remove all the pages in the navigation stack. nav.pop() is probably called too many times.`,
@ -542,15 +508,11 @@ export class Nav implements NavOutlet {
// At this point the transition can not be rejected, any throw should be an error
// there are views to insert
if (insertViews) {
// manually set the new view's id if an id was passed in the options
if (isPresent(opts.id)) {
enteringView.id = opts.id;
}
// add the views to the
for (let i = 0; i < insertViews.length; i++) {
const view = insertViews[i];
this._insertViewAt(view, ti.insertStart + i);
let insertIndex = ti.insertStart;
for (const view of insertViews) {
this.insertViewAt(view, insertIndex);
insertIndex++;
}
if (ti.enteringRequiresTransition) {
@ -565,22 +527,20 @@ export class Nav implements NavOutlet {
// batch all of lifecycles together
// let's make sure, callbacks are zoned
if (destroyQueue && destroyQueue.length > 0) {
for (let i = 0; i < destroyQueue.length; i++) {
const view = destroyQueue[i];
for (const view of destroyQueue) {
lifecycle(view.element, ViewLifecycle.WillLeave);
lifecycle(view.element, ViewLifecycle.DidLeave);
lifecycle(view.element, ViewLifecycle.WillUnload);
}
// once all lifecycle events has been delivered, we can safely detroy the views
for (let i = 0; i < destroyQueue.length; i++) {
this._destroyView(destroyQueue[i]);
for (const view of destroyQueue) {
this.destroyView(view);
}
}
}
private async _transition(enteringView: ViewController, leavingView: ViewController, ti: TransitionInstruction): Promise<NavResult> {
private async transition(enteringView: ViewController, leavingView: ViewController, ti: TransitionInstruction): Promise<NavResult> {
if (!ti.requiresTransition) {
// transition is not required, so we are already done!
// they're inserting/removing the views somewhere in the middle or
@ -591,9 +551,9 @@ export class Nav implements NavOutlet {
requiresTransition: false
});
}
if (this._sbTrns) {
this._sbTrns.destroy();
this._sbTrns = null;
if (this.sbTrns) {
this.sbTrns.destroy();
this.sbTrns = null;
}
// we should animate (duration > 0) if the pushed page is not the first one (startup)
@ -602,7 +562,7 @@ export class Nav implements NavOutlet {
const animationBuilder = this.getAnimationBuilder(ti.opts);
const progressAnimation = ti.opts.progressAnimation
? (animation: Animation) => this._sbTrns = animation
? (animation: Animation) => this.sbTrns = animation
: undefined;
const opts = ti.opts;
@ -625,17 +585,14 @@ export class Nav implements NavOutlet {
leavingEl
};
const trns = await transition(animationOpts);
return this._transitionFinish(trns, enteringView, leavingView, ti.opts);
return this.transitionFinish(trns, enteringView, leavingView, ti.opts);
}
private _transitionFinish(transition: Animation|void, enteringView: ViewController, leavingView: ViewController, opts: NavOptions): NavResult {
private transitionFinish(transition: Animation|void, enteringView: ViewController, leavingView: ViewController, opts: NavOptions): NavResult {
const hasCompleted = transition ? transition.hasCompleted : true;
if (hasCompleted) {
this._cleanup(enteringView);
} else {
this._cleanup(leavingView);
}
const cleanupView = hasCompleted ? enteringView : leavingView;
this.cleanup(cleanupView);
// this is the root transition
// it's safe to destroy this transition
@ -651,20 +608,21 @@ export class Nav implements NavOutlet {
}
private getAnimationBuilder(opts: NavOptions) {
if (opts.duration === 0 || opts.animate === false || !this._init || this.animated === false || this._views.length <= 1) {
if (opts.duration === 0 || opts.animate === false || !this.init || this.animated === false || this.views.length <= 1) {
return undefined;
}
const mode = opts.animation || this.config.get('pageTransition', this.mode);
return mode === 'ios' ? iosTransitionAnimation : mdTransitionAnimation;
}
private _insertViewAt(view: ViewController, index: number) {
const existingIndex = this._views.indexOf(view);
private insertViewAt(view: ViewController, index: number) {
const views = this.views;
const existingIndex = views.indexOf(view);
if (existingIndex > -1) {
// this view is already in the stack!!
// move it to its new location
assert(view.nav === this, 'view is not part of the nav');
this._views.splice(index, 0, this._views.splice(existingIndex, 1)[0]);
views.splice(index, 0, views.splice(existingIndex, 1)[0]);
} else {
assert(!view.nav, 'nav is used');
// this is a new view to add to the stack
@ -672,14 +630,14 @@ export class Nav implements NavOutlet {
view.nav = this;
// insert the entering view into the correct index in the stack
this._views.splice(index, 0, view);
views.splice(index, 0, view);
}
}
private _removeView(view: ViewController) {
assert(view._state === ViewState.Attached || view._state === ViewState.Destroyed, 'view state should be loaded or destroyed');
private removeView(view: ViewController) {
assert(view.state === ViewState.Attached || view.state === ViewState.Destroyed, 'view state should be loaded or destroyed');
const views = this._views;
const views = this.views;
const index = views.indexOf(view);
assert(index > -1, 'view must be part of the stack');
if (index >= 0) {
@ -687,21 +645,23 @@ export class Nav implements NavOutlet {
}
}
private _destroyView(view: ViewController) {
private destroyView(view: ViewController) {
view._destroy();
this._removeView(view);
this.removeView(view);
}
/**
* DOM WRITE
*/
private _cleanup(activeView: ViewController) {
private cleanup(activeView: ViewController) {
// ok, cleanup time!! Destroy all of the views that are
// INACTIVE and come after the active view
// only do this if the views exist, though
if (!this._destroyed) {
const activeViewIndex = this._views.indexOf(activeView);
const views = this._views;
if (this.destroyed) {
return;
}
const views = this.views;
const activeViewIndex = views.indexOf(activeView);
for (let i = views.length - 1; i >= 0; i--) {
const view = views[i];
@ -709,7 +669,7 @@ export class Nav implements NavOutlet {
// this view comes after the active view
// let's unload it
lifecycle(view.element, ViewLifecycle.WillUnload);
this._destroyView(view);
this.destroyView(view);
} else if (i < activeViewIndex) {
// this view comes before the active view
@ -718,10 +678,9 @@ export class Nav implements NavOutlet {
}
}
}
}
private swipeBackStart() {
if (this.isTransitioning || this._queue.length > 0) {
if (this.isTransitioning || this.queue.length > 0) {
return;
}
@ -731,7 +690,7 @@ export class Nav implements NavOutlet {
progressAnimation: true
};
this._queueTrns({
this.queueTrns({
removeStart: -1,
removeCount: 1,
opts: opts,
@ -739,22 +698,20 @@ export class Nav implements NavOutlet {
}
private swipeBackProgress(detail: GestureDetail) {
if (this._sbTrns) {
if (this.sbTrns) {
// continue to disable the app while actively dragging
// TODO
// this.app.setEnabled(false, ACTIVE_TRANSITION_DEFAULT);
this.isTransitioning = true;
// set the transition animation's progress
const delta = detail.deltaX;
const stepValue = delta / window.innerWidth;
// set the transition animation's progress
this._sbTrns.progressStep(stepValue);
this.sbTrns.progressStep(stepValue);
}
}
private swipeBackEnd(detail: GestureDetail) {
if (this._sbTrns) {
if (this.sbTrns) {
// the swipe back gesture has ended
const delta = detail.deltaX;
const width = window.innerWidth;
@ -772,11 +729,11 @@ export class Nav implements NavOutlet {
realDur = Math.min(dur, 300);
}
this._sbTrns.progressEnd(shouldComplete, stepValue, realDur);
this.sbTrns.progressEnd(shouldComplete, stepValue, realDur);
}
}
canSwipeBack(): boolean {
private canSwipeBack(): boolean {
return (
this.swipeBackEnabled &&
!this.isTransitioning &&
@ -785,9 +742,9 @@ export class Nav implements NavOutlet {
}
render() {
const dom = [];
if (this.swipeBackEnabled) {
dom.push(<ion-gesture
return [
this.swipeBackEnabled &&
<ion-gesture
canStart={this.canSwipeBack.bind(this)}
onStart={this.swipeBackStart.bind(this)}
onMove={this.swipeBackProgress.bind(this)}
@ -797,12 +754,9 @@ export class Nav implements NavOutlet {
type='pan'
direction='x'
threshold={10}
attachTo='body'/>);
}
if (this.mode === 'ios') {
dom.push(<div class='nav-decor'/>);
}
dom.push(<slot></slot>);
return dom;
attachTo='body'/>,
this.mode === 'ios' && <div class='nav-decor'/>,
<slot></slot>
];
}
}

View File

@ -81,19 +81,16 @@ boolean
#### getRouteId()
#### getViews()
#### insert()
#### insertPages()
#### pop()
#### length()
#### popAll()
#### pop()
#### popTo()
@ -108,9 +105,6 @@ boolean
#### removeIndex()
#### removeView()
#### setPages()

View File

@ -112,7 +112,7 @@ describe('NavController', () => {
);
expect(nav.length()).toEqual(1);
expect(nav.getByIndex(0).component).toEqual(MockView1);
expect(nav.isTransitioning).toEqual(false);
expect(nav['isTransitioning']).toEqual(false);
}, 10000);
@ -131,7 +131,7 @@ describe('NavController', () => {
expect(nav.length()).toEqual(2);
expect(nav.getByIndex(0).component).toEqual(MockView1);
expect(nav.getByIndex(1).component).toEqual(MockView2);
expect(nav.isTransitioning).toEqual(false);
expect(nav['isTransitioning']).toEqual(false);
}, 10000);
@ -175,16 +175,6 @@ describe('NavController', () => {
describe('insert', () => {
it('should not modify the view id', async () => {
const view = mockView(MockView4);
view.id = 'custom_id';
await nav.insert(0, view);
expect(view.id).toEqual('custom_id');
expect(view.id).toEqual('custom_id');
}, 10000);
it('should insert at the begining with no async transition', async () => {
const view4 = mockView(MockView4);
const instance4 = spyOnLifecycles(view4);
@ -366,7 +356,7 @@ describe('NavController', () => {
);
expect(err).toEqual(rejectReason);
expect(nav.length()).toEqual(0);
expect(nav.isTransitioning).toEqual(false);
expect(nav['isTransitioning']).toEqual(false);
done();
});
}, 10000);
@ -403,7 +393,7 @@ describe('NavController', () => {
);
expect(nav.length()).toEqual(1);
expect(nav.getByIndex(0).component).toEqual(MockView1);
expect(nav.isTransitioning).toEqual(false);
expect(nav['isTransitioning']).toEqual(false);
}, 10000);
@ -1003,7 +993,7 @@ describe('NavController', () => {
const view2 = mockView();
mockViews(nav, [view1, view2]);
const result = nav.canSwipeBack();
const result = nav['canSwipeBack']();
expect(result).toEqual(false);
});
@ -1013,7 +1003,7 @@ describe('NavController', () => {
const view2 = mockView();
mockViews(nav, [view1, view2]);
const result = nav.canSwipeBack();
const result = nav['canSwipeBack']();
expect(result).toEqual(true);
});
});
@ -1092,7 +1082,7 @@ function mockView(component ?: any, data ?: any) {
}
function mockViews(nav: Nav, views: ViewController[]) {
nav['_views'] = views;
nav['views'] = views;
views.forEach(v => {
v.nav = nav;
});
@ -1111,7 +1101,7 @@ function mockNavController(): Nav {
? mockElement(enteringView.component) as HTMLElement
: enteringView.element = enteringView.component as HTMLElement;
}
enteringView._state = ViewState.Attached;
enteringView.state = ViewState.Attached;
};
return nav;
}

View File

@ -1,68 +1,61 @@
import { NavOptions, ViewState } from './nav-util';
import { ViewState } from './nav-util';
import { assert } from '../../utils/helpers';
import { FrameworkDelegate, Nav } from '../..';
import { ComponentProps, FrameworkDelegate, Nav } from '../..';
import { attachComponent } from '../../utils/framework-delegate';
/**
* @name ViewController
* @description
* Access various features and information about the current view.
* @usage
* ```ts
* import { Component } from '@angular/core';
* import { ViewController } from 'ionic-angular';
*
* @Component({...})
* export class MyPage{
*
* constructor(public viewCtrl: ViewController) {}
*
* }
* ```
*/
export class ViewController {
private _cntDir: any;
private _leavingOpts: NavOptions;
export class ViewController {
nav: Nav;
_state: ViewState = ViewState.New;
/** @hidden */
id: string;
state: ViewState = ViewState.New;
element: HTMLElement;
delegate: FrameworkDelegate;
constructor(
public component: any,
public data: any
public params: any
) {}
/**
* @hidden
*/
async init(container: HTMLElement) {
this._state = ViewState.Attached;
this.state = ViewState.Attached;
if (!this.element) {
const component = this.component;
this.element = await attachComponent(this.delegate, container, component, ['ion-page', 'hide-page'], this.data);
this.element = await attachComponent(this.delegate, container, component, ['ion-page', 'hide-page'], this.params);
}
}
/**
* @hidden
* DOM WRITE
*/
setLeavingOpts(opts: NavOptions) {
this._leavingOpts = opts;
_destroy() {
assert(this.state !== ViewState.Destroyed, 'view state must be ATTACHED');
const element = this.element;
if (element) {
if (this.delegate) {
this.delegate.removeViewFromDom(element.parentElement, element);
} else {
element.remove();
}
}
this.nav = null;
this.state = ViewState.Destroyed;
}
}
matches(id: string, params: any): boolean {
if (this.component !== id) {
export function matches(view: ViewController|undefined, id: string, params: ComponentProps): boolean {
if (!view) {
return false;
}
if (view.component !== id) {
return false;
}
const currentParams = this.data;
const currentParams = view.params;
const null1 = (currentParams == null);
const null2 = (params == null);
if (null1 !== null2) {
@ -87,35 +80,3 @@ export class ViewController {
}
return true;
}
/**
* @hidden
* DOM WRITE
*/
_destroy() {
assert(this._state !== ViewState.Destroyed, 'view state must be ATTACHED');
const element = this.element;
if (element) {
if (this.delegate) {
this.delegate.removeViewFromDom(element.parentElement, element);
} else {
element.remove();
}
}
this.nav = this._cntDir = this._leavingOpts = null;
this._state = ViewState.Destroyed;
}
/**
* Get the index of the current component in the current navigation stack.
* @returns {number} Returns the index of this page within its `NavController`.
*/
get index(): number {
return (this.nav ? this.nav.indexOf(this) : -1);
}
}
export function isViewController(viewCtrl: any): viewCtrl is ViewController {
return viewCtrl instanceof ViewController;
}