refactor(nav): NavController/Tabs overhaul

Closes #213
This commit is contained in:
Adam Bradley
2015-10-01 15:04:49 -05:00
parent 1225336fab
commit d4a3005524
14 changed files with 419 additions and 470 deletions

View File

@ -31,8 +31,9 @@ $flex-order-view-content: 0;
$flex-order-toolbar-top: -10; $flex-order-toolbar-top: -10;
$flex-order-toolbar-bottom: 10; $flex-order-toolbar-bottom: 10;
$flex-order-tab-bar-top: -30; $flex-order-tab-bar-navbar: -30;
$flex-order-tab-bar-bottom: 30; $flex-order-tab-bar-top: -20;
$flex-order-tab-bar-bottom: 20;
@ -96,7 +97,8 @@ ion-nav {
height: 100%; height: 100%;
} }
ion-pane { ion-pane,
ion-view.pane-view {
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; right: 0;
@ -110,14 +112,28 @@ ion-pane {
flex-direction: column; flex-direction: column;
} }
.nav-item { ion-view {
display: none; display: none;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
flex-direction: column;
background-color: $background-color;
transform: translateZ(0px);
&.show-view { &.show-view {
display: flex; display: flex;
} }
} }
.no-navbar > .navbar-container {
display: none;
}
.navbar-container { .navbar-container {
position: relative; position: relative;
min-height: 4.4rem; min-height: 4.4rem;
@ -131,20 +147,6 @@ ion-pane {
order: $flex-order-view-content; order: $flex-order-view-content;
} }
ion-view {
display: flex;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
flex-direction: column;
background-color: $background-color;
transform: translateZ(0px);
}
[hidden], [hidden],
template, template,
root-anchor { root-anchor {

View File

@ -123,6 +123,6 @@ export class NavbarTemplate {
@Optional() viewCtrl: ViewController, @Optional() viewCtrl: ViewController,
@Optional() templateRef: TemplateRef @Optional() templateRef: TemplateRef
) { ) {
viewCtrl && viewCtrl.addTemplateRef('navbar', templateRef); viewCtrl && viewCtrl.setNavbarTemplateRef(templateRef);
} }
} }

View File

@ -1,53 +0,0 @@
import {Component, View, Directive, Host, ElementRef, forwardRef, Inject} from 'angular2/angular2';
import {ViewContainerRef} from 'angular2/src/core/compiler/view_container_ref';
import {Pane} from './pane';
import {NavController} from './nav-controller';
@Directive({selector: 'template[pane-anchor]'})
export class PaneAnchor {
constructor(
@Host() @Inject(forwardRef(() => Pane)) pane: Pane,
elementRef: ElementRef
) {
pane.sectionAnchorElementRef = elementRef;
}
}
@Directive({selector: 'template[content-anchor]'})
export class PaneContentAnchor {
constructor(
@Host() @Inject(forwardRef(() => Pane)) pane: Pane,
viewContainerRef: ViewContainerRef
) {
pane.contentContainerRef = viewContainerRef;
}
}
@Directive({
selector: 'template[navbar-anchor]'
})
class NavBarAnchor {
constructor(
@Inject(forwardRef(() => NavController)) navCtrl: NavController,
viewContainerRef: ViewContainerRef
) {
navCtrl.navbarViewContainer(viewContainerRef);
}
}
@Component({
selector: 'section',
host: {
'class': 'navbar-container'
}
})
@View({
template: '<template navbar-anchor></template>',
directives: [NavBarAnchor]
})
export class NavBarContainer {}

View File

@ -1,12 +1,12 @@
import {Compiler, ElementRef, Injector, bind, NgZone} from 'angular2/angular2'; import {Component, ComponentRef, Compiler, ElementRef, Injector, bind, NgZone} from 'angular2/angular2';
import {DynamicComponentLoader} from 'angular2/src/core/compiler/dynamic_component_loader'; import {DynamicComponentLoader} from 'angular2/src/core/compiler/dynamic_component_loader';
import {DirectiveBinding} from 'angular2/src/core/compiler/element_injector';
import {AppViewManager} from 'angular2/src/core/compiler/view_manager'; import {AppViewManager} from 'angular2/src/core/compiler/view_manager';
import {Ion} from '../ion'; import {Ion} from '../ion';
import {IonicConfig} from '../../config/config'; import {IonicConfig} from '../../config/config';
import {IonicApp} from '../app/app'; import {IonicApp} from '../app/app';
import {ViewController} from './view-controller'; import {ViewController} from './view-controller';
import {PaneController} from './pane';
import {Transition} from '../../transitions/transition'; import {Transition} from '../../transitions/transition';
import {SwipeBackGesture} from './swipe-back'; import {SwipeBackGesture} from './swipe-back';
import * as util from 'ionic/util'; import * as util from 'ionic/util';
@ -35,7 +35,6 @@ export class NavController extends Ion {
this.zone = zone; this.zone = zone;
this.views = []; this.views = [];
this.panes = new PaneController(this);
this._sbTrans = null; this._sbTrans = null;
this._sbEnabled = config.setting('swipeBackEnabled') || false; this._sbEnabled = config.setting('swipeBackEnabled') || false;
@ -43,7 +42,6 @@ export class NavController extends Ion {
this.id = ++ctrlIds; this.id = ++ctrlIds;
this._ids = -1; this._ids = -1;
this.zIndexes = -1;
// build a new injector for child ViewControllers to use // build a new injector for child ViewControllers to use
this.bindings = Injector.resolve([ this.bindings = Injector.resolve([
@ -336,6 +334,51 @@ export class NavController extends Ion {
} }
compileView(componentType) {
// create a new ion-view annotation
let annotation = new Component({
selector: 'ion-view',
host: {
'[class.pane-view]': '_paneView'
}
});
let ionViewComponentType = DirectiveBinding.createFromType(componentType, annotation);
// create a unique token that works as a cache key
ionViewComponentType.token = 'ionView' + componentType.name;
// compile the Component
return this.compiler.compileInHost(ionViewComponentType);
}
createViewComponetRef(hostProtoViewRef, contentContainerRef, viewCtrlBindings) {
let bindings = this.bindings.concat(Injector.resolve(viewCtrlBindings));
// the same guts as DynamicComponentLoader.loadNextToLocation
var hostViewRef =
contentContainerRef.createHostView(hostProtoViewRef, -1, bindings);
var newLocation = this.viewMngr.getHostElement(hostViewRef);
var newComponent = this.viewMngr.getComponent(newLocation);
var dispose = () => {
var index = contentContainerRef.indexOf(hostViewRef);
if (index !== -1) {
contentContainerRef.remove(index);
}
};
return new ComponentRef(newLocation, newComponent, dispose);
}
getBindings(viewCtrl) {
// create bindings to this ViewController and its NavParams
return this.bindings.concat(Injector.resolve([
bind(ViewController).toValue(viewCtrl),
bind(NavParams).toValue(viewCtrl.params),
]));
}
/** /**
* TODO * TODO
*/ */
@ -667,28 +710,6 @@ export class NavController extends Ion {
return this._anchorER; 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 * TODO
* @param {TODO} view TODO * @param {TODO} view TODO

View File

@ -1,4 +1,5 @@
import {Directive, View, ElementRef, Host, Optional, forwardRef, Injector, NgZone} from 'angular2/angular2'; import {Component, Directive, View, ElementRef, Host, Optional, forwardRef, Inject, Injector, NgZone, Renderer} from 'angular2/angular2';
import {ViewContainerRef} from 'angular2/src/core/compiler/view_container_ref';
import {IonicComponent} from '../../config/decorators'; import {IonicComponent} from '../../config/decorators';
import {NavController} from './nav-controller'; import {NavController} from './nav-controller';
@ -23,18 +24,19 @@ export class Nav extends NavController {
/** /**
* TODO * TODO
* @param {NavController} hostnavCtrl TODO * @param {NavController} hostNavCtrl TODO
* @param {Injector} injector TODO * @param {Injector} injector TODO
* @param {ElementRef} elementRef TODO * @param {ElementRef} elementRef TODO
* @param {NgZone} zone TODO * @param {NgZone} zone TODO
*/ */
constructor( constructor(
@Optional() hostnavCtrl: NavController, @Optional() hostNavCtrl: NavController,
injector: Injector, injector: Injector,
elementRef: ElementRef, elementRef: ElementRef,
zone: NgZone zone: NgZone
) { ) {
super(hostnavCtrl, injector, elementRef, zone); super(hostNavCtrl, injector, elementRef, zone);
this.panes = [];
} }
/** /**
@ -55,8 +57,151 @@ export class Nav extends NavController {
this.isSwipeBackEnabled( isSwipeBackEnabled ); this.isSwipeBackEnabled( isSwipeBackEnabled );
} }
loadContainer(hostProtoViewRef, componentType, viewCtrl, done) {
// this gets or creates the Pane which similar nav items live in
// Nav items with just a navbar/content would all use the same Pane
// Tabs and view's without a navbar would get a different Panes
let structure = this.inspectStructure(hostProtoViewRef);
if (structure.tabs) {
// the component being loaded is an <ion-tabs>
// Tabs is essentially a pane, cuz it has its own navbar and content containers
let contentContainerRef = this.viewMngr.getViewContainer(this.anchorElementRef());
let viewComponetRef = this.createViewComponetRef(hostProtoViewRef, contentContainerRef, this.getBindings(viewCtrl));
viewComponetRef.instance._paneView = true;
viewCtrl.disposals.push(() => {
viewComponetRef.dispose();
});
viewCtrl.onReady().then(() => {
done();
});
} else {
// normal ion-view going into pane
this.getPane(structure, viewCtrl, (pane) => {
// add the content of the view into the pane's content area
let viewComponetRef = this.createViewComponetRef(hostProtoViewRef, pane.contentContainerRef, this.getBindings(viewCtrl));
viewCtrl.disposals.push(() => {
viewComponetRef.dispose();
// remove the pane if there are no view items left
pane.totalViews--;
if (pane.totalViews === 0) {
pane.dispose && pane.dispose();
}
});
// count how many ViewControllers are in this pane
pane.totalViews++;
// a new ComponentRef has been created
// set the ComponentRef's instance to this ViewController
viewCtrl.setInstance(viewComponetRef.instance);
// remember the ElementRef to the content that was just created
viewCtrl.viewElementRef(viewComponetRef.location);
// get the NavController's container for navbars, which is
// the place this NavController will add each ViewController's navbar
let navbarContainerRef = pane.navbarContainerRef;
// get this ViewController's navbar TemplateRef, which may not
// exist if the ViewController's template didn't have an <ion-navbar *navbar>
let navbarTemplateRef = viewCtrl.getNavbarTemplateRef();
// create the navbar view if the pane has a navbar container, and the
// ViewController's instance has a navbar TemplateRef to go to inside of it
if (navbarContainerRef && navbarTemplateRef) {
let navbarView = navbarContainerRef.createEmbeddedView(navbarTemplateRef, -1);
viewCtrl.disposals.push(() => {
let index = navbarContainerRef.indexOf(navbarView);
if (index > -1) {
navbarContainerRef.remove(index);
}
});
}
done();
});
}
}
getPane(structure, viewCtrl, done) {
let pane = this.panes[this.panes.length - 1];
if (pane && pane.navbar === structure.navbar) {
// the last pane's structure is the same as the one
// this ViewController will need, so reuse it
done(pane);
} else {
// create a new nav pane
this.loader.loadNextToLocation(Pane, this.anchorElementRef(), this.getBindings(viewCtrl)).then(componentRef => {
// get the pane reference
pane = this.newPane;
this.newPane = null;
pane.showNavbar(structure.navbar);
pane.dispose = () => {
componentRef.dispose();
this.panes.splice(this.panes.indexOf(pane), 1);
};
this.panes.push(pane);
done(pane);
}, loaderErr => {
console.error(loaderErr);
}).catch(err => {
console.error(err);
});
}
}
addPane(pane) {
this.newPane = pane;
}
inspectStructure(componentProtoViewRef) {
let navbar = false;
let tabs = false;
//let key = '_';
componentProtoViewRef._protoView.elementBinders.forEach(rootElementBinder => {
if (!rootElementBinder.componentDirective || !rootElementBinder.nestedProtoView) return;
rootElementBinder.nestedProtoView.elementBinders.forEach(nestedElementBinder => {
if ( isComponent(nestedElementBinder, 'Tabs') ) {
tabs = true;
}
if (!nestedElementBinder.componentDirective && nestedElementBinder.nestedProtoView) {
nestedElementBinder.nestedProtoView.elementBinders.forEach(templatedElementBinder => {
if ( isComponent(templatedElementBinder, 'Navbar') ) {
navbar = true;
}
});
}
});
});
return {
navbar,
tabs
};
}
} }
function isComponent(elementBinder, id) {
return (elementBinder && elementBinder.componentDirective && elementBinder.componentDirective.metadata.id == id);
}
@Directive({selector: 'template[pane-anchor]'}) @Directive({selector: 'template[pane-anchor]'})
class NavPaneAnchor { class NavPaneAnchor {
@ -64,3 +209,66 @@ class NavPaneAnchor {
nav.anchorElementRef(elementRef); nav.anchorElementRef(elementRef);
} }
} }
@Directive({selector: 'template[navbar-anchor]'})
class NavBarAnchor {
constructor(
@Host() @Inject(forwardRef(() => Pane)) pane: Pane,
viewContainerRef: ViewContainerRef
) {
pane.navbarContainerRef = viewContainerRef;
}
}
@Directive({selector: 'template[content-anchor]'})
class ContentAnchor {
constructor(
@Host() @Inject(forwardRef(() => Pane)) pane: Pane,
viewContainerRef: ViewContainerRef
) {
pane.contentContainerRef = viewContainerRef;
}
}
@Component({
selector: 'ion-pane',
host: {
'class': 'nav'
}
})
@View({
template: '' +
'<section class="navbar-container">' +
'<template navbar-anchor></template>' +
'</section>' +
'<section class="content-container">' +
'<template content-anchor></template>' +
'</section>',
directives: [NavBarAnchor, ContentAnchor]
})
class Pane {
constructor(
nav: Nav,
elementRef: ElementRef,
renderer: Renderer
) {
this.zIndex = (nav.panes.length ? nav.panes[nav.panes.length - 1].zIndex + 1 : 0);
renderer.setElementStyle(elementRef, 'zIndex', this.zIndex);
nav.addPane(this);
this.totalViews = 0;
this.elementRef = elementRef;
this.renderer = renderer;
}
showNavbar(hasNavbar) {
this.hasNavbar = hasNavbar;
if (!hasNavbar) {
this.renderer.setElementClass(this.elementRef, 'no-navbar', true);
}
}
}

View File

@ -1,134 +0,0 @@
import {Component, Directive, View, ElementRef, Inject, forwardRef, Injector, bind} from 'angular2/angular2';
import {Ion} from '../ion';
import {IonicConfig} from '../../config/config';
import {NavController} from './nav-controller';
import {IonicComponent} from '../../config/decorators';
import {PaneAnchor, PaneContentAnchor, NavBarContainer} from './anchors';
/**
* TODO
*/
export class PaneController {
/**
* TODO
* @param {NavController} navCtrl TODO
*/
constructor(navCtrl: NavController) {
this.panes = [];
this.navCtrl = navCtrl;
this.bindings = Injector.resolve([
bind(NavController).toValue(navCtrl)
]);
}
/**
* TODO
* @param {TODO} nav TODO
* @param {Function} nav TODO
*/
get(itemStructure, callback) {
// this gets or creates the Pane which similar nav items live in
// Nav items with just a navbar/content would all use the same Pane
// Tabs and view's without a navbar would get a different Panes
let key = itemStructure.key;
let navCtrl = this.navCtrl;
let pane = this.panes[this.panes.length - 1];
if (pane && pane.key === key) {
// the last pane's structure is the same as the one the item needs to go in
callback(pane);
} else {
// create a new nav pane
navCtrl.loader.loadNextToLocation(Pane, navCtrl.anchorElementRef(), this.bindings).then((componentRef) => {
// get the pane reference
pane = this.newPane;
this.newPane = null;
let sectionAnchorElementRef = pane && pane.sectionAnchorElementRef;
if (!sectionAnchorElementRef) {
return callback();
}
pane.key = key;
pane.dispose = () => {
componentRef.dispose();
this.panes.splice(this.panes.indexOf(pane), 1);
};
this.panes.push(pane);
let promises = [];
let sectionsToAdd = [];
// decide which sections should be added to this Pane, ie: nav bars, footers, etc.
// add only the sections it needs
if (itemStructure.navbar) {
sectionsToAdd.push(NavBarContainer);
}
// add the sections which this type of Pane requires
sectionsToAdd.forEach(SectionClass => {
// 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(
navCtrl.loader.loadNextToLocation(SectionClass, sectionAnchorElementRef)
);
});
// wait for all of the sections to resolve
Promise.all(promises).then(() => {
callback(pane);
}, err => {
console.error(err)
});
}, loaderErr => {
console.error(loaderErr);
}).catch(err => {
console.error(err);
});
}
}
add(pane) {
this.newPane = pane;
}
}
@IonicComponent({
selector: 'ion-pane',
classId: 'nav',
host: {
'[style.z-index]': 'zIndex',
}
})
@View({
template: `
<template pane-anchor></template>
<section class="content-container">
<template content-anchor></template>
</section>
`,
directives: [PaneAnchor, PaneContentAnchor]
})
export class Pane extends Ion {
constructor(
@Inject(forwardRef(() => NavController)) navCtrl: NavController,
elementRef: ElementRef,
ionicConfig: IonicConfig
) {
super(elementRef, ionicConfig);
navCtrl.panes.add(this);
this.totalItems = 0;
this.zIndex = ++navCtrl.zIndexes;
}
}

View File

@ -1,8 +1,6 @@
import {Component, EventEmitter, ElementRef, bind, Injector, ComponentRef} from 'angular2/angular2';
import {DirectiveBinding} from 'angular2/src/core/compiler/element_injector';
import {NavParams} from './nav-controller'; import {NavParams} from './nav-controller';
/** /**
* TODO * TODO
*/ */
@ -16,188 +14,42 @@ export class ViewController {
this.state = 0; this.state = 0;
this.disposals = []; this.disposals = [];
this.protos = {};
this._nbItms = []; this._nbItms = [];
this._promises = [];
this.templateRefs = {}; this.navbarTemplateRef = null;
} }
/** /**
* TODO * @private
* @param {TODO} name TODO
* @param {TODO} protoViewRef TODO
*/ */
addProtoViewRef(name, protoViewRef) { stage(done) {
this.protos[name] = protoViewRef;
}
/**
* TODO
* @param {TODO} name TODO
* @param {TODO} templateRef TODO
*/
addTemplateRef(name, templateRef) {
this.templateRefs[name] = templateRef;
}
/**
* TODO
* @param {Function} callback TODO
* @returns {TODO} TODO
*/
stage(callback) {
let navCtrl = this.navCtrl; let navCtrl = this.navCtrl;
if (this.instance || !navCtrl) { if (this.instance || !navCtrl) {
// already compiled this view // already compiled this view
return callback(); return done();
} }
let annotation = new Component({ // compile the componenet and create a ProtoViewRef
selector: 'ion-view', navCtrl.compileView(this.componentType).then(hostProtoViewRef => {
host: {
'class': 'nav-item'
}
});
let ionViewComponentType = DirectiveBinding.createFromType(this.componentType, annotation); // get the pane the NavController wants to use
// the pane is where all this content will be placed into
navCtrl.loadContainer(hostProtoViewRef, this.componentType, this, () => {
// create a unique token that works as a cache key // this ViewController instance has finished loading
ionViewComponentType.token = 'ionView' + this.componentType.name;
// compile the Component
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 ViewController will fit into
navCtrl.panes.get(itemStructure, pane => {
this.pane = pane;
let bindings = navCtrl.bindings.concat(Injector.resolve([
bind(NavParams).toValue(this.params),
bind(ViewController).toValue(this)
]));
// add the content of the view to the content area
// it will already have the correct context
let contentContainer = pane.contentContainerRef;
// the same guts as DynamicComponentLoader.loadNextToLocation
var hostViewRef =
contentContainer.createHostView(hostProtoViewRef, -1, bindings);
var newLocation = navCtrl.viewMngr.getHostElement(hostViewRef);
var newComponent = navCtrl.viewMngr.getComponent(newLocation);
pane.totalItems++;
var dispose = () => {
var index = contentContainer.indexOf(hostViewRef);
if (index !== -1) {
contentContainer.remove(index);
// remove the pane if there are no view items left
pane.totalItems--;
if (pane.totalItems === 0) {
pane.dispose();
}
}
};
this.disposals.push(dispose);
var viewComponetRef = new ComponentRef(newLocation, newComponent, dispose);
// 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 = navCtrl.navbarViewContainer();
// // get the item's navbar protoview
let navbarTemplateRef = this.templateRefs.navbar;
// add a navbar view if the pane has a navbar container, and the
// item's instance has a navbar protoview to go to inside of it
if (navbarViewContainer && navbarTemplateRef) {
let navbarView = navbarViewContainer.createEmbeddedView(navbarTemplateRef, -1);
this.disposals.push(() => {
let index = navbarViewContainer.indexOf(navbarView);
if (index > -1) {
navbarViewContainer.remove(index);
}
});
}
// this item has finished loading
try { try {
this.loaded(); this.loaded();
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
// fire callback when all child promises have been resolved done();
Promise.all(this._promises).then(() => {
callback();
this._promises = [];
});
}, panesErr => {
console.error(panesErr);
}); });
}, compileInHostErr => {
console.error(compileInHostErr);
}); });
} }
/**
* TODO
* @param {TODO} childPromise TODO
*/
addPromise(childPromise) {
this._promises.push(childPromise);
}
/**
* TODO
* @param {TODO} componentProtoViewRef TODO
*/
inspectStructure(componentProtoViewRef) {
let navbar = false;
let key = '_';
componentProtoViewRef._protoView.elementBinders.forEach(rootElementBinder => {
if (!rootElementBinder.componentDirective || !rootElementBinder.nestedProtoView) return;
rootElementBinder.nestedProtoView.elementBinders.forEach(nestedElementBinder => {
if ( isComponent(nestedElementBinder, 'Tabs') ) {
navbar = true;
}
if (!nestedElementBinder.componentDirective && nestedElementBinder.nestedProtoView) {
nestedElementBinder.nestedProtoView.elementBinders.forEach(templatedElementBinder => {
if ( isComponent(templatedElementBinder, 'Navbar') ) {
navbar = true;
}
});
}
});
});
if (this.navCtrl.childNavbar()) {
navbar = false;
}
if (navbar) key += 'n'
return {
navbar,
key
};
}
/** /**
* TODO * TODO
* @returns {boolean} TODO * @returns {boolean} TODO
@ -247,6 +99,20 @@ export class ViewController {
} }
} }
/**
* @private
*/
setNavbarTemplateRef(templateRef) {
this.navbarTemplateRef = templateRef;
}
/**
* @private
*/
getNavbarTemplateRef() {
return this.navbarTemplateRef;
}
/** /**
* TODO * TODO
* @param {TODO} val TODO * @param {TODO} val TODO
@ -329,7 +195,7 @@ export class ViewController {
* TODO * TODO
* @returns {TODO} TODO * @returns {TODO} TODO
*/ */
navbarBackgroundRef() { navbarBgRef() {
let navbarView = this.navbarView(); let navbarView = this.navbarView();
if (navbarView) { if (navbarView) {
return navbarView.getNativeElement().querySelector('.toolbar-background'); return navbarView.getNativeElement().querySelector('.toolbar-background');
@ -406,7 +272,3 @@ export class ViewController {
} }
} }
function isComponent(elementBinder, id) {
return (elementBinder && elementBinder.componentDirective && elementBinder.componentDirective.metadata.id == id);
}

View File

@ -1,4 +1,5 @@
import {Directive, Component, View, Host, ElementRef, forwardRef, Injector, NgZone} from 'angular2/angular2'; import {Directive, Component, View, Host, ElementRef, forwardRef, Injector, NgZone} from 'angular2/angular2';
import {ViewContainerRef} from 'angular2/src/core/compiler/view_container_ref';
import {NavController} from '../nav/nav-controller'; import {NavController} from '../nav/nav-controller';
import {ViewController} from '../nav/view-controller'; import {ViewController} from '../nav/view-controller';
@ -31,8 +32,8 @@ import {Tabs} from './tabs';
} }
}) })
@View({ @View({
template: '<template pane-anchor></template><ng-content></ng-content>', template: '<template content-anchor></template><ng-content></ng-content>',
directives: [forwardRef(() => TabPaneAnchor)] directives: [forwardRef(() => TabContentAnchor)]
}) })
export class Tab extends NavController { export class Tab extends NavController {
@ -55,12 +56,10 @@ export class Tab extends NavController {
super(tabs, injector, elementRef, zone); super(tabs, injector, elementRef, zone);
this.tabs = tabs; this.tabs = tabs;
this.childNavbar(true);
let viewCtrl = this.viewCtrl = new ViewController(tabs.Host); let viewCtrl = this.viewCtrl = new ViewController(tabs.Host);
viewCtrl.setInstance(this); viewCtrl.setInstance(this);
viewCtrl.viewElementRef(elementRef); viewCtrl.viewElementRef(elementRef);
tabs.addTab(this); this._initTab = tabs.addTab(this);
this.navbarView = viewCtrl.navbarView = () => { this.navbarView = viewCtrl.navbarView = () => {
let activeView = this.getActive(); let activeView = this.getActive();
@ -79,18 +78,19 @@ export class Tab extends NavController {
} }
onInit() { onInit() {
if (this._initialResolve) { console.log('Tab onInit');
this.tabs.select(this).then(() => {
this._initialResolve(); if (this._initTab) {
this._initialResolve = null; this.tabs.select(this);
});
} else {
// TODO: OPTIONAL PRELOAD OTHER TABS!
// setTimeout(() => {
// this.load();
// }, 300);
} }
} }
/**
* TODO
* @param {Function} callback TODO
*/
load(callback) { load(callback) {
if (!this._loaded && this.root) { if (!this._loaded && this.root) {
let opts = { let opts = {
@ -107,15 +107,6 @@ export class Tab extends NavController {
} }
} }
/**
* TODO
* @returns {TODO} TODO
*/
queueInitial() {
// this Tab will be used as the initial one for the first load of Tabs
return new Promise(res => { this._initialResolve = res; });
}
get isSelected() { get isSelected() {
return this.tabs.isActive(this.viewCtrl); return this.tabs.isActive(this.viewCtrl);
} }
@ -124,23 +115,53 @@ export class Tab extends NavController {
return !this.tabs.isActive(this.viewCtrl); return !this.tabs.isActive(this.viewCtrl);
} }
loadContainer(hostProtoViewRef, componentType, viewCtrl, done) {
let viewComponetRef = this.createViewComponetRef(hostProtoViewRef, this.contentContainerRef, this.getBindings(viewCtrl));
viewCtrl.disposals.push(() => {
viewComponetRef.dispose();
});
// a new ComponentRef has been created
// set the ComponentRef's instance to this ViewController
viewCtrl.setInstance(viewComponetRef.instance);
// remember the ElementRef to the content that was just created
viewCtrl.viewElementRef(viewComponetRef.location);
// get the NavController's container for navbars, which is
// the place this NavController will add each ViewController's navbar
let navbarContainerRef = this.tabs.navbarContainerRef;
// get this ViewController's navbar TemplateRef, which may not
// exist if the ViewController's template didn't have an <ion-navbar *navbar>
let navbarTemplateRef = viewCtrl.getNavbarTemplateRef();
// create the navbar view if the pane has a navbar container, and the
// ViewController's instance has a navbar TemplateRef to go to inside of it
if (navbarContainerRef && navbarTemplateRef) {
let navbarView = navbarContainerRef.createEmbeddedView(navbarTemplateRef, -1);
viewCtrl.disposals.push(() => {
let index = navbarContainerRef.indexOf(navbarView);
if (index > -1) {
navbarContainerRef.remove(index);
}
});
}
done();
}
} }
/** @Directive({selector: 'template[content-anchor]'})
* TODO class TabContentAnchor {
*/ constructor(
@Directive({ @Host() tab: Tab,
selector: 'template[pane-anchor]' viewContainerRef: ViewContainerRef
}) ) {
class TabPaneAnchor { tab.contentContainerRef = viewContainerRef;
/**
* TODO
* @param {Tab} tab TODO
* @param {ElementRef} elementRef TODO
*/
constructor(@Host() tab: Tab, elementRef: ElementRef) {
tab.anchorElementRef(elementRef);
} }
} }

View File

@ -42,6 +42,10 @@ ion-tab {
} }
} }
ion-tabs > .navbar-container {
order: $flex-order-tab-bar-navbar;
}
.tab-bar-container { .tab-bar-container {
position: relative; position: relative;
order: $flex-order-tab-bar-bottom; order: $flex-order-tab-bar-bottom;

View File

@ -1,4 +1,5 @@
import {Component, Directive, View, Injector, NgFor, ElementRef, Optional, Host, forwardRef, NgZone} from 'angular2/angular2'; import {Component, Directive, View, Injector, NgFor, ElementRef, Optional, Host, forwardRef, NgZone} from 'angular2/angular2';
import {ViewContainerRef} from 'angular2/src/core/compiler/view_container_ref';
import {Ion} from '../ion'; import {Ion} from '../ion';
import {IonicApp} from '../app/app'; import {IonicApp} from '../app/app';
@ -42,6 +43,9 @@ import * as dom from 'ionic/util/dom';
}) })
@IonicView({ @IonicView({
template: '' + template: '' +
'<section class="navbar-container">' +
'<template navbar-anchor></template>' +
'</section>' +
'<nav class="tab-bar-container">' + '<nav class="tab-bar-container">' +
'<tab-bar role="tablist">' + '<tab-bar role="tablist">' +
'<a *ng-for="#t of tabs" [tab]="t" class="tab-button" role="tab">' + '<a *ng-for="#t of tabs" [tab]="t" class="tab-button" role="tab">' +
@ -54,7 +58,11 @@ import * as dom from 'ionic/util/dom';
'<section class="content-container">' + '<section class="content-container">' +
'<ng-content></ng-content>' + '<ng-content></ng-content>' +
'</section>', '</section>',
directives: [forwardRef(() => TabButton), forwardRef(() => TabHighlight)] directives: [
forwardRef(() => TabButton),
forwardRef(() => TabHighlight),
forwardRef(() => TabNavBarAnchor)
]
}) })
export class Tabs extends NavController { export class Tabs extends NavController {
/** /**
@ -71,6 +79,8 @@ export class Tabs extends NavController {
super(hostNavCtrl, injector, elementRef, zone); super(hostNavCtrl, injector, elementRef, zone);
this.app = app; this.app = app;
this._ready = new Promise(res => { this._isReady = res; });
// Tabs may also be an actual ViewController which was navigated to // Tabs may also be an actual ViewController which was navigated to
// if Tabs is static and not navigated to within a NavController // if Tabs is static and not navigated to within a NavController
// then skip this and don't treat it as it's own ViewController // then skip this and don't treat it as it's own ViewController
@ -93,24 +103,19 @@ export class Tabs extends NavController {
viewCtrl.enableBack = () => { viewCtrl.enableBack = () => {
return false; return false;
}; };
viewCtrl.onReady = () => {
return this._ready;
};
} }
} }
/**
* TODO
* @param {Tab} tab TODO
*/
addTab(tab) { addTab(tab) {
// 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); this.add(tab.viewCtrl);
if (this.length() === 1) { // return true/false if it's the initial tab
// this was the first tab added, queue this one to be loaded and selected return (this.length() === 1);
let promise = tab.queueInitial();
this.viewCtrl && this.viewCtrl.addPromise(promise);
}
} }
/** /**
@ -142,6 +147,7 @@ export class Tabs extends NavController {
this.transition(enteringView, leavingView, opts, () => { this.transition(enteringView, leavingView, opts, () => {
this.highlight && this.highlight.select(tab); this.highlight && this.highlight.select(tab);
this._isReady();
resolve(); resolve();
}); });
}); });
@ -229,3 +235,14 @@ class TabHighlight {
} }
} }
@Directive({selector: 'template[navbar-anchor]'})
class TabNavBarAnchor {
constructor(
@Host() tabs: Tabs,
viewContainerRef: ViewContainerRef
) {
tabs.navbarContainerRef = viewContainerRef;
}
}

View File

@ -8,7 +8,7 @@
<ion-navbar *navbar> <ion-navbar *navbar>
<ion-title>Heart</ion-title> <ion-title>Heart</ion-title>
</ion-navbar> </ion-navbar>
<ion-content class="padding"> <ion-content padding>
<h2>Tab 1</h2> <h2>Tab 1</h2>
</ion-content> </ion-content>
` `
@ -27,7 +27,7 @@ class Tab1 {
<ion-navbar *navbar> <ion-navbar *navbar>
<ion-title>Star</ion-title> <ion-title>Star</ion-title>
</ion-navbar> </ion-navbar>
<ion-content class="padding"> <ion-content padding>
<h2>Tab 2</h2> <h2>Tab 2</h2>
</ion-content> </ion-content>
` `
@ -46,7 +46,7 @@ class Tab2 {
<ion-navbar *navbar> <ion-navbar *navbar>
<ion-title>Stopwatch</ion-title> <ion-title>Stopwatch</ion-title>
</ion-navbar> </ion-navbar>
<ion-content class="padding"> <ion-content padding>
<h2>Tab 3</h2> <h2>Tab 3</h2>
</ion-content> </ion-content>
` `
@ -58,12 +58,13 @@ class Tab3 {
} }
@App({ @App({
template: ` template: `
<ion-tabs> <ion-tabs>
<ion-tab tab-title="Heart" tab-icon="heart" [root]="root1"></ion-tab> <ion-tab tab-title="Heart" tab-icon="heart" [root]="root1"></ion-tab>
<ion-tab tab-title="Star" tab-icon="star" [root]="root2"></ion-tab> <ion-tab tab-title="Star" tab-icon="star" [root]="root2"></ion-tab>
<ion-tab tab-title="Stopwatch" tab-icon="stopwatch" [root]="root3"></ion-tab> <ion-tab tab-title="Stopwatch" tab-icon="stopwatch" [root]="root3"></ion-tab>
</ion-tabs>` </ion-tabs>
`
}) })
export class TabsPage { export class TabsPage {
constructor() { constructor() {

View File

@ -32,7 +32,7 @@ class IOSTransition extends Transition {
.fadeIn() .fadeIn()
.to(TRANSLATEX, CENTER); .to(TRANSLATEX, CENTER);
this.enteringNavbarBackground this.enteringNavbarBg
.to(TRANSLATEX, CENTER); .to(TRANSLATEX, CENTER);
// leaving view moves off screen // leaving view moves off screen
@ -45,7 +45,7 @@ class IOSTransition extends Transition {
.from(TRANSLATEX, CENTER) .from(TRANSLATEX, CENTER)
.from(OPACITY, 1); .from(OPACITY, 1);
this.leavingNavbarBackground this.leavingNavbarBg
.from(TRANSLATEX, CENTER); .from(TRANSLATEX, CENTER);
// set properties depending on direction // set properties depending on direction
@ -59,7 +59,7 @@ class IOSTransition extends Transition {
this.enteringTitle this.enteringTitle
.from(TRANSLATEX, OFF_LEFT); .from(TRANSLATEX, OFF_LEFT);
this.enteringNavbarBackground this.enteringNavbarBg
.from(TRANSLATEX, OFF_LEFT); .from(TRANSLATEX, OFF_LEFT);
this.leavingView this.leavingView
@ -70,7 +70,7 @@ class IOSTransition extends Transition {
.to(TRANSLATEX, '100%') .to(TRANSLATEX, '100%')
.to(OPACITY, 0); .to(OPACITY, 0);
this.leavingNavbarBackground this.leavingNavbarBg
.to(TRANSLATEX, '100%'); .to(TRANSLATEX, '100%');
if (this.leaving.enableBack() && this.viewWidth() > 200) { if (this.leaving.enableBack() && this.viewWidth() > 200) {
@ -88,7 +88,7 @@ class IOSTransition extends Transition {
this.enteringTitle this.enteringTitle
.from(TRANSLATEX, '99.5%'); .from(TRANSLATEX, '99.5%');
this.enteringNavbarBackground this.enteringNavbarBg
.from(TRANSLATEX, '99.5%'); .from(TRANSLATEX, '99.5%');
this.leavingView this.leavingView
@ -99,7 +99,7 @@ class IOSTransition extends Transition {
.to(TRANSLATEX, OFF_LEFT) .to(TRANSLATEX, OFF_LEFT)
.to(OPACITY, 0); .to(OPACITY, 0);
this.leavingNavbarBackground this.leavingNavbarBg
.to(TRANSLATEX, OFF_LEFT); .to(TRANSLATEX, OFF_LEFT);
if (this.entering.enableBack() && this.viewWidth() > 200) { if (this.entering.enableBack() && this.viewWidth() > 200) {

View File

@ -48,8 +48,8 @@ export class Transition extends Animation {
this.enteringNavbarItems = new Animation(enteringView.navbarItemRefs()); this.enteringNavbarItems = new Animation(enteringView.navbarItemRefs());
enteringNavbar.add(this.enteringNavbarItems.fadeIn()); enteringNavbar.add(this.enteringNavbarItems.fadeIn());
this.enteringNavbarBackground = new Animation(enteringView.navbarBackgroundRef()); this.enteringNavbarBg = new Animation(enteringView.navbarBgRef());
enteringNavbar.add(this.enteringNavbarBackground); enteringNavbar.add(this.enteringNavbarBg);
} }
@ -73,8 +73,8 @@ export class Transition extends Animation {
this.leavingNavbarItems = new Animation(leavingView.navbarItemRefs()); this.leavingNavbarItems = new Animation(leavingView.navbarItemRefs());
leavingNavbar.add(this.leavingNavbarItems.fadeOut()); leavingNavbar.add(this.leavingNavbarItems.fadeOut());
this.leavingNavbarBackground = new Animation(leavingView.navbarBackgroundRef()); this.leavingNavbarBg = new Animation(leavingView.navbarBgRef());
leavingNavbar.add(this.leavingNavbarBackground); leavingNavbar.add(this.leavingNavbarBg);
this.add(this.leavingView, leavingNavbar); this.add(this.leavingView, leavingNavbar);
} }

View File

@ -62,7 +62,7 @@
"rx": "rx" "rx": "rx"
} }
}) })
System.import("index"); System.import("index").then(function(m) {}, console.error.bind(console));;
</script> </script>
</body> </body>