refactor(): rename ViewController/ViewItem

Rename ViewController to NavController, ViewItem to ViewController
This commit is contained in:
Adam Bradley
2015-09-23 16:57:25 -05:00
parent fb9fb47c37
commit 89c0a0ef09
28 changed files with 930 additions and 1091 deletions

View File

@@ -130,6 +130,20 @@ ion-pane {
order: $flex-order-view-content;
}
ion-view {
display: flex;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
flex-direction: column;
background-color: white;
transform: translateZ(0px);
}
[hidden],
template,
root-anchor {

View File

@@ -1,17 +0,0 @@
/**
* TODO
*/
@Decorator({
selector: 'ion-primary-options'
})
export class ItemPrimaryOptions {
}
/**
* TODO
*/
@Decorator({
selector: 'ion-secondary-options'
})
export class ItemSecondaryOptions {
}

View File

@@ -1,90 +0,0 @@
import {ElementRef, Host, Directive} from 'angular2/angular2';
import {Item} from 'ionic/components/item/item';
import {SlideGesture} from 'ionic/gestures/slide-gesture';
/**
* @name ionPrimarySwipeButtons
* @description
* Creates a swipeable button inside a list item, that is visible when the item is swiped to the left by the user. Swiped open buttons can be hidden with `setOpen(false)`.
*
* @usage
* TODO
*/
@Directive({
selector: 'ion-primary-swipe-buttons'
})
export class ItemPrimarySwipeButtons {
/**
* @param {ElementRef} elementRef A reference to the component's DOM element.
* @param {Item} item The list item containing the swipeable buttons.
*/
constructor(
elementRef: ElementRef,
@Host() item: Item
) {
item.primarySwipeButtons = this;
this.ele = elementRef.nativeElement;
this.item = item;
this.gesture = new ItemSlideGesture(this);
this.gesture.listen();
}
/**
* @param {boolean} isOpen Whether or not the button should be set to open/visible.
*/
setOpen(isOpen) {
if (isOpen !== this.isOpen) {
this.isOpen = isOpen;
requestAnimationFrame(() => {
this.ele.classList[isOpen?'add':'remove'](isOpen);
});
}
}
}
/**
* TODO
*/
@Directive({
selector: 'ion-secondary-swipe-buttons'
})
export class ItemSecondarySwipeButtons {
}
class ItemSlideGesture extends SlideGesture {
constructor(buttons) {
super(buttons.item.ele)
this.buttons = buttons
}
getSlideBoundaries() {
return {
min: -this.buttons.ele.offsetWidth,
max: 0,
};
}
getElementStartPos(slide, ev) {
return this.buttons.isOpen ? slide.max : slide.min;
}
onSlideBeforeStart() {
this.buttons.ele.classList.add('changing')
this.buttons.ele.classList.add('no-transition')
return new Promise(resolve => {
requestAnimationFrame(resolve)
})
}
onSlide(slide, ev) {
this.buttons.ele.style.transform = 'translate3d(' + slide.distance + 'px,0,0)';
}
onSlideEnd(slide, ev) {
this.buttons.ele.style.transform = ''
this.buttons.ele.classList.remove('no-transition')
if (Math.abs(ev.velocityX) > 0.2 || Math.abs(slide.delta) > Math.abs(slide.max) * 0.5) {
this.buttons.setOpen(!this.buttons.isOpen);
}
}
}

View File

@@ -1,9 +1,8 @@
import {Component, Directive, View, ElementRef, NgIf, ViewQuery, QueryList} from 'angular2/angular2';
import {ItemPrimaryOptions, ItemSecondaryOptions} from './item-options';
import {ItemPrimarySwipeButtons, ItemSecondarySwipeButtons} from './item-swipe-buttons';
import {dom} from 'ionic/util';
/**
* @name ionItem
* @description

View File

@@ -2,7 +2,7 @@ import {Directive, ElementRef, Optional} from 'angular2/angular2';
import {Ion} from '../ion';
import {IonicApp} from '../app/app';
import {ViewItem} from '../view/view-item';
import {ViewController} from '../nav/view-controller';
import {Navbar} from '../nav-bar/nav-bar';
@@ -24,12 +24,12 @@ export class MenuToggle extends Ion {
constructor(
app: IonicApp,
elementRef: ElementRef,
@Optional() item: ViewItem,
@Optional() viewCtrl: ViewController,
@Optional() navbar: Navbar
) {
super(elementRef, null);
this.app = app;
this.item = item;
this.viewCtrl = viewCtrl;
this.withinNavbar = !!navbar;
}
@@ -45,8 +45,8 @@ export class MenuToggle extends Ion {
}
get isHidden() {
if (this.withinNavbar && this.item) {
return !this.item.isRoot();
if (this.withinNavbar && this.viewCtrl) {
return !this.viewCtrl.isRoot();
}
return false;
}

View File

@@ -5,8 +5,8 @@ import {ToolbarBase} from '../toolbar/toolbar';
import {IonicConfig} from '../../config/config';
import {IonicView} from '../../config/decorators';
import {IonicApp} from '../app/app';
import {ViewItem} from '../view/view-item';
import {ViewController} from '../view/view-controller';
import {ViewController} from '../nav/view-controller';
import {NavController} from '../nav/nav-controller';
@Directive({
@@ -17,19 +17,19 @@ import {ViewController} from '../view/view-controller';
})
class BackButton extends Ion {
constructor(
@Optional() viewCtrl: ViewController,
@Optional() navCtrl: NavController,
elementRef: ElementRef,
@Optional() @Inject(forwardRef(() => Navbar)) navbar: Navbar
) {
super(elementRef, null);
this.viewCtrl = viewCtrl;
this.navCtrl = navCtrl;
navbar && navbar.setBackButtonRef(elementRef);
}
goBack(ev) {
ev.stopPropagation();
ev.preventDefault();
this.viewCtrl && this.viewCtrl.pop();
this.navCtrl && this.navCtrl.pop();
}
}
@@ -73,14 +73,14 @@ class BackButtonText extends Ion {
export class Navbar extends ToolbarBase {
constructor(
app: IonicApp,
@Optional() item: ViewItem,
@Optional() viewCtrl: ViewController,
elementRef: ElementRef,
config: IonicConfig
) {
super(elementRef, config);
this.app = app;
item && item.navbarView(this);
viewCtrl && viewCtrl.navbarView(this);
this.bbIcon = config.setting('backButtonIcon');
this.bbDefault = config.setting('backButtonText');
@@ -119,9 +119,9 @@ export class Navbar extends ToolbarBase {
})
export class NavbarTemplate {
constructor(
@Optional() item: ViewItem,
@Optional() viewCtrl: ViewController,
@Optional() templateRef: TemplateRef
) {
item && item.addTemplateRef('navbar', templateRef);
viewCtrl && viewCtrl.addTemplateRef('navbar', templateRef);
}
}

View File

@@ -2,7 +2,7 @@ import {Component, View, Directive, Host, ElementRef, forwardRef, Inject} from '
import {ViewContainerRef} from 'angular2/src/core/compiler/view_container_ref';
import {Pane} from './pane';
import {ViewController} from '../view/view-controller';
import {NavController} from './nav-controller';
@Directive({selector: 'template[pane-anchor]'})
@@ -32,10 +32,10 @@ export class PaneContentAnchor {
})
class NavBarAnchor {
constructor(
@Inject(forwardRef(() => ViewController)) viewCtrl: ViewController,
@Inject(forwardRef(() => NavController)) navCtrl: NavController,
viewContainerRef: ViewContainerRef
) {
viewCtrl.navbarViewContainer(viewContainerRef);
navCtrl.navbarViewContainer(viewContainerRef);
}
}

View File

@@ -1,51 +1,783 @@
import {extend} from '../../util/util';
import {Compiler, ElementRef, Injector, bind, NgZone} from 'angular2/angular2';
import {DynamicComponentLoader} from 'angular2/src/core/compiler/dynamic_component_loader';
import {AppViewManager} from 'angular2/src/core/compiler/view_manager';
import {Ion} from '../ion';
import {IonicConfig} from '../../config/config';
import {IonicApp} from '../app/app';
import {ViewController} from './view-controller';
import {PaneController} from './pane';
import {Transition} from '../../transitions/transition';
import {SwipeBackGesture} from './swipe-back';
import * as util from 'ionic/util';
/**
* TODO
*/
export class NavController {
export class NavController extends Ion {
constructor(
parentnavCtrl: NavController,
injector: Injector,
elementRef: ElementRef,
zone: NgZone
) {
let config = injector.get(IonicConfig);
super(elementRef, config);
this.parent = parentnavCtrl;
this.compiler = injector.get(Compiler);
this.loader = injector.get(DynamicComponentLoader);
this.viewMngr = injector.get(AppViewManager);
this.app = injector.get(IonicApp);
this.config = config;
this.zone = zone;
this.views = [];
this.panes = new PaneController(this);
this._sbTrans = null;
this.sbEnabled = config.setting('swipeBackEnabled') || false;
this.sbThreshold = config.setting('swipeBackThreshold') || 40
this.id = ++ctrlIds;
this._ids = -1;
this.zIndexes = -1;
// build a new injector for child ViewControllers to use
this.bindings = Injector.resolve([
bind(NavController).toValue(this)
]);
}
/**
* TODO
* @param {TODO} nav TODO
* @param {TODO} componentType TODO
* @param {TODO} [params={}] TODO
* @param {TODO} [opts={}] TODO
* @returns {Promise} TODO
*/
constructor(nav) {
this._nav = nav;
push(componentType, params = {}, opts = {}) {
if (!componentType) {
return Promise.reject();
}
if (typeof componentType !== 'function') {
throw 'Loading component must be a component class, not "' + componentType.toString() + '"';
}
let resolve;
let promise = new Promise(res => { resolve = res; });
// do not animate if this is the first in the stack
if (!this.views.length) {
opts.animation = 'none';
}
// default the direction to "forward"
opts.direction = opts.direction || 'forward';
// the active view is going to be the leaving one (if one exists)
let leavingView = this.getActive() || new ViewController();
leavingView.shouldCache = (util.isBoolean(opts.cacheleavingView) ? opts.cacheleavingView : true);
leavingView.shouldDestroy = !leavingView.shouldCache;
if (leavingView.shouldDestroy) {
leavingView.willUnload();
}
// create a new ViewController
let enteringView = new ViewController(this, componentType, params);
// add the view to the stack
this.add(enteringView);
if (this.router) {
// notify router of the state change
this.router.stateChange('push', enteringView, params);
}
// start the transition
this.transition(enteringView, leavingView, opts, () => {
resolve();
});
return promise;
}
/**
* Set the history stack to match the list of component items.
* @param {TODO} items TODO
* @return {TODO} TODO
* TODO
* @param {TODO} [opts={}] TODO
* @returns {Promise} TODO
*/
setItems(items) {
return this._nav.setItems(items);
pop(opts = {}) {
if (!this.canGoBack()) {
return Promise.reject();
}
let resolve;
let promise = new Promise(res => { resolve = res; });
// default the direction to "back"
opts.direction = opts.direction || 'back';
// get the active view and set that it is staged to be leaving
// was probably the one popped from the stack
let leavingView = this.getActive() || new ViewController();
leavingView.shouldCache = (util.isBoolean(opts.cacheleavingView) ? opts.cacheleavingView : false);
leavingView.shouldDestroy = !leavingView.shouldCache;
if (leavingView.shouldDestroy) {
leavingView.willUnload();
}
// the entering view is now the new last view
// Note: we might not have an entering view if this is the
// only view on the history stack.
let enteringView = this.getPrevious(leavingView);
if (enteringView) {
if (this.router) {
// notify router of the state change
this.router.stateChange('pop', enteringView);
}
// start the transition
this.transition(enteringView, leavingView, opts, () => {
// transition completed, destroy the leaving view
resolve();
});
} else {
this._transComplete();
resolve();
}
return promise;
}
/**
* Clear the history stack.
* @return {TODO} TODO
* Set the view stack to reflect the given component classes.
* @param {TODO} components TODO
* @param {TODO} [opts={}] TODO
* @returns {Promise} TODO
*/
clear() {
return this._nav.clear();
setViews(components, opts = {}) {
if (!components || !components.length) {
return Promise.resolve();
}
// if animate has not been set then default to false
opts.animate = opts.animate || false;
// ensure leaving views are not cached, and should be destroyed
opts.cacheleavingView = false;
// 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(0, this.views.length - 1);
for (let i = 0; i < autoRemoveItems.length; i++) {
autoRemoveItems[i].shouldDestroy = true;
autoRemoveItems[i].shouldCache = false;
autoRemoveItems[i].willUnload();
}
}
let componentObj = null;
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
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;
viewCtrl = new ViewController(this, componentType, componentObj.params);
viewCtrl.state = CACHED_STATE;
viewCtrl.shouldDestroy = false;
viewCtrl.shouldCache = false;
// add the item to the stack
this.add(viewCtrl);
}
}
}
// get the component that will become the active item
// it'll be the last one in the given components array
componentObj = components[ components.length - 1 ];
componentType = componentObj.componentType || componentObj;
// transition the leaving and entering
return this.push(componentType, componentObj.params, opts);
}
/**
* Push a new component onto the history stack.
* @return {TODO} TODO
* TODO
* @param {TODO} componentType TODO
* @param {TODO} [params={}] TODO
* @param {TODO} [opts={}] TODO
* @returns {Promise} TODO
*/
push() {
return this._nav.push.apply(this._nav, arguments);
setRoot(componentType, params = {}, opts = {}) {
return this.setViews([{
componentType,
params
}], opts);
}
/**
* Pop the top most (visible) component off the history stack.
* @return {TODO} TODO
* TODO
* @param {TODO} enteringView TODO
* @param {TODO} leavingView TODO
* @param {TODO} opts TODO
* @param {Function} callback TODO
* @returns {any} TODO
*/
pop() {
return this._nav.pop.apply(this._nav, arguments);
transition(enteringView, leavingView, opts, callback) {
if (!enteringView || enteringView === leavingView) {
return callback();
}
if (opts.animate === false) {
opts.animation = 'none';
} else if (!opts.animation) {
opts.animation = this.config.setting('viewTransition');
}
opts.animate = (opts.animation !== 'none');
// wait for the new view to complete setup
enteringView.stage(() => {
this.zone.runOutsideAngular(() => {
enteringView.shouldDestroy = false;
enteringView.shouldCache = false;
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
let transAnimation = Transition.create(this, opts);
if (!opts.animate) {
// force it to not animate the elements, just apply the "to" styles
transAnimation.duration(0);
}
let duration = transAnimation.duration();
if (duration > 64) {
// block any clicks during the transition and provide a
// fallback to remove the clickblock if something goes wrong
this.app.setEnabled(false, duration);
}
// start the transition
transAnimation.play().then(() => {
// 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();
enteringView.didEnter();
leavingView.didLeave();
// all done!
this.zone.run(() => {
this._transComplete();
callback();
});
});
});
});
}
/**
* TODO
*/
swipeBackStart() {
if (!this.app.isEnabled() || !this.canSwipeBack()) {
return;
}
// disables the app during the transition
this.app.setEnabled(false);
// default the direction to "back"
let opts = {
direction: 'back'
};
// get the active view and set that it is staged to be leaving
// was probably the one popped from the stack
let leavingView = this.getActive() || new ViewController();
leavingView.shouldDestroy = true;
leavingView.shouldCache = false;
leavingView.willLeave();
leavingView.willUnload();
// the entering view is now the new last view
let enteringView = this.getPrevious(leavingView);
enteringView.shouldDestroy = false;
enteringView.shouldCache = false;
enteringView.willEnter();
// wait for the new view to complete setup
enteringView.stage(() => {
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;
// init the swipe back transition animation
this._sbTrans = Transition.create(this, opts);
this._sbTrans.easing('linear').progressStart();
});
});
}
/**
* TODO
* @param {TODO} progress TODO
*/
swipeBackProgress(value) {
if (this._sbTrans) {
// continue to disable the app while actively dragging
this.app.setEnabled(false, 4000);
// set the transition animation's progress
this._sbTrans.progress(value);
}
}
/**
* @private
* @param {TODO} completeSwipeBack Should the swipe back complete or not.
* @param {number} rate How fast it closes
*/
swipeBackEnd(completeSwipeBack, rate) {
if (!this._sbTrans) return;
// disables the app during the transition
this.app.setEnabled(false);
this._sbTrans.progressEnd(completeSwipeBack, rate).then(() => {
this.zone.run(() => {
// find the views that were entering and leaving
let enteringView = this.getStagedenteringView();
let leavingView = this.getStagedleavingView();
if (enteringView && leavingView) {
// finish up the animation
if (completeSwipeBack) {
// swipe back has completed navigating back
// update each view's state
enteringView.state = ACTIVE_STATE;
leavingView.state = CACHED_STATE;
enteringView.didEnter();
leavingView.didLeave();
if (this.router) {
// notify router of the pop state change
this.router.stateChange('pop', enteringView);
}
} 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.willEnter();
leavingView.didEnter();
enteringView.didLeave();
leavingView.shouldDestroy = false;
enteringView.shouldDestroy = false;
}
}
// empty out and dispose the swipe back transition animation
this._sbTrans && this._sbTrans.dispose();
this._sbTrans = null;
// all done!
this._transComplete();
});
});
}
_runSwipeBack() {
if (this.canSwipeBack()) {
// it is possible to swipe back
if (this.sbGesture) {
// this is already an active gesture, don't create another one
return;
}
let opts = {
edge: 'left',
threshold: this.sbThreshold
};
this.sbGesture = new SwipeBackGesture(this.getNativeElement(), opts, this);
console.debug('SwipeBackGesture listen');
this.sbGesture.listen();
} else if (this.sbGesture) {
// it is not possible to swipe back and there is an
// active sbGesture, so unlisten it
console.debug('SwipeBackGesture unlisten');
this.sbGesture.unlisten();
this.sbGesture = null;
}
}
/**
* TODO
* @param {TODO} val TODO
* @returns {TODO} TODO
*/
isSwipeBackEnabled(val) {
if (arguments.length) {
this.sbEnabled = !!val;
}
return this.sbEnabled;
}
/**
* If it's possible to use swipe back or not. If it's not possible
* to go back, or swipe back is not enable then this will return false.
* If it is possible to go back, and swipe back is enabled, then this
* will return true.
* @returns {boolean}
*/
canSwipeBack() {
return (this.sbEnabled && this.canGoBack());
}
/**
* Returns `true` if there's a valid previous view that we can pop back to.
* Otherwise returns false.
* @returns {boolean}
*/
canGoBack() {
let activeView = this.getActive();
if (activeView) {
return activeView.enableBack();
}
return false;
}
/**
* @private
*/
_transComplete() {
let destroys = [];
this.views.forEach(view => {
if (view) {
if (view.shouldDestroy) {
destroys.push(view);
} else if (view.state === CACHED_STATE && view.shouldCache) {
view.shouldCache = false;
}
}
});
destroys.forEach(view => {
this.remove(view);
view.destroy();
});
// allow clicks again, but still set an enable time
// meaning nothing with this view controller can happen for XXms
this.app.setEnabled(true);
if (this.views.length === 1) {
this.elementRef.nativeElement.classList.add('has-views');
}
this._runSwipeBack();
}
/**
* TODO
* @returns {TODO} TODO
*/
getActive() {
for (let i = 0, ii = this.views.length; i < ii; i++) {
if (this.views[i].state === ACTIVE_STATE) {
return this.views[i];
}
}
return null;
}
/**
* TODO
* @param {TODO} instance TODO
* @returns {TODO} TODO
*/
getByInstance(instance) {
if (instance) {
for (let i = 0, ii = this.views.length; i < ii; i++) {
if (this.views[i].instance === instance) {
return this.views[i];
}
}
}
return null;
}
/**
* TODO
* @param {TODO} index TODO
* @returns {TODO} TODO
*/
getByIndex(index) {
if (index < this.views.length && index > -1) {
return this.views[index];
}
return null;
}
/**
* TODO
* @param {TODO} view TODO
* @returns {TODO} TODO
*/
getPrevious(view) {
if (view) {
return this.views[ this.views.indexOf(view) - 1 ];
}
return null;
}
/**
* TODO
* @returns {TODO} TODO
*/
getStagedenteringView() {
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;
}
/**
* TODO
* @returns {TODO} TODO
*/
getStagedleavingView() {
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;
}
/**
* TODO
* @param {TODO} nbContainer TODO
* @returns {TODO} TODO
*/
navbarViewContainer(nbContainer) {
if (nbContainer) {
this._nbContainer = nbContainer;
}
if (this._nbContainer) {
return this._nbContainer;
}
if (this.parent) {
return this.parent.navbarViewContainer();
}
}
/**
* TODO
* @returns {TODO} TODO
*/
anchorElementRef() {
if (arguments.length) {
this._anchorER = arguments[0];
}
return this._anchorER;
}
/**
* TODO
* @returns {TODO} TODO
*/
anchorViewContainerRef() {
if (arguments.length) {
this._anchorVC = arguments[0];
}
return this._anchorVC;
}
/**
* TODO
* @returns {TODO} TODO
*/
childNavbar() {
if (arguments.length) {
this._childNavbar = arguments[0];
}
return this._childNavbar;
}
/**
* TODO
* @param {TODO} view TODO
* @returns {TODO} TODO
*/
add(view) {
view.id = this.id + '-' + (++this._ids);
this.views.push(view);
}
/**
* TODO
* @param {TODO} viewOrIndex TODO
* @returns {TODO} TODO
*/
remove(viewOrIndex) {
util.array.remove(this.views, viewOrIndex);
}
/**
* First view in this nav controller's stack. This would
* not return an view which is about to be destroyed.
* @returns {TODO} TODO
*/
first() {
for (let i = 0, l = this.views.length; i < l; i++) {
if (!this.views[i].shouldDestroy) {
return this.views[i];
}
}
return null;
}
/**
* Last view in this nav controller's stack. This would
* not return an view which is about to be destroyed.
* @returns {TODO} TODO
*/
last() {
for (let i = this.views.length - 1; i >= 0; i--) {
if (!this.views[i].shouldDestroy) {
return this.views[i];
}
}
return null;
}
/**
* TODO
* @param {TODO} view TODO
* @returns {TODO} TODO
*/
indexOf(view) {
return this.views.indexOf(view);
}
/**
* Number of sibling views in the nav controller. This does
* not include views which are about to be destroyed.
* @returns {TODO} TODO
*/
length() {
let len = 0;
for (let i = 0, l = this.views.length; i < l; i++) {
if (!this.views[i].shouldDestroy) {
len++;
}
}
return len;
}
/**
* TODO
* @returns {TODO} TODO
*/
instances() {
let instances = [];
for (let view of this.views) {
if (view.instance) {
instances.push(view.instance);
}
}
return instances;
}
/**
* TODO
* @param {TODO} view TODO
* @returns {TODO} TODO
*/
isActive(view) {
return (view && view.state === ACTIVE_STATE);
}
/**
* TODO
* @param {TODO} view TODO
* @returns {TODO} TODO
*/
isStagedEntering(view) {
return (view && view.state === STAGED_ENTERING_STATE);
}
/**
* TODO
* @param {TODO} router TODO
*/
registerRouter(router) {
this.router = router;
}
}
const ACTIVE_STATE = 1;
const CACHED_STATE = 2;
const STAGED_ENTERING_STATE = 3;
const STAGED_LEAVING_STATE = 4;
let ctrlIds = -1;
/**
* TODO
*/

