From d1e39a2eafb2f6192efd2e41b328d286d6e443a5 Mon Sep 17 00:00:00 2001 From: Adam Bradley Date: Thu, 4 Jun 2015 08:22:50 -0500 Subject: [PATCH] nav/tabs wip --- ionic/components/nav/nav-item.js | 19 +- ionic/components/nav/nav.js | 527 ++----------------------------- ionic/components/tabs/tab.js | 48 ++- ionic/components/tabs/tabs.js | 9 +- ionic/transitions/transition.js | 10 +- 5 files changed, 74 insertions(+), 539 deletions(-) diff --git a/ionic/components/nav/nav-item.js b/ionic/components/nav/nav-item.js index 2fc4d2b078..9038113a6e 100644 --- a/ionic/components/nav/nav-item.js +++ b/ionic/components/nav/nav-item.js @@ -5,6 +5,7 @@ import {bind} from 'angular2/di'; import * as util from 'ionic/util'; import {NavController} from './nav-controller'; import {Nav} from './nav'; +import {NavBase} from './nav-base'; import {TabPane, NavPane, NavPaneSection} from './nav'; @@ -29,22 +30,15 @@ export class NavItem { this.protos[name] = protoViewRef; } - stage() { + stage(callback) { // update if it's possible to go back from this nav item //this.enableBack = !!this.nav.getPrevious(this); - return this.render();; - } - - render() { if (this.instance) { // already compiled this view - return Promise.resolve(); + return callback(); } - let resolve; - let promise = new Promise((res) => { resolve = res; }); - // compile the Component this.nav.compiler.compileInHost(this.Component).then(componentProtoViewRef => { @@ -53,10 +47,11 @@ export class NavItem { let itemStructure = getProtoViewStructure(componentProtoViewRef); // get the appropriate NavPane which this NavItem will fit into - this.nav.getPane(itemStructure).then(navPane => { + this.nav.getPane(itemStructure, navPane => { // create a new injector just for this NavItem let injector = this.nav.injector.resolveAndCreateChild([ + bind(NavBase).toValue(this.navBase), bind(NavController).toValue(this.nav.navCtrl), bind(NavParams).toValue(new NavParams(this.params)), bind(NavItem).toValue(this) @@ -95,12 +90,10 @@ export class NavItem { this.loaded(); - resolve(); + callback(); }); }); - - return promise; } cache() { diff --git a/ionic/components/nav/nav.js b/ionic/components/nav/nav.js index 36ca6e0bc4..ff39a713d2 100644 --- a/ionic/components/nav/nav.js +++ b/ionic/components/nav/nav.js @@ -1,21 +1,16 @@ import {Parent} from 'angular2/src/core/annotations_impl/visibility'; import {Component, Directive, onInit} from 'angular2/src/core/annotations_impl/annotations'; +import {Optional} from 'angular2/src/di/annotations_impl' import {View} from 'angular2/src/core/annotations_impl/view'; import {ElementRef} from 'angular2/src/core/compiler/element_ref'; import {DynamicComponentLoader} from 'angular2/src/core/compiler/dynamic_component_loader'; import {Injector} from 'angular2/di'; import {ViewContainerRef} from 'angular2/src/core/compiler/view_container_ref'; import {Compiler} from 'angular2/angular2'; -import {bind} from 'angular2/di'; -import {NavController} from './nav-controller'; -import {NavItem, NavParams} from './nav-item'; -import {Tabs} from '../tabs/tabs'; -import {nav} from './nav-base'; +import {NavBase} from './nav-base'; import {SwipeHandle} from './swipe-handle'; import {IonicComponent} from '../../config/component'; -import * as util from 'ionic/util'; -import {Transition, ClickBlock} from 'ionic/ionic'; @Component({ @@ -27,480 +22,51 @@ import {Transition, ClickBlock} from 'ionic/ionic'; }) @View({ template: ` - + `, directives: [NavPaneAnchor] }) export class Nav { - constructor(compiler:Compiler, elementRef: ElementRef, loader: DynamicComponentLoader, injector: Injector, viewContainerRef: ViewContainerRef) { - this.compiler = compiler; - this.elementRef = elementRef; - this.loader = loader; - this.injector = injector; - this.viewContainerRef = viewContainerRef; - this.items = []; - this.navCtrl = new NavController(this); - this.sbTransition = null; - this.sbActive = false; + constructor( + @Optional() parentNavBase: NavBase, + compiler:Compiler, + elementRef: ElementRef, + loader: DynamicComponentLoader, + injector: Injector + ) { + this.navBase = new NavBase(parentNavBase, compiler, elementRef, loader, injector); + this.domElement = elementRef.domElement; this.config = Nav.config.invoke(this); - - this.navPanes = {}; - } - - getPane(itemStructure) { - // this gets or creates the NavPane which similar nav items live in - // Nav items with just a navbar/content would all use the same NavPane - // Tabs and view's without a navbar would get a different NavPanes - let resolve; - let promise = new Promise((res) => { resolve = res; }); - - if (this.navPanes[itemStructure.key]) { - // nav pane which the entering component already exists - resolve(this.navPanes[itemStructure.key]); - - } else { - // create a new nav pane - this.navPanes[itemStructure.key] = null; - - // add a NavPane element - // when the NavPane is added, it'll also add its reference to the navPanes object - this.loader.loadNextToExistingLocation(NavPane, this.anchorElementRef, null).then(() => { - - // get the navPane reference by name - let navPane = this.navPanes[itemStructure.key]; - - // get the element inside the NavPane to add sections to - let sectionViewContainerRef = navPane.sectionAnchorElementRef; - let promises = []; - let sectionsToAdd = [] - - // decide which sections should be added to this NavPane, ie: nav bars, tab bars, etc. - // add only the sections it needs - if (itemStructure.navbar) { - sectionsToAdd.push(NavBarSection); - } - - // add the sections which this type of NavPane requires - sectionsToAdd.forEach(SectionClass => { - // as each section is compiled and added to the NavPane - // the section will add a reference to itself in the NavPane's sections object - promises.push( this.loader.loadNextToExistingLocation(SectionClass, sectionViewContainerRef, null) ); - }); - - // wait for all of the sections to resolve - Promise.all(promises).then(() => { - resolve(navPane); - }); - - }); - - } - - return promise; - } - - addPane(navPane) { - for (let np in this.navPanes) { - if (this.navPanes[np] === null) { - this.navPanes[np] = navPane; - } - } } onInit() { - if (this.initial) { - this.push(this.initial); - } + this.navBase.initial(this.initial); } - push(Component, params = {}, opts = {}) { - if (this.isTransitioning()) { - return Promise.reject(); - } - - let resolve; - let promise = new Promise(res => { resolve = res; }); - - // default the direction to "forward" - opts.direction = opts.direction || 'forward'; - - if(opts.animate === false) { - opts.animation = 'none'; - } - - // do not animate if this is the first in the stack - if (!this.items.length) { - opts.animation = 'none'; - } - - // the active item is going to be the leaving one (if one exists) - let leavingItem = this.getActive() || new NavItem(); - leavingItem.shouldDestroy = false; - leavingItem.shouldCache = true; - leavingItem.willCache(); - - // create a new NavStackItem - let enteringItem = new NavItem(this, Component, params); - - // set that this item is staged (it's not ready to be animated in yet) - enteringItem.state = STAGED_STATE; - - // add the item to the stack - this.items.push(enteringItem); - - // start the transition - this.transition(enteringItem, leavingItem, opts).then(() => { - resolve(); - }); - - return promise; + setPaneAnchor(elementRef) { + this.navBase.setPaneAnchor(elementRef); } - pop(opts = {}) { - if (this.isTransitioning() || this.items.length < 1) { - return Promise.reject(); - } - - if(opts.animate === false) { - opts.animation = 'none'; - } - - 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 NavItem(); - leavingItem.shouldDestroy = true; - leavingItem.shouldCache = false; - 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) { - // start the transition - this.transition(enteringItem, leavingItem, opts).then(() => { - // transition completed, destroy the leaving item - resolve(); - }); - - } else { - this.transitionComplete(); - resolve(); - } - - return promise; + addPane(pane) { + this.navBase.addPane(pane); } - transition(enteringItem, leavingItem, opts) { - let resolve; - let promise = new Promise(res => { resolve = res; }); - - opts.isAnimated = (opts.animation !== 'none'); - - this.transitionStart(opts); - - // wait for the new item to complete setup - enteringItem.stage().then(() => { - - enteringItem.shouldDestroy = false; - enteringItem.shouldCache = false; - enteringItem.willEnter(); - leavingItem.willLeave(); - - // set that the leaving item is stage to be leaving - leavingItem.state = STAGED_LEAVING_STATE; - - // set that the new item pushed on the stack is staged to be entering - // setting staged state is important for the transition logic to find the correct item - enteringItem.state = STAGED_ENTERING_STATE; - - // init the transition animation - let transAnimation = Transition.create(this, opts); - - // wait for the items to be fully staged - transAnimation.stage().then(() => { - - // update the state that the items are actively entering/leaving - enteringItem.state = ACTIVELY_ENTERING_STATE; - leavingItem.state = ACTIVELY_LEAVING_STATE; - - // 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.transitionComplete(); - - // resolve that this push has completed - resolve(); - }); - - }); - - }); - - return promise; - } - - swipeBackStart() { - if (this.isTransitioning() || this.items.length < 2) { - return; - } - - this.sbActive = true; - this.sbResolve = null; - - // 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 NavItem(); - 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(); - - // start the transition - this.transitionStart({ isAnimated: true }); - - // wait for the new item to complete setup - enteringItem.stage().then(() => { - - // set that the leaving item is stage to be leaving - leavingItem.state = STAGED_LEAVING_STATE; - - // set that the new item pushed on the stack is staged to be entering - // setting staged state is important for the transition logic to find the correct item - enteringItem.state = STAGED_ENTERING_STATE; - - // init the transition animation - this.sbTransition = Transition.create(this, opts); - this.sbTransition.easing('linear'); - - // wait for the items to be fully staged - this.sbTransition.stage().then(() => { - - // update the state that the items are actively entering/leaving - enteringItem.state = ACTIVELY_ENTERING_STATE; - leavingItem.state = ACTIVELY_LEAVING_STATE; - - let swipeBackPromise = new Promise(res => { this.sbResolve = res; }); - - swipeBackPromise.then((completeSwipeBack) => { - - if (completeSwipeBack) { - // swipe back has completed, update each item's state - enteringItem.state = ACTIVE_STATE; - leavingItem.state = CACHED_STATE; - - enteringItem.didEnter(); - leavingItem.didLeave(); - - } else { - // cancelled the swipe back, return items to original state - leavingItem.state = ACTIVE_STATE; - enteringItem.state = CACHED_STATE; - - leavingItem.willEnter(); - leavingItem.didEnter(); - enteringItem.didLeave(); - - leavingItem.shouldDestroy = false; - enteringItem.shouldDestroy = false; - } - - // all done! - this.transitionComplete(); - - }); - - }); - - }); - - } - - swipeBackEnd(completeSwipeBack, progress, playbackRate) { - // to reverse the animation use a negative playbackRate - if (this.sbTransition && this.sbActive) { - this.sbActive = false; - - if (progress <= 0) { - this.swipeBackProgress(0.0001); - } else if (progress >= 1) { - this.swipeBackProgress(0.9999); - } - - if (!completeSwipeBack) { - playbackRate = playbackRate * -1; - } - - this.sbTransition.playbackRate(playbackRate); - - this.sbTransition.play().then(() => { - this.sbResolve && this.sbResolve(completeSwipeBack); - this.sbTransition && this.sbTransition.dispose(); - this.sbResolve = this.sbTransition = null; - }); - } - } - - swipeBackProgress(progress) { - if (this.sbTransition) { - ClickBlock(true, 4000); - this.sbTransition.progress( Math.min(1, Math.max(0, progress)) ); - } - } - - transitionStart(opts) { - if (opts.isAnimated) { - // block possible clicks during transition - ClickBlock(true, 520); - this.getNavElement().classList.add('transitioning'); - } - } - - transitionComplete() { - - this.items.forEach((item) => { - if (item) { - if (item.shouldDestroy) { - this.remove(item); - item.destroy(); - - } else if (item.state === CACHED_STATE && item.shouldCache) { - item.cache(); - item.shouldCache = false; - } - } - }); - - this.getNavElement().classList.remove('transitioning'); - - // allow clicks again - ClickBlock(false); - } - - isTransitioning() { - let state; - for (let i = 0, ii = this.items.length; i < ii; i++) { - state = this.items[i].state; - if (state === ACTIVELY_ENTERING_STATE || - state === ACTIVELY_LEAVING_STATE || - state === STAGED_ENTERING_STATE || - state === STAGED_LEAVING_STATE) { - return true; - } - } - return false; - } - - clear() { - let pops = []; - for(let item of this.items) { - pops.push(this.pop({ - animate: false - })); - } - return Promise.all(pops); - } - - 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; - } - - getNavElement() { - return this.domElement; - } - - remove(itemOrIndex) { - util.array.remove(this.items, itemOrIndex); - } - - width() { - return this.domElement.offsetWidth; - } - - get swipeBackEnabled() { - // let activeItem = this.nav.getActive(); - // if (activeItem) { - // return activeItem.enableBack; - // } - return false; - } } new IonicComponent(Nav, {}); -// Used to dynamically place new NavPanes next to this -// Which is inside of ion-nav @Directive({ - selector: 'template[nav-pane-anchor]' + selector: 'template[pane-anchor]' }) class NavPaneAnchor { constructor(@Parent() nav: Nav, elementRef: ElementRef) { - nav.anchorElementRef = elementRef; + nav.navBase.setPaneAnchor(elementRef); } } - @Component({selector:'ion-nav-pane'}) @View({ template: ` @@ -523,50 +89,6 @@ export class NavPane { } - -@Component({selector:'ion-tab-pane'}) -@View({ - template: ` - -
- -
- - `, - directives: [TabPaneContentAnchor, TabPaneSectionAnchor] -}) -export class TabPane extends NavPane { - constructor(@Parent() nav: Nav, viewContainerRef: ViewContainerRef) { - super(nav, viewContainerRef); - } -} - - -@Directive({ - selector: 'template[content-anchor]' -}) -class TabPaneContentAnchor { - constructor(@Parent() tabPane: TabPane, viewContainerRef: ViewContainerRef) { - tabPane.contentContainerRef = viewContainerRef; - } -} - - -// Used to dynamically create new sections for a NavPane -// This is simply a reference point to create new sections -// Navbar, toolbar, and tabbar sections would be created next to this -@Directive({ - selector: 'template[section-anchor]' -}) -class TabSectionAnchor { - constructor(@Parent() tabPane: TabPane, elementRef: ElementRef) { - tabPane.sectionAnchorElementRef = elementRef; - } -} - - // Used to dynamically create new sections for a NavPane // This is simply a reference point to create new sections // Navbar, toolbar, and tabbar sections would be created next to this @@ -605,7 +127,7 @@ class NavPaneContentAnchor { `, directives: [NavBarSectionAnchor] }) -class NavBarSection { +export class NavBarSection { constructor(@Parent() navPane: NavPane, viewContainerRef: ViewContainerRef, elementRef: ElementRef) { this.navPane = navPane; navPane.addSection('navbar', this); @@ -622,12 +144,3 @@ class NavBarSectionAnchor { navBarSection.viewContainerRef = viewContainerRef; } } - - -const STAGED_STATE = 'staged'; -const STAGED_ENTERING_STATE = 'staged-enter'; -const STAGED_LEAVING_STATE = 'staged-leave'; -const ACTIVELY_ENTERING_STATE = 'entering'; -const ACTIVELY_LEAVING_STATE = 'leaving'; -const ACTIVE_STATE = 'active'; -const CACHED_STATE = 'cached'; diff --git a/ionic/components/tabs/tab.js b/ionic/components/tabs/tab.js index 3e486c055b..8e7e99e8f2 100644 --- a/ionic/components/tabs/tab.js +++ b/ionic/components/tabs/tab.js @@ -1,8 +1,16 @@ import {Parent} from 'angular2/src/core/annotations_impl/visibility'; import {Directive, Component} from 'angular2/src/core/annotations_impl/annotations'; +import {Optional} from 'angular2/src/di/annotations_impl' import {View} from 'angular2/src/core/annotations_impl/view'; import {ElementRef} from 'angular2/src/core/compiler/element_ref'; +import {Compiler} from 'angular2/angular2'; +import {DynamicComponentLoader} from 'angular2/src/core/compiler/dynamic_component_loader'; +import {Injector} from 'angular2/di'; +import {ViewContainerRef} from 'angular2/src/core/compiler/view_container_ref'; +import {Nav} from '../nav/nav'; +import {NavBase} from '../nav/nav-base'; +import {NavItem} from '../nav/nav-item'; import {Tabs} from './tabs'; import {Content} from '../content/content'; import {IonicComponent} from 'ionic/config/component'; @@ -33,9 +41,21 @@ import {IonicComponent} from 'ionic/config/component'; directives: [TabAnchor] }) export class Tab { - constructor(@Parent() tabs: Tabs, elementRef: ElementRef) { - this.domElement = elementRef.domElement; + constructor( + @Parent() tabs: Tabs, + @Optional() parentNavBase: NavBase, + compiler:Compiler, + elementRef: ElementRef, + loader: DynamicComponentLoader, + injector: Injector + ) { + this.navBase = new NavBase(parentNavBase, compiler, elementRef, loader, injector); + this.domElement = elementRef.domElement; + this.config = Nav.config.invoke(this); + + this.isSelected = false; + this.ariaHidden = true; tabs.addTab(this); this.panelId = 'tab-panel-' + this.id; this.labeledBy = 'tab-button-' + this.id; @@ -44,15 +64,23 @@ export class Tab { } onInit() { - if (this.initial) { - // console.log('Tab onInit') - // this.navBase.push(this.initial); - } + this.navBase.initial(this.initial); } - select(isSelected) { - this.isSelected = isSelected; - this.ariaHidden = !isSelected; + select(shouldSelect) { + if (shouldSelect && !this.isSelected) { + console.log('Select tab', this.id); + + } else if (!shouldSelect && this.isSelected) { + console.log('Deselect tab', this.id); + } + + this.isSelected = shouldSelect; + this.ariaHidden = !shouldSelect; + } + + setPaneAnchor(elementRef) { + this.navBase.setPaneAnchor(elementRef); } } @@ -63,6 +91,6 @@ export class Tab { }) class TabAnchor { constructor(@Parent() tab: Tab, elementRef: ElementRef) { - console.log('TabAnchor constructor', tab.id) + tab.setPaneAnchor(elementRef); } } diff --git a/ionic/components/tabs/tabs.js b/ionic/components/tabs/tabs.js index b94af41132..caea0de063 100644 --- a/ionic/components/tabs/tabs.js +++ b/ionic/components/tabs/tabs.js @@ -56,7 +56,7 @@ export class Tabs { onInit() { if (this.tabs.length > 0) { - //this.selectTab(this.tabs[0]); + this.selectTab(this.tabs[0]); } } @@ -75,13 +75,10 @@ export class Tabs { } if (!tabToSelect || this._selected === tabToSelect) return; - this.tabs.forEach(otherTab => { - if (otherTab !== tabToSelect) { - otherTab.select(false); - } + this.tabs.forEach(tab => { + tab.select( (tab === tabToSelect) ); }); - tabToSelect.select(true); this._selected = tabToSelect; } diff --git a/ionic/transitions/transition.js b/ionic/transitions/transition.js index 0e0b35007d..40663cc856 100644 --- a/ionic/transitions/transition.js +++ b/ionic/transitions/transition.js @@ -1,5 +1,5 @@ import {Animation} from '../animations/animation'; -import {rafPromise} from '../util/dom'; +import {raf, rafPromise} from '../util/dom'; const SHOW_NAVBAR_CSS = 'show-navbar'; const SHOW_VIEW_CSS = 'show-view'; @@ -46,8 +46,12 @@ export class Transition extends Animation { } - stage() { - return rafPromise(); + stage(callback) { + // if no callback was supplied then return a promise + if (!callback) { + return rafPromise(); + } + raf(callback); }