diff --git a/ionic/components/app/app.ts b/ionic/components/app/app.ts
index cedb51345b..2c2fdbab73 100644
--- a/ionic/components/app/app.ts
+++ b/ionic/components/app/app.ts
@@ -53,9 +53,13 @@ export class IonicApp {
* it will automatically enable the app again. It's basically a fallback incase
* something goes wrong during a transition and the app wasn't re-enabled correctly.
*/
- setEnabled(isEnabled, fallback=700) {
- this._disTime = (isEnabled ? 0 : Date.now() + fallback);
- this._clickBlock.show(!isEnabled, fallback + 100);
+ setEnabled(isEnabled, duration=700) {
+ this._disTime = (isEnabled ? 0 : Date.now() + duration);
+
+ if (duration > 32 || isEnabled) {
+ // only do a click block if the duration is longer than XXms
+ this._clickBlock.show(!isEnabled, duration + 64);
+ }
}
/**
diff --git a/ionic/components/nav/nav-controller.ts b/ionic/components/nav/nav-controller.ts
index 9aac7a0f07..fa96b1f416 100644
--- a/ionic/components/nav/nav-controller.ts
+++ b/ionic/components/nav/nav-controller.ts
@@ -9,7 +9,7 @@ import {ViewController} from './view-controller';
import {Animation} from '../../animations/animation';
import {SwipeBackGesture} from './swipe-back';
import {isBoolean, array} from '../../util/util';
-import {rafFrames} from '../../util/dom';
+import {raf, rafFrames} from '../../util/dom';
/**
* _For examples on the basic usage of NavController, check out the
@@ -101,7 +101,6 @@ import {rafFrames} from '../../util/dom';
export class NavController extends Ion {
/** @internal */
- static _tranitionScope: WtfScopeFn = wtfCreateScope('ionic.NavController#_transition()');
static _loadPageScope: WtfScopeFn = wtfCreateScope('ionic.NavController#loadPage()');
static _transCompleteScope: WtfScopeFn = wtfCreateScope('ionic.NavController#_transComplete()');
@@ -133,6 +132,7 @@ export class NavController extends Ion {
this._views = [];
this._trnsTime = 0;
+ this._trnsDelay = config.get('pageTransitionDelay');
this._sbTrans = null;
this._sbEnabled = config.get('swipeBackEnabled') || false;
@@ -365,7 +365,7 @@ export class NavController extends Ion {
}
// ensure the entering view is shown
- this._renderView(viewCtrl, true);
+ this._cachePage(viewCtrl, true);
let resolve = null;
let promise = new Promise(res => { resolve = res; });
@@ -385,7 +385,7 @@ export class NavController extends Ion {
popView.willUnload();
// only the leaving view should be shown, all others hide
- this._renderView(popView, (popView === leavingView));
+ this._cachePage(popView, (popView === leavingView));
}
}
@@ -592,7 +592,7 @@ export class NavController extends Ion {
if (opts.animate) {
// only the leaving view should be shown, all others hide
- this._renderView(popView, (popView === leavingView));
+ this._cachePage(popView, (popView === leavingView));
}
}
}
@@ -647,16 +647,11 @@ export class NavController extends Ion {
}
/**
- *
* @private
- * @param {TODO} enteringView TODO
- * @param {TODO} leavingView TODO
- * @param {TODO} opts TODO
- * @param {Function} done TODO
- * @returns {any} TODO
*/
_transition(enteringView, leavingView, opts, done) {
if (enteringView === leavingView) {
+ // if the entering view and leaving view are the same thing don't continue
return done(enteringView);
}
@@ -669,122 +664,245 @@ export class NavController extends Ion {
if (!enteringView) {
// if no entering view then create a bogus one
+ // already consider this bogus one loaded
enteringView = new ViewController()
enteringView.loaded();
}
console.time('_transition ' + (enteringView.componentType && enteringView.componentType.name));
- this._stage(enteringView, opts, () => {
- if (enteringView.shouldDestroy) {
- // already marked as a view that will be destroyed, don't continue
- return done(enteringView);
- }
+ /* Async steps to complete a transition
+ 1. _render: compile the view and render it in the DOM. Load page if it hasn't loaded already. When done call postRender
+ 2. _postRender: Run willEnter/willLeave, then wait a frame (change detection happens), then call beginTransition
+ 3. _beforeTrans: Create the transition's animation, play the animation, wait for it to end
+ 4. _afterTrans: Run didEnter/didLeave, call _transComplete()
+ 5. _transComplete: Cleanup, remove cache views, then call the final callback
+ */
- this._cd.detectChanges();
+ // begin the multiple async process of transitioning to the entering view
+ this._render(enteringView, leavingView, opts, done);
+ }
- this._zone.runOutsideAngular(() => {
- this._setZIndex(enteringView, leavingView, opts.direction);
+ /**
+ * @private
+ */
+ _render(enteringView, leavingView, opts, done) {
+ // compile/load the view into the DOM
- enteringView.shouldDestroy = false;
- enteringView.shouldCache = false;
+ if (enteringView.shouldDestroy) {
+ // about to be destroyed, shouldn't continue
+ done(enteringView);
- this._postRender(enteringView, opts, () => {
+ } else if (enteringView.isLoaded()) {
+ // already compiled this view, do not load again and continue
+ this._postRender(enteringView, leavingView, opts, done);
- if (!opts.preload) {
- enteringView.willEnter();
- leavingView.willLeave();
- }
-
- // 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;
-
- // init the transition animation
- opts.renderDelay = opts.transitionDelay || this.config.get('pageTransitionDelay');
-
- let transAnimation = Animation.createTransition(this._getStagedEntering(),
- this._getStagedLeaving(),
- opts);
- if (opts.animate === false) {
- // force it to not animate the elements, just apply the "to" styles
- transAnimation.clearDuration();
- transAnimation.duration(0);
- }
-
- let duration = transAnimation.duration();
- let enableApp = (duration < 64);
- // block any clicks during the transition and provide a
- // fallback to remove the clickblock if something goes wrong
- this.app.setEnabled(enableApp, duration);
- this.setTransitioning(!enableApp, duration);
-
- if (opts.pageType) {
- transAnimation.before.addClass(opts.pageType);
- }
-
- // start the transition
- transAnimation.play(() => {
- // transition has completed, update each view's state
- enteringView.state = ACTIVE_STATE;
- leavingView.state = CACHED_STATE;
-
- // dispose any views that shouldn't stay around
- transAnimation.dispose();
-
- if (!opts.preload) {
- enteringView.didEnter();
- leavingView.didLeave();
- }
-
- this._zone.run(() => {
- if (this.keyboard.isOpen()) {
- this.keyboard.onClose(() => {
- this._transComplete();
- console.timeEnd('_transition ' + (enteringView.componentType && enteringView.componentType.name));
- done(enteringView);
- }, 32);
-
- } else {
- this._transComplete();
- console.timeEnd('_transition ' + (enteringView.componentType && enteringView.componentType.name));
- done(enteringView);
- }
- });
+ } else {
+ // view has not been compiled/loaded yet
+ // continue once the view has finished compiling
+ // DOM WRITE
+ 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(enteringView, leavingView, opts, done);
});
- });
+ } else {
+ enteringView.loaded();
+ this._postRender(enteringView, leavingView, opts, done);
+ }
+ });
+ }
+ }
+
+ /**
+ * @private
+ */
+ _postRender(enteringView, leavingView, opts, done) {
+ // called after _render has completed and the view is compiled/loaded
+
+ if (enteringView.shouldDestroy) {
+ // view already marked as a view that will be destroyed, don't continue
+ done(enteringView);
+
+ } else if (!opts.preload) {
+ // the enteringView will become the active view, and is not being preloaded
+
+ // call each view's lifecycle events
+ // POSSIBLE DOM READ THEN DOM WRITE
+ enteringView.willEnter();
+ leavingView.willLeave();
+
+ // set the correct zIndex for the entering and leaving views
+ // DOM WRITE
+ this._setZIndex(enteringView, leavingView, opts.direction);
+
+ // lifecycle events may have updated some data
+ // wait one frame and allow the raf to do a change detection
+ // before kicking off the transition and showing the new view
+ raf(() => {
+ this._beforeTrans(enteringView, leavingView, opts, done);
});
+ } else {
+ // this view is being preloaded, don't call lifecycle events
+ // transition does not need to animate
+ opts.animate = false;
+ this._beforeTrans(enteringView, leavingView, opts, done);
+ }
+ }
+
+ /**
+ * @private
+ */
+ _beforeTrans(enteringView, leavingView, opts, done) {
+ // called after one raf from postRender()
+ // create the transitions animation, play the animation
+ // when the transition ends call wait for it to end
+
+ // everything during the transition should runOutsideAngular
+ this._zone.runOutsideAngular(() => {
+
+ // ensure the entering view is not destroyed or cached
+ enteringView.shouldDestroy = false;
+ enteringView.shouldCache = false;
+
+ // 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;
+
+ // init the transition animation
+ opts.renderDelay = opts.transitionDelay || self._trnsDelay;
+
+ let transAnimation = Animation.createTransition(enteringView,
+ leavingView,
+ opts);
+ if (opts.animate === false) {
+ // force it to not animate the elements, just apply the "to" styles
+ transAnimation.clearDuration();
+ transAnimation.duration(0);
+ }
+
+ let duration = transAnimation.duration();
+ let enableApp = (duration < 64);
+ // block any clicks during the transition and provide a
+ // fallback to remove the clickblock if something goes wrong
+ this.app.setEnabled(enableApp, duration);
+ this.setTransitioning(!enableApp, duration);
+
+ if (opts.pageType) {
+ transAnimation.before.addClass(opts.pageType);
+ }
+
+ // start the transition
+ transAnimation.play(() => {
+ // transition animation has ended
+
+ // dispose the animation and it's element references
+ transAnimation.dispose();
+
+ this._afterTrans(enteringView, leavingView, opts, done);
+ });
});
}
/**
* @private
*/
- _stage(viewCtrl, opts, done) {
- if (viewCtrl.isLoaded() || viewCtrl.shouldDestroy) {
- // already compiled this view
- return done();
- }
+ _afterTrans(enteringView, leavingView, opts, done) {
+ // 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;
- // get the pane the NavController wants to use
- // the pane is where all this content will be placed into
- this.loadPage(viewCtrl, null, opts, () => {
- if (viewCtrl.onReady) {
- viewCtrl.onReady(() => {
- viewCtrl.loaded();
- done();
- });
+ // run inside of the zone again
+ this._zone.run(() => {
+
+ if (!opts.preload) {
+ enteringView.didEnter();
+ leavingView.didLeave();
+ }
+
+ if (this.keyboard.isOpen()) {
+ // the keyboard is still open!
+ // no problem, let's just close for them
+ this.keyboard.onClose(() => {
+ // keyboard has finished closing, transition complete
+ this._transComplete();
+ console.timeEnd('_transition ' + (enteringView.componentType && enteringView.componentType.name));
+ done(enteringView);
+ }, 32);
} else {
- viewCtrl.loaded();
- done();
+ // all good, transition complete
+ this._transComplete();
+ console.timeEnd('_transition ' + (enteringView.componentType && enteringView.componentType.name));
+ done(enteringView);
}
});
}
+ /**
+ * @private
+ */
+ _transComplete() {
+ this._views.forEach(view => {
+ if (view) {
+ if (view.shouldDestroy) {
+ view.didUnload();
+
+ } else if (view.state === CACHED_STATE && view.shouldCache) {
+ view.shouldCache = false;
+ }
+ }
+ });
+
+ // allow clicks again, but still set an enable time
+ // meaning nothing with this view controller can happen for XXms
+ this.app.setEnabled(true);
+ this.setTransitioning(false);
+
+ this._sbComplete();
+
+ this._cleanup();
+ }
+
+ /**
+ * @private
+ */
+ _cleanup(activeView) {
+ // the active view, and the previous view, should be rendered in dom and ready to go
+ // all others, like a cached page 2 back, should be display: none and not rendered
+ let destroys = [];
+ activeView = activeView || this.getActive();
+ let previousView = this.getPrevious(activeView);
+
+ this._views.forEach(view => {
+ if (view) {
+ if (view.shouldDestroy) {
+ destroys.push(view);
+
+ } else if (view.isLoaded()) {
+ let shouldShow = (view === activeView) || (view === previousView);
+ this._cachePage(view, shouldShow);
+ }
+ }
+ });
+
+ // all views being destroyed should be removed from the list of views
+ // and completely removed from the dom
+ destroys.forEach(view => {
+ this._remove(view);
+ view.destroy();
+ });
+ }
+
/**
* @private
*/
@@ -852,17 +970,6 @@ export class NavController extends Ion {
});
}
- _postRender(enteringView, opts, done) {
- enteringView.postRender();
-
- if (opts.animate === false) {
- done();
-
- } else {
- rafFrames(2, done);
- }
- }
-
_setZIndex(enteringView, leavingView, direction) {
let enteringPageRef = enteringView && enteringView.pageRef();
if (enteringPageRef) {
@@ -885,7 +992,7 @@ export class NavController extends Ion {
}
}
- _renderView(viewCtrl, shouldShow) {
+ _cachePage(viewCtrl, shouldShow) {
// using hidden element attribute to display:none and not render views
// renderAttr of '' means the hidden attribute will be added
// renderAttr of null means the hidden attribute will be removed
@@ -935,7 +1042,7 @@ export class NavController extends Ion {
enteringView.willEnter();
// wait for the new view to complete setup
- enteringView._stage(enteringView, {}, () => {
+ this._render(enteringView, {}, () => {
this._zone.runOutsideAngular(() => {
// set that the new view pushed on the stack is staged to be entering/leaving
@@ -1023,7 +1130,6 @@ export class NavController extends Ion {
// all done!
this._transComplete();
-
});
});
@@ -1142,7 +1248,7 @@ export class NavController extends Ion {
} else if (view.isLoaded()) {
let shouldShow = (view === activeView) || (view === previousView);
- this._renderView(view, shouldShow);
+ this._cachePage(view, shouldShow);
}
}
});
diff --git a/ionic/components/nav/test/basic/index.ts b/ionic/components/nav/test/basic/index.ts
index 2834f68838..de9ed10bc1 100644
--- a/ionic/components/nav/test/basic/index.ts
+++ b/ionic/components/nav/test/basic/index.ts
@@ -39,6 +39,7 @@ class MyCmpTest{}
+
@@ -86,6 +87,10 @@ class FirstPage {
pushAnother() {
this.nav.push(AnotherPage);
}
+
+ reload() {
+ window.location.reload();
+ }
}
@@ -154,9 +159,15 @@ class FullPage {
})
class PrimaryHeaderPage {
constructor(
- nav: NavController
+ nav: NavController,
+ viewCtrl: ViewController
) {
- this.nav = nav
+ this.nav = nav;
+ this.viewCtrl = viewCtrl;
+ }
+
+ onPageWillEnter() {
+ this.viewCtrl.setBackButtonText('Previous');
}
pushAnother() {
diff --git a/ionic/components/nav/view-controller.ts b/ionic/components/nav/view-controller.ts
index ef15477941..d6f839af97 100644
--- a/ionic/components/nav/view-controller.ts
+++ b/ionic/components/nav/view-controller.ts
@@ -280,15 +280,6 @@ export class ViewController {
}
}
- /**
- * @private
- */
- postRender() {
- // let navbar = this.getNavbar();
- // navbar && navbar.postRender();
- // ctrlFn(this, 'onPagePostRender');
- }
-
/**
* @private
* The view is about to enter and become the active view.
diff --git a/ionic/components/tap-click/tap-click.ts b/ionic/components/tap-click/tap-click.ts
index 92b5e96370..c59b122192 100644
--- a/ionic/components/tap-click/tap-click.ts
+++ b/ionic/components/tap-click/tap-click.ts
@@ -13,37 +13,40 @@ import {RippleActivator} from './ripple';
@Injectable()
export class TapClick {
constructor(app: IonicApp, config: Config, zone: NgZone) {
- this.app = app;
- this.zone = zone;
+ let self = this;
+ self.app = app;
+ self.zone = zone;
- this.lastTouch = 0;
- this.disableClick = 0;
- this.lastActivated = 0;
+ self.lastTouch = 0;
+ self.disableClick = 0;
+ self.lastActivated = 0;
if (config.get('activator') == 'ripple') {
- this.activator = new RippleActivator(app, config, zone);
+ self.activator = new RippleActivator(app, config, zone);
} else if (config.get('activator') == 'highlight') {
- this.activator = new Activator(app, config, zone);
+ self.activator = new Activator(app, config, zone);
}
- this.usePolyfill = (config.get('tapPolyfill') === true);
+ self.usePolyfill = (config.get('tapPolyfill') === true);
zone.runOutsideAngular(() => {
- addListener('click', this.click.bind(this), true);
+ addListener('click', self.click.bind(self), true);
- addListener('touchstart', this.touchStart.bind(this));
- addListener('touchend', this.touchEnd.bind(this));
- addListener('touchcancel', this.pointerCancel.bind(this));
+ if (self.usePolyfill) {
+ addListener('touchstart', self.touchStart.bind(self));
+ addListener('touchend', self.touchEnd.bind(self));
+ addListener('touchcancel', self.pointerCancel.bind(self));
+ }
- addListener('mousedown', this.mouseDown.bind(this), true);
- addListener('mouseup', this.mouseUp.bind(this), true);
+ addListener('mousedown', self.mouseDown.bind(self), true);
+ addListener('mouseup', self.mouseUp.bind(self), true);
});
- this.pointerMove = function(ev) {
- console.log('pointerMove');
- if ( hasPointerMoved(POINTER_MOVE_UNTIL_CANCEL, this.startCoord, pointerCoord(ev)) ) {
- this.pointerCancel(ev);
+
+ self.pointerMove = function(ev) {
+ if ( hasPointerMoved(POINTER_MOVE_UNTIL_CANCEL, self.startCoord, pointerCoord(ev)) ) {
+ self.pointerCancel(ev);
}
};
}
@@ -60,7 +63,7 @@ export class TapClick {
let endCoord = pointerCoord(ev);
if (!hasPointerMoved(POINTER_TOLERANCE, this.startCoord, endCoord)) {
- console.debug('create click from touch');
+ console.debug('create click from touch ' + Date.now());
// prevent native mouse click events for XX amount of time
this.disableClick = this.lastTouch + DISABLE_NATIVE_CLICK_AMOUNT;
@@ -78,7 +81,7 @@ export class TapClick {
mouseDown(ev) {
if (this.isDisabledNativeClick()) {
- console.debug('mouseDown prevent', ev.target.tagName);
+ console.debug('mouseDown prevent ' + ev.target.tagName + ' ' + Date.now());
// does not prevent default on purpose
// so native blur events from inputs can happen
ev.stopPropagation();
@@ -90,7 +93,7 @@ export class TapClick {
mouseUp(ev) {
if (this.isDisabledNativeClick()) {
- console.debug('mouseUp prevent', ev.target.tagName);
+ console.debug('mouseUp prevent ' + ev.target.tagName + ' ' + Date.now());
ev.preventDefault();
ev.stopPropagation();
}
@@ -125,21 +128,21 @@ export class TapClick {
}
pointerCancel(ev) {
- console.debug('pointerCancel from', ev.type);
+ console.debug('pointerCancel from ' + ev.type + ' ' + Date.now());
this.activator && this.activator.clearState();
this.moveListeners(false);
}
moveListeners(shouldAdd) {
removeListener(this.usePolyfill ? 'touchmove' : 'mousemove', this.pointerMove);
-
- this.zone.runOutsideAngular(() => {
+
+ //this.zone.runOutsideAngular(() => {
if (shouldAdd) {
addListener(this.usePolyfill ? 'touchmove' : 'mousemove', this.pointerMove);
} else {
}
- });
+ //});
}
click(ev) {
@@ -153,7 +156,7 @@ export class TapClick {
}
if (preventReason !== null) {
- console.debug('click prevent', preventReason);
+ console.debug('click prevent ' + preventReason + ' ' + Date.now());
ev.preventDefault();
ev.stopPropagation();
}