This commit is contained in:
Adam Bradley
2015-05-12 14:39:22 -05:00
parent 79c17318fb
commit 0c935944cb
9 changed files with 178 additions and 302 deletions

View File

@ -1,6 +1,6 @@
import {NgElement} from 'angular2/angular2';
import * as util from 'ionic/util'; import * as util from 'ionic/util';
import {Transition, ClickBlock} from 'ionic/ionic'; import {Transition, ClickBlock} from 'ionic/ionic';
import {NavItem} from './nav-item'
const STAGED_STATE = 'staged'; const STAGED_STATE = 'staged';
@ -12,85 +12,19 @@ const ACTIVE_STATE = 'active';
const CACHED_STATE = 'cached'; const CACHED_STATE = 'cached';
/*
* Used by tabs and nav
*/
export class NavBase { export class NavBase {
constructor(
element: NgElement
) {
this.domElement = element.domElement;
// this is our sane stack of items. This is synchronous and says an item constructor() {
// is removed even if it's still animating out. this.items = [];
this._stack = [];
// The navItems array is what add/remove components from the dom.
// These arrays won't remove a component until they're
// done animating out.
this.navItems = [];
}
containsClass(Class) {
for (let i = 0; i < this._stack.length; i++) {
if (this._stack[i].Class === Class) {
return true;
}
}
return false;
} }
set initial(Class) { set initial(Class) {
if (!this.initialized) { if (!this._init) {
this.initialized = true this._init = true
this.push(Class); this.push(Class);
} }
} }
getActive() {
for (let i = 0, ii = this.navItems.length; i < ii; i++) {
if (this.navItems[i].state === ACTIVE_STATE) {
return this.navItems[i];
}
}
return null;
}
getPrevious(item) {
if (item) {
return this._stack[ this._stack.indexOf(item) - 1 ];
}
return null;
}
getStagedEnteringItem() {
for (let i = 0, ii = this.navItems.length; i < ii; i++) {
if (this.navItems[i].state === STAGED_ENTERING_STATE) {
return this.navItems[i];
}
}
return null;
}
getStagedLeavingItem() {
for (let i = 0, ii = this.navItems.length; i < ii; i++) {
if (this.navItems[i].state === STAGED_LEAVING_STATE) {
return this.navItems[i];
}
}
return null;
}
getLeavingItems() {
let items = [];
for (let i = 0, ii = this.navItems.length; i < ii; i++) {
if (this.navItems[i].state === ACTIVELY_LEAVING_STATE || this.navItems[i].state === STAGED_LEAVING_STATE) {
items.push(this.navItems[i]);
}
}
return items;
}
push(Class: Function, params = {}, opts = {}) { push(Class: Function, params = {}, opts = {}) {
let resolve; let resolve;
let promise = new Promise(res => { resolve = res; }); let promise = new Promise(res => { resolve = res; });
@ -99,7 +33,7 @@ export class NavBase {
opts.direction = opts.direction || 'forward'; opts.direction = opts.direction || 'forward';
// do not animate if this is the first in the stack // do not animate if this is the first in the stack
if (!this._stack.length) { if (!this.items.length) {
opts.animation = 'none'; opts.animation = 'none';
} }
@ -107,14 +41,13 @@ export class NavBase {
let leavingItem = this.getActive() || {}; let leavingItem = this.getActive() || {};
// create a new NavStackItem // create a new NavStackItem
let enteringItem = new NavStackItem(Class, params); let enteringItem = new NavItem(this, Class, params);
// set that this item is staged (it's not ready to be animated in yet) // set that this item is staged (it's not ready to be animated in yet)
enteringItem.state = STAGED_STATE; enteringItem.state = STAGED_STATE;
// add the item to the stack (just renders in the DOM, doesn't animate yet) // add the item to the stack
this._stack.push(enteringItem); this.items.push(enteringItem);
this.navItems.push(enteringItem);
// start the transition // start the transition
this.transition(enteringItem, leavingItem, opts).then(() => { this.transition(enteringItem, leavingItem, opts).then(() => {
@ -132,7 +65,7 @@ export class NavBase {
opts.direction = opts.direction || 'back'; opts.direction = opts.direction || 'back';
// remove the last item // remove the last item
this._stack.pop(); this.items.pop();
// the entering item is now the new last item // the entering item is now the new last item
let enteringItem = this.last() let enteringItem = this.last()
@ -206,17 +139,64 @@ export class NavBase {
return promise; return promise;
} }
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;
}
getPrevious(item) {
if (item) {
return this.items[ this.items.indexOf(item) - 1 ];
}
return null;
}
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;
}
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;
}
getLeavingItems() {
let items = [];
for (let i = 0, ii = this.items.length; i < ii; i++) {
if (this.items[i].state === ACTIVELY_LEAVING_STATE || this.items[i].state === STAGED_LEAVING_STATE) {
items.push(this.items[i]);
}
}
return items;
}
last() { last() {
return this._stack[this._stack.length - 1] return this.items[this.items.length - 1]
} }
length() { length() {
return this._stack.length; return this.items.length;
} }
popAll() { popAll() {
while (this._stack.length) { while (this.items.length) {
const item = this._stack.pop() const item = this.items.pop()
this._destroy(item) this._destroy(item)
} }
} }
@ -226,31 +206,31 @@ export class NavBase {
// then performs a normal pop. // then performs a normal pop.
popTo(index, opts = {}) { popTo(index, opts = {}) {
// Abort if we're already here. // Abort if we're already here.
if (this._stack.length <= index + 1) { if (this.items.length <= index + 1) {
return Promise.resolve(); return Promise.resolve();
} }
// Save the current navItem, and remove all the other ones in front of our // Save the current navItem, and remove all the other ones in front of our
// target nav item. // target nav item.
const current = this._stack.pop() const current = this.items.pop()
while (this._stack.length > index + 1) { while (this.items.length > index + 1) {
const item = this._stack.pop() const item = this.items.pop()
this._destroy(item) this._destroy(item)
} }
// Now put the current navItem back on the stack and run a normal pop animation. // Now put the current navItem back on the stack and run a normal pop animation.
this._stack.push(current) this.items.push(current)
return this.pop(opts) return this.pop(opts)
} }
remove(index) { remove(index) {
const item = this._stack[index]; const item = this.items[index];
this._stack.splice(index, 1); this.items.splice(index, 1);
this._destroy(item); this._destroy(item);
} }
_destroy(navItem) { _destroy(navItem) {
util.array.remove(this.navItems, navItem); util.array.remove(this.items, navItem);
} }
getToolbars(pos: String) { getToolbars(pos: String) {
@ -270,27 +250,3 @@ export class NavBase {
return !!this.getPrevious(this.getActive()); return !!this.getPrevious(this.getActive());
} }
} }
class NavStackItem {
constructor(ComponentClass, params = {}) {
this.Class = ComponentClass;
this.params = params;
this.id = util.nextUid();
this._setupPromise = new Promise((resolve) => {
this._resolveSetup = resolve;
});
}
setup() {
return this._setupPromise;
}
finishSetup(navItem, componentInstance) {
this.navItem = navItem;
this.instance = componentInstance;
this._resolveSetup();
}
}

View File

@ -0,0 +1,13 @@
export class NavController {
constructor() {
}
push() {
return this.nav.push.apply(this.nav, arguments);
}
pop() {
return this.nav.pop.apply(this.nav, arguments);
}
}

View File

@ -1,33 +0,0 @@
import {
Decorator,
View,
For,
NgElement,
Parent,
Ancestor
} from 'angular2/angular2';
import {Optional} from 'angular2/di'
import {Nav} from 'ionic/ionic';
@Decorator({
selector: '[push-to]'
})
export class PushToNav {
constructor(
element: NgElement,
@Ancestor() viewportNav: Nav
) {
console.log('PUSH TO NAV', element.domElement, viewportNav);
this.navTag = element.domElement.getAttribute('push-to');
console.log('PUSH TO NAV', this.navTag);
}
}
@Decorator({
selector: '[href]'
})
export class HrefNav {
constructor(element: NgElement) {
}
}

View File

@ -1,127 +1,40 @@
import { import {ViewContainerRef} from 'angular2/src/core/compiler/view_container_ref';
Component,
DynamicComponent,
Decorator,
Ancestor,
NgElement,
DynamicComponentLoader,
ElementRef,
Query,
View,
} from 'angular2/angular2';
import {
Injectable,
bind,
Optional,
} from 'angular2/di';
import {Toolbar} from 'ionic/components/toolbar/toolbar';
import {NavInjectable} from 'ionic/components/nav/nav';
import * as util from 'ionic/util'; import * as util from 'ionic/util';
/*
* NavController is the public interface for pushing, popping, etc that is injected
* by users.
* Each nav item exposes a new NavController.
*/
export class NavController {
constructor() {
// Contains data passed to this NavController's NavItem
this.params = {};
// this.navItem and this.nav are set by NavItem below.
}
addToolbar(placement: String, toolbar: Toolbar) {
this.navItem._toolbars[placement].push(toolbar);
}
removeToolbar(placement: String, toolbar: Toolbar) {
let bars = this.navItem._toolbars[placement];
bars && util.array.remove(bars, toolbar);
}
push() {
return this.nav.push.apply(this.nav, arguments);
}
pop() {
return this.nav.pop.apply(this.nav, arguments);
}
}
@Component({
selector: '.nav-item',
properties: {
_item: 'item'
},
injectables: [
// Allow all descendants to inject NavController and do nav operations.
NavController
]
})
@View({
// See below for this.
template: '<div class="nav-item-child"></div>',
directives: [NavItemDynamicComponent]
})
export class NavItem { export class NavItem {
constructor(
// TODO for now, we can't inject tab as if it's a Nav constructor(nav, ComponentClass, params = {}) {
navInjectable: NavInjectable, this.nav = nav;
navCtrl: NavController, this.Class = ComponentClass;
element: NgElement this.params = params;
) { this.id = util.nextUid();
this.domElement = element.domElement;
this._toolbars = {
top: [],
bottom: []
};
this.navCtrl = navCtrl;
navCtrl.nav = navInjectable.nav;
navCtrl.navItem = this;
} }
set _item(data) { setup() {
var navChild = this.dynamicComponentChild; let resolve;
let promise = new Promise((res) => { resolve = res; });
if (this.initialized) return; let injector = null;
this.initialized = true;
this.Class = data.Class;
util.extend(this.navCtrl.params, data.params || {}); this.nav.loader.loadIntoExistingLocation(this.Class, this.nav.itemContent.elementRef, injector)
.then((componentRef) => {
navChild._loader.loadIntoExistingLocation(this.Class, navChild._elementRef).then((instance) => { console.log('Component loadIntoExistingLocation completed')
this.instance = instance;
data.finishSetup(this, instance); resolve();
}); });
}
// let vc = new ViewContainerRef(this.nav.viewManager, this.nav.elementRef);
// debugger
// let view = vc.create(this.Class, -1, this.nav.itemContent.elementRef, injector);
return promise;
} }
/**
* In angular 2.0.0-alpha.21, DynamicComponents are not allowed to publish injectables
* (bug, see https://github.com/angular/angular/issues/1596).
* To fix this problem, we have the `NavItem` above publish a `NavController ` injectable
* so the rest of the app has access to nav operations. Then we have a DynamicComponent
* instantiate the user's page.
* The `NavItem` class above can be turned into a DynamicComponent and this class can be removed
* once injectables are allowed on regular Components.
*/
@DynamicComponent({
selector: '.nav-item-child',
properties: {
_item: 'item'
}
})
export class NavItemDynamicComponent {
constructor(
loader: DynamicComponentLoader,
elementRef: ElementRef,
@Ancestor() navItem: NavItem
) {
this._loader = loader;
this._elementRef = elementRef;
navItem.dynamicComponentChild = this;
}
} }

View File

@ -1,58 +1,76 @@
import { import {Parent, Ancestor} from 'angular2/src/core/annotations_impl/visibility';
Component, import {Component, Directive} from 'angular2/src/core/annotations_impl/annotations';
View, import {View} from 'angular2/src/core/annotations_impl/view';
If, import {DynamicComponentLoader} from 'angular2/src/core/compiler/dynamic_component_loader';
For, import {ElementRef} from 'angular2/src/core/compiler/element_ref';
NgElement,
Query,
QueryList,
Descendants
} from 'angular2/angular2';
import {NavBase} from 'ionic/components/nav/nav-base'; import {NavBase} from 'ionic/components/nav/nav-base';
import {NavItem, NavItemDynamicComponent} from 'ionic/components/nav/nav-item'; import {NavItem, NavItemDynamicComponent} from 'ionic/components/nav/nav-item';
import {ToolbarContainer} from 'ionic/components/toolbar/toolbar'; import {ToolbarContainer} from 'ionic/components/toolbar/toolbar';
/**
* We need a way for children to get ahold of the instantiated `Nav`.
* Until angular supports components adding themselves to the Injector,
* Nav is going to add an instance of a "NavInjectable" class to the injector.
* This NavInjectable will have a pointer to the Nav class on `this.nav`.
* Now descendant components (only our private ones) will have access to NavInjectable,
* and be able to get access to the Nav through `navInjectable.nav` (@see navItem)
*/
export class NavInjectable {}
@Component({ @Component({
selector: 'ion-nav', selector: 'ion-nav',
properties: { properties: {
initial: 'initial' initial: 'initial'
}, }
injectables: [
// Add NavInjectable to the injector for this and all descendants
NavInjectable
]
}) })
@View({ @View({
template: ` template: `
<header class="toolbar-container" [class.hide]="hideHeader"> <header class="toolbar-container">
<div *for="#toolbar of getToolbars('top')" [toolbar-create]="toolbar"></div> <header-container></header-container>
</header> </header>
<section class="nav-item-container"> <section class="nav-item-container">
<div class="nav-item" *for="#item of navItems" [item]="item"></div> <content-container></content-container>
</section> </section>
<footer class="toolbar-container" [class.hide]="hideFooter"> <footer class="toolbar-container">
<div *for="#toolbar of getToolbars('bottom')" [toolbar-create]="toolbar"></div> <footer-container></footer-container>
</footer> </footer>
`, `,
directives: [NavItem, For, If, ToolbarContainer] directives: [HeaderContainer, ContentContainer, FooterContainer]
}) })
export class Nav extends NavBase { export class Nav extends NavBase {
constructor( constructor(
element: NgElement, loader: DynamicComponentLoader,
navInjectable: NavInjectable elementRef: ElementRef
) { ) {
super(element); super();
// Add the nav to navInjectable. this.loader = loader;
navInjectable.nav = this; this.viewManager = loader._viewManager;
this.elementRef = elementRef;
}
}
@Directive({
selector: 'header-container'
})
class HeaderContainer {
constructor(@Ancestor() nav: Nav) {
nav.itemHeader = this;
}
}
@Directive({
selector: 'content-container'
})
class ContentContainer {
constructor(@Ancestor() nav: Nav, elementRef: ElementRef) {
nav.itemContent = {
elementRef: elementRef
};
}
}
@Directive({
selector: 'footer-container'
})
class FooterContainer {
constructor(@Ancestor() nav: Nav) {
nav.footerContainer = this;
} }
} }

View File

@ -9,11 +9,14 @@ import {FirstPage} from './pages/first-page'
@Component({ selector: 'ion-app' }) @Component({ selector: 'ion-app' })
@View({ @View({
templateUrl: 'main.html' templateUrl: 'main.html',
directives: [Nav]
}) })
class IonicApp { class IonicApp {
constructor() { constructor() {
console.log('IonicApp Start'); console.log('IonicApp Start');
this.initial = FirstPage;
} }
} }

View File

@ -1,12 +1,14 @@
import {Component, View, Parent} from 'angular2/angular2' import {Component, Directive} from 'angular2/src/core/annotations_impl/annotations';
import {NavController, Toolbar, Content} from 'ionic/ionic' import {View} from 'angular2/src/core/annotations_impl/view';
import {NavController} from 'ionic/ionic'
import {SecondPage} from './second-page' import {SecondPage} from './second-page'
@Component() @Component()
@View({ @View({
templateUrl: 'pages/first-page.html', templateUrl: 'pages/first-page.html',
directives: [Toolbar] directives: []
}) })
export class FirstPage { export class FirstPage {
constructor( constructor(

View File

@ -1,12 +1,14 @@
import {Component, View, Parent} from 'angular2/angular2' import {Component, Directive} from 'angular2/src/core/annotations_impl/annotations';
import {NavController, Toolbar, Content} from 'ionic/ionic' import {View} from 'angular2/src/core/annotations_impl/view';
import {NavController} from 'ionic/ionic'
import {ThirdPage} from './third-page' import {ThirdPage} from './third-page'
@Component() @Component()
@View({ @View({
templateUrl: 'pages/second-page.html', templateUrl: 'pages/second-page.html',
directives: [Toolbar] directives: []
}) })
export class SecondPage { export class SecondPage {
constructor( constructor(

View File

@ -1,11 +1,13 @@
import {Component, View, Parent} from 'angular2/angular2' import {Component, Directive} from 'angular2/src/core/annotations_impl/annotations';
import {NavController, Toolbar, Content} from 'ionic/ionic' import {View} from 'angular2/src/core/annotations_impl/view';
import {NavController} from 'ionic/ionic'
@Component() @Component()
@View({ @View({
templateUrl: 'pages/third-page.html', templateUrl: 'pages/third-page.html',
directives: [Toolbar, Content] directives: []
}) })
export class ThirdPage { export class ThirdPage {
constructor( constructor(