fix(navController): update element caching

Also renamed setViews() to setPages(), and moved the index argument in
insert() to be the first arg.
Closes #641
This commit is contained in:
Adam Bradley
2015-11-25 10:27:55 -06:00
parent 61d3a49a75
commit f4d82f05ea
9 changed files with 127 additions and 79 deletions

View File

@@ -77,7 +77,6 @@ import {extend} from '../../util/util';
'</div>' +
'</action-sheet-wrapper>',
host: {
'[style.zIndex]': '_zIndex',
'role': 'dialog'
},
directives: [NgFor, NgIf, Icon]

View File

@@ -125,7 +125,7 @@ export class NavController extends Ion {
this._loader = loader;
this._viewManager = viewManager;
this._zone = zone;
this.renderer = renderer;
this._renderer = renderer;
this._views = [];
this._trnsTime = 0;
@@ -165,8 +165,7 @@ export class NavController extends Ion {
*/
push(componentType, params = {}, opts = {}, callback) {
if (!componentType) {
console.debug('invalid componentType to push');
return Promise.reject();
return Promise.reject('invalid componentType to push');
}
if (typeof componentType !== 'function') {
@@ -211,10 +210,6 @@ export class NavController extends Ion {
// add the view to the stack
this._add(enteringView);
if (opts.preCleanup !== false) {
this._cleanup(enteringView);
}
if (this.router) {
// notify router of the state change
this.router.stateChange('push', enteringView, params);
@@ -292,23 +287,31 @@ export class NavController extends Ion {
return Promise.resolve();
}
let resolve;
// ensure the entering view is shown
this._renderView(viewCtrl, true);
let resolve = null;
let promise = new Promise(res => { resolve = res; });
opts.direction = opts.direction || 'back';
let leavingView = this.getActive() || new ViewController();
// get the views to auto remove without having to do a transiton for each
// the last view (the currently active one) will do a normal transition out
if (this._views.length > 1) {
let autoRemoveItems = this._views.slice(targetIndex, this._views.length);
let popView;
for (let i = 0; i < autoRemoveItems.length; i++) {
autoRemoveItems[i].shouldDestroy = true;
autoRemoveItems[i].shouldCache = false;
autoRemoveItems[i].willUnload();
popView = autoRemoveItems[i];
popView.shouldDestroy = true;
popView.shouldCache = false;
popView.willUnload();
// only the leaving view should be shown, all others hide
this._renderView(popView, (popView === leavingView));
}
}
let leavingView = this.getPrevious(viewCtrl);
if (this.router) {
this.router.stateChange('pop', viewCtrl);
}
@@ -328,11 +331,11 @@ export class NavController extends Ion {
/**
* Inserts a view into the nav stack at the specified index.
* @param {Component} The name of the component you want to insert into the nav stack
* @param {Index} The index where you want to insert the view
* @param {Component} 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(componentType, index, params = {}, opts = {}) {
insert(index, componentType, params = {}, opts = {}) {
if (!componentType || index < 0) {
return Promise.reject();
}
@@ -373,20 +376,29 @@ export class NavController extends Ion {
return Promise.resolve();
}
/**
* @private
*/
setViews(components, opts = {}) {
console.warn('setViews() deprecated, use setPages() instead');
this.setPages(components, opts);
}
/**
* Set the view stack to reflect the given component classes.
* @param {TODO} components TODO
* @param {TODO} [opts={}] TODO
* @returns {Promise} TODO
*/
setViews(components, opts = {}) {
setPages(components, opts = {}) {
if (!components || !components.length) {
return Promise.resolve();
}
let leavingView = this.getActive() || new ViewController();
// if animate has not been set then default to false
opts.animate = opts.animate || false;
opts.preCleanup = false;
// ensure leaving views are not cached, and should be destroyed
opts.cacheLeavingView = false;
@@ -395,10 +407,17 @@ export class NavController extends Ion {
// the last view (the currently active one) will do a normal transition out
if (this._views.length > 1) {
let autoRemoveItems = this._views.slice(0, this._views.length - 1);
let popView;
for (let i = 0; i < autoRemoveItems.length; i++) {
autoRemoveItems[i].shouldDestroy = true;
autoRemoveItems[i].shouldCache = false;
autoRemoveItems[i].willUnload();
popView = autoRemoveItems[i];
popView.shouldDestroy = true;
popView.shouldCache = false;
popView.willUnload();
if (opts.animate) {
// only the leaving view should be shown, all others hide
this._renderView(popView, (popView === leavingView));
}
}
}
@@ -406,15 +425,14 @@ export class NavController extends Ion {
let componentType = null;
let viewCtrl = null;
// create the ViewControllers that go before the new active ViewController in the stack
// but the previous views won't should render yet
// 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 newBeforeItems = components.slice(0, components.length - 1);
for (let j = 0; j < newBeforeItems.length; j++) {
componentObj = newBeforeItems[j];
if (componentObj) {
// could be an object with a componentType property, or it is a componentType
componentType = componentObj.componentType || componentObj;
@@ -446,7 +464,7 @@ export class NavController extends Ion {
* @returns {Promise} Returns a promise when done
*/
setRoot(componentType, params = {}, opts = {}) {
return this.setViews([{
return this.setPages([{
componentType,
params
}], opts);
@@ -462,6 +480,7 @@ export class NavController extends Ion {
* @returns {any} TODO
*/
_transition(enteringView, leavingView, opts, done) {
console.debug('_transition', enteringView, leavingView, opts);
let self = this;
if (enteringView === leavingView) {
@@ -487,9 +506,8 @@ export class NavController extends Ion {
return done(enteringView);
}
self._setZIndex(enteringView.instance, leavingView.instance, opts.direction);
self._zone.runOutsideAngular(() => {
self._setZIndex(enteringView, leavingView, opts.direction);
enteringView.shouldDestroy = false;
enteringView.shouldCache = false;
@@ -638,7 +656,7 @@ export class NavController extends Ion {
if (this._views.length === 1) {
this._zone.runOutsideAngular(() => {
rafFrames(38, () => {
this.renderer.setElementClass(this.elementRef, 'has-views', true);
this._renderer.setElementClass(this.elementRef, 'has-views', true);
});
});
}
@@ -647,17 +665,42 @@ export class NavController extends Ion {
});
}
_setZIndex(enteringInstance, leavingInstance, direction) {
if (!leavingInstance) {
enteringInstance._zIndex = 10;
_setZIndex(enteringView, leavingView, direction) {
let enteringPageRef = enteringView && enteringView.pageRef();
if (enteringPageRef) {
if (!leavingView || !leavingView.isLoaded()) {
enteringView.zIndex = 10;
} else if (direction === 'back') {
// moving back
enteringInstance._zIndex = leavingInstance._zIndex - 1;
} else if (direction === 'back') {
// moving back
enteringView.zIndex = leavingView.zIndex - 1;
} else {
// moving forward
enteringInstance._zIndex = leavingInstance._zIndex + 1;
} else {
// moving forward
enteringView.zIndex = leavingView.zIndex + 1;
}
if (enteringView.zIndex !== enteringView._zIndex) {
this._renderer.setElementStyle(enteringPageRef, 'z-index', enteringView.zIndex);
enteringView._zIndex = enteringView.zIndex;
}
}
}
_renderView(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
// doing checks to make sure we only make an update to the element when needed
if (shouldShow && viewCtrl._hdnAttr === '' ||
!shouldShow && viewCtrl._hdnAttr !== '') {
viewCtrl._hdnAttr = (shouldShow ? null : '');
this._renderer.setElementAttribute(viewCtrl.pageRef(), 'hidden', viewCtrl._hdnAttr);
let navbarRef = viewCtrl.navbarRef();
if (navbarRef) {
this._renderer.setElementAttribute(navbarRef, 'hidden', viewCtrl._hdnAttr);
}
}
}
@@ -899,9 +942,8 @@ export class NavController extends Ion {
destroys.push(view);
} else {
let isActiveView = (view === activeView);
let isPreviousView = (view === previousView);
view.domCache && view.domCache(isActiveView, isPreviousView);
let shouldShow = (view === activeView) || (view === previousView);
this._renderView(view, shouldShow);
}
}
});

View File

@@ -36,7 +36,7 @@ class MyCmpTest{}
<button ion-item [nav-push]="pushPage" [nav-params]="{id:40}">Push w/ [nav-push] and [nav-params]</button>
<button ion-item [nav-push]="[\'FirstPage\', {id: 22}]">Push w/ [nav-push] array and string view name</button>
<button ion-item nav-push="FirstPage" [nav-params]="{id: 23}">Push w/ nav-push and [nav-params]</button>
<button ion-item (click)="setViews()">setViews() (Go to PrimaryHeaderPage)</button>
<button ion-item (click)="setPages()">setPages() (Go to PrimaryHeaderPage)</button>
<button ion-item (click)="setRoot()">setRoot(PrimaryHeaderPage) (Go to PrimaryHeaderPage)</button>
<button ion-item (click)="nav.pop()">Pop</button>
@@ -63,12 +63,12 @@ class FirstPage {
}
}
setViews() {
setPages() {
let items = [
PrimaryHeaderPage
];
this.nav.setViews(items);
this.nav.setPages(items);
}
setRoot(PrimaryHeaderPage) {
@@ -99,7 +99,7 @@ class FirstPage {
<p><button (click)="pushAnother()">Push to AnotherPage</button></p>
<p><button (click)="pushFirstPage()">Push to FirstPage</button></p>
<p><button class="e2eFrom2To1" nav-pop>Pop with NavPop (Go back to 1st)</button></p>
<p><button (click)="setViews()">setViews() (Go to PrimaryHeaderPage, FirstPage 1st in history)</button></p>
<p><button (click)="setPages()">setPages() (Go to PrimaryHeaderPage, FirstPage 1st in history)</button></p>
</ion-content>
`
})
@@ -112,13 +112,13 @@ class FullPage {
this.params = params;
}
setViews() {
setPages() {
let items = [
FirstPage,
PrimaryHeaderPage
];
this.nav.setViews(items);
this.nav.setPages(items);
}
pushPrimaryHeaderPage() {
@@ -168,7 +168,7 @@ class PrimaryHeaderPage {
}
insert() {
this.nav.insert(FirstPage, 2);
this.nav.insert(2, FirstPage);
}
removeSecond() {

View File

@@ -39,7 +39,7 @@ class SecondPage {
this.nav = nav;
}
insertPage(){
this.nav.insert(InsertPage, 1)
this.nav.insert(1, InsertPage)
}
}

View File

@@ -36,12 +36,16 @@ export function run() {
}
function mockCanGoBackFn() {
return true;
return true;
}
// beforeEach(inject([Compiler], compiler => {
beforeEach(() => {
nav = new NavController(null, null, new Config(), null, null, null, null, null);
nav._renderer = {
setElementAttribute: function(){},
setElementStyle: function(){}
};
});
it('should exist', () => {
@@ -125,12 +129,12 @@ export function run() {
});
});
describe("setViews", () => {
describe("setPages", () => {
it('should return a resolved Promise if components is falsy', done => {
let s = jasmine.createSpy('success');
let f = jasmine.createSpy('fail');
let promise = nav.setViews();
let promise = nav.setPages();
promise.then(s, f).then(() => {
expect(s).toHaveBeenCalled();
@@ -147,7 +151,7 @@ export function run() {
let arr = [FirstPage, SecondPage, ThirdPage];
nav._transition = mockTransitionFn;
nav.setViews(arr);
nav.setPages(arr);
//_views[0] will be transitioned out of
expect(nav._views[1].componentType).toBe(FirstPage);
@@ -161,22 +165,24 @@ export function run() {
it('insert page at the specified index', () => {
nav._views = [{}, {}, {}];
expect(nav._views[2].componentType).toBeUndefined();
nav.insert(FirstPage, 2);
nav.insert(2, FirstPage);
expect(nav._views[2].componentType).toBe(FirstPage);
});
it('push page if index >= _views.length', () => {
nav._views = [{}, {}, {}];
spyOn(nav, 'push').and.callThrough();
nav.insert(FirstPage, 2);
nav.insert(2, FirstPage);
expect(nav.push).not.toHaveBeenCalled();
nav._transition = mockTransitionFn;
nav.insert(FirstPage, 4);
nav.insert(4, FirstPage);
expect(nav._views[4].componentType).toBe(FirstPage);
expect(nav.push).toHaveBeenCalled();
nav.insert(FirstPage, 10);
nav.setTransitioning(false);
nav.insert(10, FirstPage);
expect(nav._views[5].componentType).toBe(FirstPage);
expect(nav.push.calls.count()).toBe(2);
});
@@ -270,23 +276,30 @@ export function run() {
describe("_setZIndex", () => {
it('should set zIndex 10 on first entering view', () => {
let enteringInstance = {};
nav._setZIndex(enteringInstance, null, 'forward');
expect(enteringInstance._zIndex).toEqual(10);
let enteringView = new ViewController();
enteringView.setPageRef({});
nav._setZIndex(enteringView, null, 'forward');
expect(enteringView.zIndex).toEqual(10);
});
it('should set zIndex 1 on second entering view', () => {
let leavingInstance = { _zIndex: 0 };
let enteringInstance = {};
nav._setZIndex(enteringInstance, leavingInstance, 'forward');
expect(enteringInstance._zIndex).toEqual(1);
let leavingView = new ViewController();
leavingView.zIndex = 0;
leavingView._loaded = true;
let enteringView = new ViewController();
enteringView.setPageRef({});
nav._setZIndex(enteringView, leavingView, 'forward');
expect(enteringView.zIndex).toEqual(1);
});
it('should set zIndex 0 on entering view going back', () => {
let leavingInstance = { _zIndex: 1 };
let enteringInstance = {};
nav._setZIndex(enteringInstance, leavingInstance, 'back');
expect(enteringInstance._zIndex).toEqual(0);
let leavingView = new ViewController();
leavingView.zIndex = 1;
leavingView._loaded = true;
let enteringView = new ViewController();
enteringView.setPageRef({});
nav._setZIndex(enteringView, leavingView, 'back');
expect(enteringView.zIndex).toEqual(0);
});
});

View File

@@ -209,8 +209,4 @@ export class ViewController {
this.instance.onPageDidUnload && this.instance.onPageDidUnload();
}
domCache(isActiveView, isPreviousView) {
this.instance._hidden = (!isActiveView && !isPreviousView);
}
}

View File

@@ -287,7 +287,6 @@ const OVERLAY_TYPE = 'popup';
'</div>' +
'</popup-wrapper>',
host: {
'[style.zIndex]': '_zIndex',
'role': 'dialog'
},
directives: [FORM_DIRECTIVES, NgClass, NgIf, NgFor, Button]

View File

@@ -70,7 +70,6 @@ export function Page(config={}) {
config.host = config.host || {};
config.host['[hidden]'] = '_hidden';
config.host['[class.tab-subpage]'] = '_tabSubPage';
config.host['[style.zIndex]'] = '_zIndex';
var annotations = Reflect.getMetadata('annotations', cls) || [];
annotations.push(new Component(config));
Reflect.defineMetadata('annotations', annotations, cls);