View File

@@ -29,12 +29,12 @@ export class NavRouter extends RouterOutlet {
nav: Nav) {
super(_elementRef, _loader, _parentRouter, nameAttr);
// Nav is Ionic's ViewController, which we injected into this class
// Nav is Ionic's NavController, which we injected into this class
this.nav = nav;
// register this router with Ionic's ViewController
// Ionic's ViewController will call this NavRouter's "stateChange"
// method when the ViewController has...changed its state
// register this router with Ionic's NavController
// Ionic's NavController will call this NavRouter's "stateChange"
// method when the NavController has...changed its state
nav.registerRouter(this);
}
@@ -55,7 +55,7 @@ export class NavRouter extends RouterOutlet {
return Promise.resolve();
}
// tell the ViewController which componentType, and it's params, to navigate to
// tell the NavController which componentType, and it's params, to navigate to
return this.nav.push(componentType, nextInstruction.params);
}
@@ -66,23 +66,23 @@ export class NavRouter extends RouterOutlet {
/**
* TODO
* @param {TODO} type TODO
* @param {TODO} viewItem TODO
* @param {TODO} viewCtrl TODO
*/
stateChange(type, viewItem) {
// stateChange is called by Ionic's ViewController
stateChange(type, viewCtrl) {
// stateChange is called by Ionic's NavController
// type could be "push" or "pop"
// viewItem is Ionic's ViewItem class, which has the properties "componentType" and "params"
// viewCtrl is Ionic's ViewController class, which has the properties "componentType" and "params"
// only do an update if there's an actual view change
if (!viewItem || this._activeViewId === viewItem.id) return;
this._activeViewId = viewItem.id;
if (!viewCtrl || this._activeViewId === viewCtrl.id) return;
this._activeViewId = viewCtrl.id;
// get the best PathRecognizer for this view's componentType
let pathRecognizer = this.getPathRecognizerByComponent(viewItem.componentType);
let pathRecognizer = this.getPathRecognizerByComponent(viewCtrl.componentType);
if (pathRecognizer) {
// generate a componentInstruction from the view's PathRecognizer and params
let componentInstruction = pathRecognizer.generate(viewItem.params.data);
let componentInstruction = pathRecognizer.generate(viewCtrl.params.data);
// create an Instruction from the componentInstruction
let instruction = new Instruction(componentInstruction, null);

View File

@@ -1,7 +1,7 @@
import {Directive, View, ElementRef, Host, Optional, forwardRef, Injector, NgZone} from 'angular2/angular2';
import {IonicComponent} from '../../config/decorators';
import {ViewController} from '../view/view-controller';
import {NavController} from './nav-controller';
/**
* TODO
@@ -19,22 +19,22 @@ import {ViewController} from '../view/view-controller';
template: '<template pane-anchor></template>',
directives: [forwardRef(() => NavPaneAnchor)]
})
export class Nav extends ViewController {
export class Nav extends NavController {
/**
* TODO
* @param {ViewController} hostViewCtrl TODO
* @param {NavController} hostnavCtrl TODO
* @param {Injector} injector TODO
* @param {ElementRef} elementRef TODO
* @param {NgZone} zone TODO
*/
constructor(
@Optional() hostViewCtrl: ViewController,
@Optional() hostnavCtrl: NavController,
injector: Injector,
elementRef: ElementRef,
zone: NgZone
) {
super(hostViewCtrl, injector, elementRef, zone);
super(hostnavCtrl, injector, elementRef, zone);
}
/**

View File

@@ -2,7 +2,7 @@ import {Component, Directive, View, ElementRef, Inject, forwardRef, Injector, bi
import {Ion} from '../ion';
import {IonicConfig} from '../../config/config';
import {ViewController} from '../view/view-controller';
import {NavController} from './nav-controller';
import {IonicComponent} from '../../config/decorators';
import {PaneAnchor, PaneContentAnchor, NavBarContainer} from './anchors';
@@ -12,14 +12,14 @@ import {PaneAnchor, PaneContentAnchor, NavBarContainer} from './anchors';
export class PaneController {
/**
* TODO
* @param {ViewController} viewCtrl TODO
* @param {NavController} navCtrl TODO
*/
constructor(viewCtrl: ViewController) {
constructor(navCtrl: NavController) {
this.panes = [];
this.viewCtrl = viewCtrl;
this.navCtrl = navCtrl;
this.bindings = Injector.resolve([
bind(ViewController).toValue(viewCtrl)
bind(NavController).toValue(navCtrl)
]);
}
@@ -34,7 +34,7 @@ export class PaneController {
// Tabs and view's without a navbar would get a different Panes
let key = itemStructure.key;
let viewCtrl = this.viewCtrl;
let navCtrl = this.navCtrl;
let pane = this.panes[this.panes.length - 1];
if (pane && pane.key === key) {
@@ -43,7 +43,7 @@ export class PaneController {
} else {
// create a new nav pane
viewCtrl.loader.loadNextToLocation(Pane, viewCtrl.anchorElementRef(), this.bindings).then((componentRef) => {
navCtrl.loader.loadNextToLocation(Pane, navCtrl.anchorElementRef(), this.bindings).then((componentRef) => {
// get the pane reference
pane = this.newPane;
@@ -76,7 +76,7 @@ export class PaneController {
// as each section is compiled and added to the Pane
// the section will add a reference to itself in the Pane's sections object
promises.push(
viewCtrl.loader.loadNextToLocation(SectionClass, sectionAnchorElementRef)
navCtrl.loader.loadNextToLocation(SectionClass, sectionAnchorElementRef)
);
});
@@ -121,14 +121,14 @@ export class PaneController {
})
export class Pane extends Ion {
constructor(
@Inject(forwardRef(() => ViewController)) viewCtrl: ViewController,
@Inject(forwardRef(() => NavController)) navCtrl: NavController,
elementRef: ElementRef,
ionicConfig: IonicConfig
) {
super(elementRef, ionicConfig);
viewCtrl.panes.add(this);
navCtrl.panes.add(this);
this.totalItems = 0;
this.zIndex = ++viewCtrl.zIndexes;
this.zIndex = ++navCtrl.zIndexes;
}
}

View File

@@ -3,27 +3,27 @@ import {SlideEdgeGesture} from 'ionic/gestures/slide-edge-gesture';
export class SwipeBackGesture extends SlideEdgeGesture {
constructor(element: Element, opts: Object = {}, viewCtrl) {
constructor(element: Element, opts: Object = {}, navCtrl) {
super(element, opts);
// Can check corners through use of eg 'left top'
this.edges = opts.edge.split(' ');
this.threshold = opts.threshold;
this.viewCtrl = viewCtrl;
this.navCtrl = navCtrl;
}
onSlideStart() {
this.viewCtrl.swipeBackStart();
this.navCtrl.swipeBackStart();
}
onSlide(slide, ev) {
this.viewCtrl.swipeBackProgress(slide.distance / slide.max);
this.navCtrl.swipeBackProgress(slide.distance / slide.max);
}
onSlideEnd(slide, ev) {
let shouldComplete = (Math.abs(ev.velocityX) > 0.2 || Math.abs(slide.delta) > Math.abs(slide.max) * 0.5);
// TODO: calculate a better playback rate depending on velocity and distance
this.viewCtrl.swipeBackEnd(shouldComplete, 1);
this.navCtrl.swipeBackEnd(shouldComplete, 1);
}
}

View File

@@ -18,7 +18,7 @@ import {NavParams, NavController} from 'ionic/ionic';
'<p>{{title}}</p>' +
'<p><button id="from1To2" primary (click)="push()">Push (Go to 2nd)</button></p>' +
'<p><button [push-data]="pushData" [nav-push]="pushPage">Push w/ nav-push (Go to 2nd)</button></p>' +
'<p><button (click)="setItems()">setItems() (Go to 3rd, no history)</button></p>' +
'<p><button (click)="setViews()">setViews() (Go to 3rd, no history)</button></p>' +
'<icon class="ion-ios-arrow-back"></icon>' +
'<f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f>' +
'<f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f>' +
@@ -39,12 +39,12 @@ class FirstPage {
}
}
setItems() {
setViews() {
let items = [
ThirdPage
];
this.nav.setItems(items);
this.nav.setViews(items);
}
push() {
@@ -61,7 +61,7 @@ class FirstPage {
<p><button (click)="pop()">Pop (Go back to 1st)</button></p>
<p><button id="from2To1" nav-pop>Pop with NavPop (Go back to 1st)</button></p>
<p><button id="from2To3" (click)="push()">Push (Go to 3rd)</button></p>
<p><button (click)="setItems()">setItems() (Go to 3rd, FirstPage 1st in history)</button></p>
<p><button (click)="setViews()">setViews() (Go to 3rd, FirstPage 1st in history)</button></p>
<div class="green"><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f></div>
</ion-content>
`
@@ -77,13 +77,13 @@ class SecondPage {
console.log('Second page params:', params);
}
setItems() {
setViews() {
let items = [
FirstPage,
ThirdPage
];
this.nav.setItems(items);
this.nav.setViews(items);
}
pop() {

View File

@@ -1,13 +1,13 @@
import {RouteConfig, Location} from 'angular2/router';
import {App, IonicView, NavParams, ViewItem} from 'ionic/ionic';
import {App, IonicView, NavParams, ViewController} from 'ionic/ionic';
@IonicView({templateUrl: 'view1.html'})
class View1Cmp {
constructor(location: Location, viewItem: ViewItem) {
constructor(location: Location, viewCtrl: ViewController) {
this.path = location.path();
this.viewItem = viewItem;
this.viewCtrl = viewCtrl;
console.log(`View1Cmp, path: ${this.path}`);
}
onViewDidEnter() {
@@ -18,9 +18,9 @@ class View1Cmp {
@IonicView({templateUrl: 'view2.html'})
class View2Cmp {
constructor(location: Location, viewItem: ViewItem) {
constructor(location: Location, viewCtrl: ViewController) {
this.path = location.path();
this.viewItem = viewItem;
this.viewCtrl = viewCtrl;
console.log(`View2Cmp, path: ${this.path}`);
}
onViewDidEnter() {
@@ -31,10 +31,10 @@ class View2Cmp {
@IonicView({templateUrl: 'view3.html'})
class View3Cmp {
constructor(params: NavParams, location: Location, viewItem: ViewItem) {
constructor(params: NavParams, location: Location, viewCtrl: ViewController) {
this.id = params.get('id');
this.path = location.path();
this.viewItem = viewItem;
this.viewCtrl = viewCtrl;
console.log(`View3Cmp, path: ${this.path}, param id: ${this.id}`);
}
onViewDidEnter() {

View File

@@ -14,7 +14,7 @@
Location Path: {{path}}
</h4>
<h4>
ViewItem Id: {{viewItem.id}}
ViewController Id: {{viewCtrl.id}}
</h4>
<p>
<a href="#/second">Second View via href</a>

View File

@@ -16,7 +16,7 @@
Location Path: {{path}}
</h4>
<h4>
ViewItem Id: {{viewItem.id}}
ViewController Id: {{viewCtrl.id}}
</h4>
<p>
<a href="#/third/12">Third View via href, 12 as id param</a>

View File

@@ -14,7 +14,7 @@
NavParams, id: {{id}}
</h4>
<h4>
ViewItem Id: {{viewItem.id}}
ViewController Id: {{viewCtrl.id}}
</h4>
<p>
<a href="#/second">Second View via href</a>

View File

@@ -1,15 +1,15 @@
import {Component, EventEmitter, ElementRef, bind, Injector, ComponentRef} from 'angular2/angular2';
import {DirectiveBinding} from 'angular2/src/core/compiler/element_injector';
import {NavParams} from '../nav/nav-controller';
import {NavParams} from './nav-controller';
/**
* TODO
*/
export class ViewItem {
export class ViewController {
constructor(viewCtrl, componentType, params = {}) {
this.viewCtrl = viewCtrl;
constructor(navCtrl, componentType, params = {}) {
this.navCtrl = navCtrl;
this.componentType = componentType;
this.params = new NavParams(params);
this.instance = null;
@@ -47,9 +47,9 @@ export class ViewItem {
* @returns {TODO} TODO
*/
stage(callback) {
let viewCtrl = this.viewCtrl;
let navCtrl = this.navCtrl;
if (this.instance || !viewCtrl) {
if (this.instance || !navCtrl) {
// already compiled this view
return callback();
}
@@ -67,19 +67,19 @@ export class ViewItem {
ionViewComponentType.token = 'ionView' + this.componentType.name;
// compile the Component
viewCtrl.compiler.compileInHost(ionViewComponentType).then(hostProtoViewRef => {
navCtrl.compiler.compileInHost(ionViewComponentType).then(hostProtoViewRef => {
// figure out the sturcture of this Component
// does it have a navbar? Is it tabs? Should it not have a navbar or any toolbars?
let itemStructure = this.sturcture = this.inspectStructure(hostProtoViewRef);
// get the appropriate Pane which this ViewItem will fit into
viewCtrl.panes.get(itemStructure, pane => {
// get the appropriate Pane which this ViewController will fit into
navCtrl.panes.get(itemStructure, pane => {
this.pane = pane;
let bindings = viewCtrl.bindings.concat(Injector.resolve([
let bindings = navCtrl.bindings.concat(Injector.resolve([
bind(NavParams).toValue(this.params),
bind(ViewItem).toValue(this)
bind(ViewController).toValue(this)
]));
// add the content of the view to the content area
@@ -89,8 +89,8 @@ export class ViewItem {
// the same guts as DynamicComponentLoader.loadNextToLocation
var hostViewRef =
contentContainer.createHostView(hostProtoViewRef, -1, bindings);
var newLocation = viewCtrl.viewMngr.getHostElement(hostViewRef);
var newComponent = viewCtrl.viewMngr.getComponent(newLocation);
var newLocation = navCtrl.viewMngr.getHostElement(hostViewRef);
var newComponent = navCtrl.viewMngr.getComponent(newLocation);
pane.totalItems++;
var dispose = () => {
@@ -108,12 +108,12 @@ export class ViewItem {
this.disposals.push(dispose);
var viewComponetRef = new ComponentRef(newLocation, newComponent, dispose);
// get the component's instance, and set it to the this ViewItem
// get the component's instance, and set it to the this ViewController
this.setInstance(viewComponetRef.instance);
this.viewElementRef(viewComponetRef.location);
// // get the item container's nav bar
let navbarViewContainer = viewCtrl.navbarViewContainer();
let navbarViewContainer = navCtrl.navbarViewContainer();
// // get the item's navbar protoview
let navbarTemplateRef = this.templateRefs.navbar;
@@ -186,7 +186,7 @@ export class ViewItem {
});
});
if (this.viewCtrl.childNavbar()) {
if (this.navCtrl.childNavbar()) {
navbar = false;
}
@@ -204,8 +204,8 @@ export class ViewItem {
*/
enableBack() {
// update if it's possible to go back from this nav item
if (this.viewCtrl) {
let previousItem = this.viewCtrl.getPrevious(this);
if (this.navCtrl) {
let previousItem = this.navCtrl.getPrevious(this);
// the previous view may exist, but if it's about to be destroyed
// it shouldn't be able to go back to
return !!(previousItem && !previousItem.shouldDestroy);
@@ -219,11 +219,10 @@ export class ViewItem {
*/
setInstance(instance) {
this.instance = instance;
this.instance._viewItem = this;
}
get index() {
return (this.viewCtrl ? this.viewCtrl.indexOf(this) : -1);
return (this.navCtrl ? this.navCtrl.indexOf(this) : -1);
}
isRoot() {

View File

@@ -1,7 +1,7 @@
import {Directive, Component, View, Host, ElementRef, forwardRef, Injector, NgZone} from 'angular2/angular2';
import {ViewController} from '../view/view-controller';
import {ViewItem} from '../view/view-item';
import {NavController} from '../nav/nav-controller';
import {ViewController} from '../nav/view-controller';
import {Tabs} from './tabs';
@@ -35,7 +35,7 @@ import {Tabs} from './tabs';
template: '<template pane-anchor></template><ng-content></ng-content>',
directives: [forwardRef(() => TabPaneAnchor)]
})
export class Tab extends ViewController {
export class Tab extends NavController {
/**
* TODO
@@ -51,32 +51,32 @@ export class Tab extends ViewController {
zone: NgZone
) {
// A Tab is both a container of many views, and is a view itself.
// A Tab is one ViewItem within it's Host Tabs (which extends ViewController)
// A Tab is a ViewController for its child ViewItems
// A Tab is one ViewController within it's Host Tabs (which extends NavController)
// A Tab is a NavController for its child ViewControllers
super(tabs, injector, elementRef, zone);
this.tabs = tabs;
this.childNavbar(true);
let item = this.item = new ViewItem(tabs.Host);
item.setInstance(this);
item.viewElementRef(elementRef);
let viewCtrl = this.viewCtrl = new ViewController(tabs.Host);
viewCtrl.setInstance(this);
viewCtrl.viewElementRef(elementRef);
tabs.addTab(this);
this.navbarView = item.navbarView = () => {
let activeItem = this.getActive();
return activeItem && activeItem.navbarView();
this.navbarView = viewCtrl.navbarView = () => {
let activeView = this.getActive();
return activeView && activeView.navbarView();
};
item.enableBack = () => {
// override ViewItem's enableBack(), should use the
viewCtrl.enableBack = () => {
// override ViewController's enableBack(), should use the
// active child nav item's enableBack() instead
let activeItem = this.getActive();
return (activeItem && activeItem.enableBack());
let activeView = this.getActive();
return (activeView && activeView.enableBack());
};
this.panelId = 'tab-panel-' + item.id;
this.labeledBy = 'tab-button-' + item.id;
this.panelId = 'tab-panel-' + viewCtrl.id;
this.labeledBy = 'tab-button-' + viewCtrl.id;
}
onInit() {
@@ -118,11 +118,11 @@ export class Tab extends ViewController {
}
get isSelected() {
return this.tabs.isActive(this.item);
return this.tabs.isActive(this.viewCtrl);
}
get isNotSelected() {
return !this.tabs.isActive(this.item);
return !this.tabs.isActive(this.viewCtrl);
}
}

View File

@@ -1,8 +1,8 @@
import {Component, Directive, View, Injector, NgFor, ElementRef, Optional, Host, forwardRef, NgZone} from 'angular2/angular2';
import {IonicApp} from '../app/app';
import {ViewController} from '../view/view-controller';
import {ViewItem} from '../view/view-item';
import {NavController} from '../nav/nav-controller';
import {ViewController} from '../nav/view-controller';
import {Icon} from '../icon/icon';
import {IonicComponent, IonicView} from '../../config/decorators';
@@ -50,41 +50,41 @@ import {IonicComponent, IonicView} from '../../config/decorators';
'</section>',
directives: [forwardRef(() => TabButton)]
})
export class Tabs extends ViewController {
export class Tabs extends NavController {
/**
* TODO
*/
constructor(
@Optional() hostViewCtrl: ViewController,
@Optional() viewItem: ViewItem,
@Optional() hostNavCtrl: NavController,
@Optional() viewCtrl: ViewController,
app: IonicApp,
injector: Injector,
elementRef: ElementRef,
zone: NgZone
) {
super(hostViewCtrl, injector, elementRef, zone);
super(hostNavCtrl, injector, elementRef, zone);
this.app = app;
// Tabs may also be an actual ViewItem which was navigated to
// if Tabs is static and not navigated to within a ViewController
// then skip this and don't treat it as it's own ViewItem
if (viewItem) {
this.item = viewItem;
// Tabs may also be an actual ViewController which was navigated to
// if Tabs is static and not navigated to within a NavController
// then skip this and don't treat it as it's own ViewController
if (viewCtrl) {
this.viewCtrl = viewCtrl;
// special overrides for the Tabs ViewItem
// the Tabs ViewItem does not have it's own navbar
// special overrides for the Tabs ViewController
// the Tabs ViewController does not have it's own navbar
// so find the navbar it should use within it's active Tab
viewItem.navbarView = () => {
viewCtrl.navbarView = () => {
let activeTab = this.getActive();
if (activeTab && activeTab.instance) {
return activeTab.instance.navbarView();
}
};
// a Tabs ViewItem should not have a back button
// a Tabs ViewController should not have a back button
// enableBack back button will later be determined
// by the active ViewItem that has a navbar
viewItem.enableBack = () => {
// by the active ViewController that has a navbar
ViewController.enableBack = () => {
return false;
};
}
@@ -96,14 +96,14 @@ export class Tabs extends ViewController {
* @param {Tab} tab TODO
*/
addTab(tab) {
// tab.item refers to the ViewItem of the individual Tab being added to Tabs (ViewController)
// this.item refers to the ViewItem instsance on Tabs
this.add(tab.item);
// tab.viewCtrl refers to the ViewController of the individual Tab being added to Tabs (NavController)
// this.viewCtrl refers to the ViewController instsance on Tabs
this.add(tab.viewCtrl);
if (this.length() === 1) {
// this was the first tab added, queue this one to be loaded and selected
let promise = tab.queueInitial();
this.item && this.item.addPromise(promise);
this.viewCtrl && this.viewCtrl.addPromise(promise);
}
}
@@ -113,28 +113,28 @@ export class Tabs extends ViewController {
* @returns {TODO} TODO
*/
select(tab) {
let enteringItem = null;
let enteringView = null;
if (typeof tab === 'number') {
enteringItem = this.getByIndex(tab);
enteringView = this.getByIndex(tab);
} else {
enteringItem = this.getByInstance(tab)
enteringView = this.getByInstance(tab)
}
if (!enteringItem || !enteringItem.instance || !this.app.isEnabled()) {
if (!enteringView || !enteringView.instance || !this.app.isEnabled()) {
return Promise.reject();
}
return new Promise(resolve => {
enteringItem.instance.load(() => {
enteringView.instance.load(() => {
let opts = {
animate: false
};
let leavingItem = this.getActive() || new ViewItem();
leavingItem.shouldDestroy = false;
leavingItem.shouldCache = true;
let leavingView = this.getActive() || new ViewController();
leavingView.shouldDestroy = false;
leavingView.shouldCache = true;
this.transition(enteringItem, leavingItem, opts, () => {
this.transition(enteringView, leavingView, opts, () => {
resolve();
});
});
@@ -171,7 +171,7 @@ class TabButton {
}
onInit() {
let id = this.tab.item.id;
let id = this.tab.viewCtrl.id;
this.btnId = 'tab-button-' + id;
this.panelId = 'tab-panel-' + id;

View File

@@ -1,780 +0,0 @@
import {Compiler, ElementRef, Injector, bind, NgZone} from 'angular2/angular2';
import {DynamicComponentLoader} from 'angular2/src/core/compiler/dynamic_component_loader';
import {AppViewManager} from 'angular2/src/core/compiler/view_manager';
import {Ion} from '../ion';
import {IonicConfig} from '../../config/config';
import {IonicApp} from '../app/app';
import {ViewItem} from './view-item';
import {NavController} from '../nav/nav-controller';
import {PaneController} from '../nav/pane';
import {Transition} from '../../transitions/transition';
import {SwipeBackGesture} from './swipe-back';
import * as util from 'ionic/util';
/**
* TODO
*/
export class ViewController extends Ion {
constructor(
parentViewCtrl: ViewController,
injector: Injector,
elementRef: ElementRef,
zone: NgZone
) {
let config = injector.get(IonicConfig);
super(elementRef, config);
this.parent = parentViewCtrl;
this.compiler = injector.get(Compiler);
this.loader = injector.get(DynamicComponentLoader);
this.viewMngr = injector.get(AppViewManager);
this.app = injector.get(IonicApp);
this.config = config;
this.zone = zone;
this.items = [];
this.panes = new PaneController(this);
this._sbTrans = null;
this.sbEnabled = config.setting('swipeBackEnabled') || false;
this.sbThreshold = config.setting('swipeBackThreshold') || 40
this.id = ++ctrlIds;
this._ids = -1;
this.zIndexes = -1;
// build a new injector for child ViewItems to use
this.bindings = Injector.resolve([
bind(ViewController).toValue(this),
bind(NavController).toValue(new NavController(this))
]);
}
/**
* TODO
* @param {TODO} componentType TODO
* @param {TODO} [params={}] TODO
* @param {TODO} [opts={}] TODO
* @returns {Promise} TODO
*/
push(componentType, params = {}, opts = {}) {
if (!componentType) {
return Promise.reject();
}
if (typeof componentType !== 'function') {
throw 'Loading component must be a component class, not "' + componentType.toString() + '"';
}
let resolve;
let promise = new Promise(res => { resolve = res; });
// do not animate if this is the first in the stack
if (!this.items.length) {
opts.animation = 'none';
}
// default the direction to "forward"
opts.direction = opts.direction || 'forward';
// the active item is going to be the leaving one (if one exists)
let leavingItem = this.getActive() || new ViewItem();
leavingItem.shouldCache = (util.isBoolean(opts.cacheLeavingItem) ? opts.cacheLeavingItem : true);
leavingItem.shouldDestroy = !leavingItem.shouldCache;
if (leavingItem.shouldDestroy) {
leavingItem.willUnload();
}
// create a new ViewItem
let enteringItem = new ViewItem(this, componentType, params);
// add the item to the stack
this.add(enteringItem);
if (this.router) {
// notify router of the state change
this.router.stateChange('push', enteringItem, params);
}
// start the transition
this.transition(enteringItem, leavingItem, opts, () => {
resolve();
});
return promise;
}
/**
* TODO
* @param {TODO} [opts={}] TODO
* @returns {Promise} TODO
*/
pop(opts = {}) {
if (!this.canGoBack()) {
return Promise.reject();
}
let resolve;
let promise = new Promise(res => { resolve = res; });
// default the direction to "back"
opts.direction = opts.direction || 'back';
// get the active item and set that it is staged to be leaving
// was probably the one popped from the stack
let leavingItem = this.getActive() || new ViewItem();
leavingItem.shouldCache = (util.isBoolean(opts.cacheLeavingItem) ? opts.cacheLeavingItem : false);
leavingItem.shouldDestroy = !leavingItem.shouldCache;
if (leavingItem.shouldDestroy) {
leavingItem.willUnload();
}
// the entering item is now the new last item
// Note: we might not have an entering item if this is the
// only item on the history stack.
let enteringItem = this.getPrevious(leavingItem);
if (enteringItem) {
if (this.router) {
// notify router of the state change
this.router.stateChange('pop', enteringItem);
}
// start the transition
this.transition(enteringItem, leavingItem, opts, () => {
// transition completed, destroy the leaving item
resolve();
});
} else {
this._transComplete();
resolve();
}
return promise;
}
/**
* Set the item stack to reflect the given component classes.
* @param {TODO} components TODO
* @param {TODO} [opts={}] TODO
* @returns {Promise} TODO
*/
setItems(components, opts = {}) {
if (!components || !components.length) {
return Promise.resolve();
}
// if animate has not been set then default to false
opts.animate = opts.animate || false;
// ensure leaving items are not cached, and should be destroyed
opts.cacheLeavingItem = false;
// get the items to auto remove without having to do a transiton for each
// the last item (the currently active one) will do a normal transition out
if (this.items.length > 1) {
let autoRemoveItems = this.items.slice(0, this.items.length - 1);
for (let i = 0; i < autoRemoveItems.length; i++) {
autoRemoveItems[i].shouldDestroy = true;
autoRemoveItems[i].shouldCache = false;
autoRemoveItems[i].willUnload();
}
}
let componentObj = null;
let componentType = null;
let viewItem = null;
// create the ViewItems that go before the new active ViewItem in the stack
// but the previous views won't should 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;
viewItem = new ViewItem(this, componentType, componentObj.params);
viewItem.state = CACHED_STATE;
viewItem.shouldDestroy = false;
viewItem.shouldCache = false;
// add the item to the stack
this.add(viewItem);
}
}
}
// get the component that will become the active item
// it'll be the last one in the given components array
componentObj = components[ components.length - 1 ];
componentType = componentObj.componentType || componentObj;
// transition the leaving and entering
return this.push(componentType, componentObj.params, opts);
}
/**
* TODO
* @param {TODO} componentType TODO
* @param {TODO} [params={}] TODO
* @param {TODO} [opts={}] TODO
* @returns {Promise} TODO
*/
setRoot(componentType, params = {}, opts = {}) {
return this.setItems([{
componentType,
params
}], opts);
}
/**
* TODO
* @param {TODO} enteringItem TODO
* @param {TODO} leavingItem TODO
* @param {TODO} opts TODO
* @param {Function} callback TODO
* @returns {any} TODO
*/
transition(enteringItem, leavingItem, opts, callback) {
if (!enteringItem || enteringItem === leavingItem) {
return callback();
}
if (opts.animate === false) {
opts.animation = 'none';
} else if (!opts.animation) {
opts.animation = this.config.setting('viewTransition');
}
opts.animate = (opts.animation !== 'none');
// wait for the new item to complete setup
enteringItem.stage(() => {
this.zone.runOutsideAngular(() => {
enteringItem.shouldDestroy = false;
enteringItem.shouldCache = false;
enteringItem.willEnter();
leavingItem.willLeave();
// set that the new item pushed on the stack is staged to be entering/leaving
// staged state is important for the transition to find the correct item
enteringItem.state = STAGED_ENTERING_STATE;
leavingItem.state = STAGED_LEAVING_STATE;
// init the transition animation
let transAnimation = Transition.create(this, opts);
if (!opts.animate) {
// force it to not animate the elements, just apply the "to" styles
transAnimation.duration(0);
}
let duration = transAnimation.duration();
if (duration > 64) {
// block any clicks during the transition and provide a
// fallback to remove the clickblock if something goes wrong
this.app.setEnabled(false, duration);
}
// start the transition
transAnimation.play().then(() => {
// transition has completed, update each item's state
enteringItem.state = ACTIVE_STATE;
leavingItem.state = CACHED_STATE;
// dispose any items that shouldn't stay around
transAnimation.dispose();
enteringItem.didEnter();
leavingItem.didLeave();
// all done!
this.zone.run(() => {
this._transComplete();
callback();
});
});
});
});
}
/**
* TODO
*/
swipeBackStart() {
if (!this.app.isEnabled() || !this.canSwipeBack()) {
return;
}
// disables the app during the transition
this.app.setEnabled(false);
// default the direction to "back"
let opts = {
direction: 'back'
};
// get the active item and set that it is staged to be leaving
// was probably the one popped from the stack
let leavingItem = this.getActive() || new ViewItem();
leavingItem.shouldDestroy = true;
leavingItem.shouldCache = false;
leavingItem.willLeave();
leavingItem.willUnload();
// the entering item is now the new last item
let enteringItem = this.getPrevious(leavingItem);
enteringItem.shouldDestroy = false;
enteringItem.shouldCache = false;
enteringItem.willEnter();
// wait for the new item to complete setup
enteringItem.stage(() => {
this.zone.runOutsideAngular(() => {
// set that the new item pushed on the stack is staged to be entering/leaving
// staged state is important for the transition to find the correct item
enteringItem.state = STAGED_ENTERING_STATE;
leavingItem.state = STAGED_LEAVING_STATE;
// init the swipe back transition animation
this._sbTrans = Transition.create(this, opts);
this._sbTrans.easing('linear').progressStart();
});
});
}
/**
* TODO
* @param {TODO} progress TODO
*/
swipeBackProgress(value) {
if (this._sbTrans) {
// continue to disable the app while actively dragging
this.app.setEnabled(false, 4000);
// set the transition animation's progress
this._sbTrans.progress(value);
}
}
/**
* @private
* @param {TODO} completeSwipeBack Should the swipe back complete or not.
* @param {number} rate How fast it closes
*/
swipeBackEnd(completeSwipeBack, rate) {
if (!this._sbTrans) return;
// disables the app during the transition
this.app.setEnabled(false);
this._sbTrans.progressEnd(completeSwipeBack, rate).then(() => {
this.zone.run(() => {
// find the items that were entering and leaving
let enteringItem = this.getStagedEnteringItem();
let leavingItem = this.getStagedLeavingItem();
if (enteringItem && leavingItem) {
// finish up the animation
if (completeSwipeBack) {
// swipe back has completed navigating back
// update each item's state
enteringItem.state = ACTIVE_STATE;
leavingItem.state = CACHED_STATE;
enteringItem.didEnter();
leavingItem.didLeave();
if (this.router) {
// notify router of the pop state change
this.router.stateChange('pop', enteringItem);
}
} else {
// cancelled the swipe back, they didn't end up going back
// return items to their original state
leavingItem.state = ACTIVE_STATE;
enteringItem.state = CACHED_STATE;
leavingItem.willEnter();
leavingItem.didEnter();
enteringItem.didLeave();
leavingItem.shouldDestroy = false;
enteringItem.shouldDestroy = false;
}
}
// empty out and dispose the swipe back transition animation
this._sbTrans && this._sbTrans.dispose();
this._sbTrans = null;
// all done!
this._transComplete();
});
});
}
_runSwipeBack() {
if (this.canSwipeBack()) {
// it is possible to swipe back
if (this.sbGesture) {
// this is already an active gesture, don't create another one
return;
}
let opts = {
edge: 'left',
threshold: this.sbThreshold
};
this.sbGesture = new SwipeBackGesture(this.getNativeElement(), opts, this);
console.debug('SwipeBackGesture listen');
this.sbGesture.listen();
} else if (this.sbGesture) {
// it is not possible to swipe back and there is an
// active sbGesture, so unlisten it
console.debug('SwipeBackGesture unlisten');
this.sbGesture.unlisten();
this.sbGesture = null;
}
}
/**
* TODO
* @param {TODO} val TODO
* @returns {TODO} TODO
*/
isSwipeBackEnabled(val) {
if (arguments.length) {
this.sbEnabled = !!val;
}
return this.sbEnabled;
}
/**
* If it's possible to use swipe back or not. If it's not possible
* to go back, or swipe back is not enable then this will return false.
* If it is possible to go back, and swipe back is enabled, then this
* will return true.
* @returns {boolean}
*/
canSwipeBack() {
return (this.sbEnabled && this.canGoBack());
}
/**
* Returns `true` if there's a valid previous view that we can pop back to.
* Otherwise returns false.
* @returns {boolean}
*/
canGoBack() {
let activeItem = this.getActive();
if (activeItem) {
return activeItem.enableBack();
}
return false;
}
/**
* @private
*/
_transComplete() {
let destroys = [];
this.items.forEach(item => {
if (item) {
if (item.shouldDestroy) {
destroys.push(item);
} else if (item.state === CACHED_STATE && item.shouldCache) {
item.shouldCache = false;
}
}
});
destroys.forEach(item => {
this.remove(item);
item.destroy();
});
// allow clicks again, but still set an enable time
// meaning nothing with this view controller can happen for XXms
this.app.setEnabled(true);
if (this.items.length === 1) {
this.elementRef.nativeElement.classList.add('has-views');
}
this._runSwipeBack();
}
/**
* TODO
* @returns {TODO} TODO
*/
getActive() {
for (let i = 0, ii = this.items.length; i < ii; i++) {
if (this.items[i].state === ACTIVE_STATE) {
return this.items[i];
}
}
return null;
}
/**
* TODO
* @param {TODO} instance TODO
* @returns {TODO} TODO
*/
getByInstance(instance) {
if (instance) {
for (let i = 0, ii = this.items.length; i < ii; i++) {
if (this.items[i].instance === instance) {
return this.items[i];
}
}
}
return null;
}
/**
* TODO
* @param {TODO} index TODO
* @returns {TODO} TODO
*/
getByIndex(index) {
if (index < this.items.length && index > -1) {
return this.items[index];
}
return null;
}
/**
* TODO
* @param {TODO} item TODO
* @returns {TODO} TODO
*/
getPrevious(item) {
if (item) {
return this.items[ this.items.indexOf(item) - 1 ];
}
return null;
}
/**
* TODO
* @returns {TODO} TODO
*/
getStagedEnteringItem() {
for (let i = 0, ii = this.items.length; i < ii; i++) {
if (this.items[i].state === STAGED_ENTERING_STATE) {
return this.items[i];
}
}
return null;
}
/**
* TODO
* @returns {TODO} TODO
*/
getStagedLeavingItem() {
for (let i = 0, ii = this.items.length; i < ii; i++) {
if (this.items[i].state === STAGED_LEAVING_STATE) {
return this.items[i];
}
}
return null;
}
/**
* TODO
* @param {TODO} nbContainer TODO
* @returns {TODO} TODO
*/
navbarViewContainer(nbContainer) {
if (nbContainer) {
this._nbContainer = nbContainer;
}
if (this._nbContainer) {
return this._nbContainer;
}
if (this.parent) {
return this.parent.navbarViewContainer();
}
}
/**
* TODO
* @returns {TODO} TODO
*/
anchorElementRef() {
if (arguments.length) {
this._anchorER = arguments[0];
}
return this._anchorER;
}
/**
* TODO
* @returns {TODO} TODO
*/
anchorViewContainerRef() {
if (arguments.length) {
this._anchorVC = arguments[0];
}
return this._anchorVC;
}
/**
* TODO
* @returns {TODO} TODO
*/
childNavbar() {
if (arguments.length) {
this._childNavbar = arguments[0];
}
return this._childNavbar;
}
/**
* TODO
* @param {TODO} item TODO
* @returns {TODO} TODO
*/
add(item) {
item.id = this.id + '-' + (++this._ids);
this.items.push(item);
}
/**
* TODO
* @param {TODO} itemOrIndex TODO
* @returns {TODO} TODO
*/
remove(itemOrIndex) {
util.array.remove(this.items, itemOrIndex);
}
/**
* First view item in this view controller's stack. This would
* not return an item which is about to be destroyed.
* @returns {TODO} TODO
*/
first() {
for (let i = 0, l = this.items.length; i < l; i++) {
if (!this.items[i].shouldDestroy) {
return this.items[i];
}
}
return null;
}
/**
* Last view item in this view controller's stack. This would
* not return an item which is about to be destroyed.
* @returns {TODO} TODO
*/
last() {
for (let i = this.items.length - 1; i >= 0; i--) {
if (!this.items[i].shouldDestroy) {
return this.items[i];
}
}
return null;
}
/**
* TODO
* @param {TODO} item TODO
* @returns {TODO} TODO
*/
indexOf(item) {
return this.items.indexOf(item);
}
/**
* Number of sibling view items in the view controller. This does
* not include items which are about to be destroyed.
* @returns {TODO} TODO
*/
length() {
let len = 0;
for (let i = 0, l = this.items.length; i < l; i++) {
if (!this.items[i].shouldDestroy) {
len++;
}
}
return len;
}
/**
* TODO
* @returns {TODO} TODO
*/
instances() {
let instances = [];
for (let item of this.items) {
if (item.instance) {
instances.push(item.instance);
}
}
return instances;
}
/**
* TODO
* @param {TODO} item TODO
* @returns {TODO} TODO
*/
isActive(item) {
return (item && item.state === ACTIVE_STATE);
}
/**
* TODO
* @param {TODO} item TODO
* @returns {TODO} TODO
*/
isStagedEntering(item) {
return (item && item.state === STAGED_ENTERING_STATE);
}
/**
* TODO
* @param {TODO} router TODO
*/
registerRouter(router) {
this.router = router;
}
}
const ACTIVE_STATE = 1;
const CACHED_STATE = 2;
const STAGED_ENTERING_STATE = 3;
const STAGED_LEAVING_STATE = 4;
let ctrlIds = -1;

View File

@@ -1,17 +0,0 @@
// View
// --------------------------------------------------
ion-view {
display: flex;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
flex-direction: column;
background-color: white;
transform: translateZ(0px);
}