diff --git a/ionic/components.js b/ionic/components.js index 86654b3ca5..3d6ce00826 100644 --- a/ionic/components.js +++ b/ionic/components.js @@ -11,8 +11,9 @@ export * from 'ionic/components/form/form' export * from 'ionic/components/input/input' export * from 'ionic/components/layout/layout' export * from 'ionic/components/list/list' -export * from 'ionic/components/nav-pane/nav-pane' +export * from 'ionic/components/nav-pane/nav-pane' //TODO remove export * from 'ionic/components/nav/nav' +export * from 'ionic/components/nav/nav-item' export * from 'ionic/components/radio/radio-button' export * from 'ionic/components/radio/radio-group' export * from 'ionic/components/search-bar/search-bar' diff --git a/ionic/components/app/structure.scss b/ionic/components/app/structure.scss index 08e3b54987..8d11427e63 100644 --- a/ionic/components/app/structure.scss +++ b/ionic/components/app/structure.scss @@ -43,7 +43,7 @@ html { height: 100%; } - .nav-pane-container { + .nav-item-container { // container of many .nav-pane's, each one containing one view position: relative; @include flex(1); diff --git a/ionic/components/nav/nav-controller.js b/ionic/components/nav/nav-controller.js new file mode 100644 index 0000000000..f1ba702efc --- /dev/null +++ b/ionic/components/nav/nav-controller.js @@ -0,0 +1,199 @@ +import {NgElement} from 'angular2/angular2'; +import * as util from 'ionic/util'; + +export class NavControllerBase { + constructor( + element: NgElement + ) { + this.domElement = element.domElement + this.domElement.classList.add('nav') + + // this is our sane stack of items. This is synchronous and says an item + // is removed even if it's still animating out. + this._stack = [] + + // _ngForLoopArray is what adds/removes components from the dom. It won't + // remove a component until it's done animating out. + this._ngForLoopArray = [] + } + + containsClass(Class) { + for (let i = 0; i < this._stack.length; i++) { + if (this._stack[i].Class === Class) { + return true + } + } + return false + } + + set initial(Class) { + if (!this.initialized) { + this.initialized = true + this.push(Class) + } + } + //TODO let the view handle enter/leave so splitview can override + + /** + * Push a new view into the history stack. + * + * @param view the new view + * @param shouldAnimate whether to animate + */ + // TODO don't push same component twice if one is already pushing + // TODO only animate if state hasn't changed + // TODO make sure the timing is together + // TODO allow starting an animation in the middle (eg gestures). Leave + // most of this up to the animation's implementation. + push(Class: Function, params = {}, opts = {}) { + util.defaults(opts, { + sync: this._stack.length === 0 + }) + + let pushedItem = new NavStackData(Class, params) + this._stack.push(pushedItem) + this._ngForLoopArray.push(pushedItem) + + return pushedItem.waitForSetup().then(() => { + let current = this._getPrevious(pushedItem) + current && current.leaveReverse(opts) + return pushedItem.enter(opts) + }) + } + + /** + * Pop a view off the history + * + * @param shouldAnimate whether to animate + */ + pop(opts = {}) { + util.defaults(opts, { + sync: false + }) + let current = this._stack.pop() + let dest = this.peek() + + dest && dest.enterReverse(opts) + return current && current.leave(opts).then(() => this._destroy(current)) + } + + peek() { + return this._stack[this._stack.length - 1] + } + + popAll() { + while (this._stack.length) { + const item = this._stack.pop() + this._destroy(item) + } + } + + // Pop from the current item to the item at the specified index. + // Removes every item in the stack between the current and the given index, + // then performs a normal pop. + popTo(index, opts = {}) { + // Abort if we're already here. + if (this._stack.length <= index + 1) { + return Promise.resolve() + } + + // Save the current navItem, and remove all the other ones in front of our + // target nav item. + const current = this._stack.pop() + while (this._stack.length > index + 1) { + const item = this._stack.pop() + this._destroy(item) + } + + // Now put the current navItem back on the stack and run a normal pop animation. + this._stack.push(current) + return this.pop(opts) + } + + setStack(stack) { + this._stack = stack.slice() + this._ngForLoopArray = stack.slice() + } + + remove(index) { + const item = this._stack[index] + this._stack.splice(index, 1) + this._destroy(item) + } + + _destroy(navItem) { + console.warn( +`Component "${navItem.Class.name}" was popped from the nav stack, But were keeping its element in the DOM for now because of an ng2 bug.` + ); + //util.array.remove(this._ngForLoopArray, navItem) + } + + _getPrevious(item) { + return this._stack[ this._stack.indexOf(item) - 1 ] + } +} + +class NavStackData { + constructor(ComponentClass, params = {}) { + this.Class = ComponentClass + this.params = params + this._setupPromise = new Promise((resolve) => { + this._resolveSetupPromise = resolve + }) + } + waitForSetup() { + return this._setupPromise + } + finishSetup(navItem, componentInstance) { + this.navItem = navItem + this.instance = componentInstance + this._resolveSetupPromise() + } + setAnimation(state) { + if (!state) { + this.navItem.domElement.removeAttribute('animate') + this.navItem.domElement.classList.remove('start') + } else { + this.navItem.domElement.setAttribute('animate', state) + } + } + setShown(isShown) { + this.navItem.domElement.classList[isShown?'add':'remove']('shown') + } + startAnimation() { + this.navItem.domElement.classList.add('start') + } + _animate({ isShown, animation }) { + this.setAnimation(animation) + this.setShown(isShown) + if (animation) { + // We have to wait two rafs for the element to show. Yawn. + return util.dom.rafPromise().then(util.dom.rafPromise).then(() => { + this.startAnimation() + return util.dom.transitionEndPromise(this.navItem.domElement).then(() => { + this.setAnimation(null) + }) + }) + } else { + return Promise.resolve() + } + } + enterReverse(opts) { + return this.enter( util.extend({reverse: true}, opts) ) + } + enter({ reverse = false, sync = false } = {}) { + return this._animate({ + isShown: true, + animation: sync ? null : (reverse ? 'enter-reverse' : 'enter') + }) + } + leave({ reverse = false, sync = false } = {}) { + return this._animate({ + isShown: false, + animation: sync ? null : (reverse ? 'leave-reverse' : 'leave') + }) + } + leaveReverse(opts) { + return this.leave( util.extend({reverse: true}, opts) ) + } +} diff --git a/ionic/components/nav/nav-item.js b/ionic/components/nav/nav-item.js new file mode 100644 index 0000000000..a0dad27f89 --- /dev/null +++ b/ionic/components/nav/nav-item.js @@ -0,0 +1,71 @@ +import { + DynamicComponent, + Parent, + NgElement, + DynamicComponentLoader, + ElementRef, +} from 'angular2/angular2'; +import { + Injectable, + bind, + Optional, +} from 'angular2/di'; +import {Nav} from 'ionic/components/nav/nav' +import {Tab} from 'ionic/components/tabs/tab' +import * as util from 'ionic/util'; + +@DynamicComponent({ + selector: 'ion-nav-item', + properties: { + _item: 'item' + } +}) +export class NavItem { + constructor( + @NgElement() element: NgElement, + loader: DynamicComponentLoader, + elementRef: ElementRef + + // FIXME: this is temporary until ng2 lets us inject tabs as a Nav + // @Optional() @Parent() viewportNav: Nav, + // @Optional() @Parent() viewportTab: Tab + ) { + this._loader = loader; + this._elementRef = elementRef; + // this.viewport = viewportTab || viewportNav; + this.domElement = element.domElement; + this.params = {}; + } + + set _item(data) { + if (this.initialized) return; + this.initialized = true; + this.Class = data.Class; + this._item = data; + + util.extend(this.params, data.params); + + this._loader.loadIntoExistingLocation(data.Class, this._elementRef).then(instance => { + this.instance = instance + data.finishSetup(this, instance) + }) + } + + // /** + // * Push out of this view into another view + // */ + // push(Class: Function, opts = {}) { + // return this.viewport.push(Class, opts) + // } + + // /** + // * Go back + // */ + // pop(opts) { + // return this.viewport.pop(opts) + // } + + // popTo(index, opts) { + // return this.viewport.popTo(index, opts) + // } +} diff --git a/ionic/components/nav/nav.js b/ionic/components/nav/nav.js index 1c0ccc0528..52d20ebe1f 100644 --- a/ionic/components/nav/nav.js +++ b/ionic/components/nav/nav.js @@ -3,239 +3,41 @@ import { View as NgView, For, NgElement, - ComponentInjector } from 'angular2/angular2'; -import {bind} from 'angular2/di'; -import {NavPane} from 'ionic/components/nav-pane/nav-pane'; -import * as util from 'ionic/util'; +import {NavControllerBase} from 'ionic/components/nav/nav-controller'; +import {NavItem} from 'ionic/components/nav/nav-item'; +import {Toolbar} from 'ionic/components/toolbar/toolbar'; -export class TestNav { - constructor(v) { - this.value = v; - } -}; - -console.log(ComponentInjector) @Component({ selector: 'ion-nav', properties: { initial: 'initial' - }, - injectables: [ - bind(TestNav).toFactory((e) => { - debugger; - return e; - }, [Nav]) - ] + } }) @NgView({ template: `
+ + + Test Nonfunctional Toolbar + +
-