refactor(NavController): restructuring and perf improvements

This commit is contained in:
Adam Bradley
2015-10-29 14:37:49 -05:00
parent cd4f683ce5
commit 36f82b2473
45 changed files with 592 additions and 887 deletions

View File

@ -251,7 +251,7 @@ export class Animation {
});
}
if (self._duration > 64) {
if (self._duration > 32) {
// begin each animation when everything is rendered in their starting point
// give the browser some time to render everything in place before starting
setTimeout(kickoff, this._opts.renderDelay);
@ -550,7 +550,7 @@ class Animate {
this.toEffect = parseEffect(toEffect);
this.shouldAnimate = (duration > 64);
this.shouldAnimate = (duration > 32);
if (!this.shouldAnimate) {
return inlineStyle(ele, this.toEffect);

View File

@ -82,79 +82,123 @@ body {
background-color: $background-color;
}
ion-app {
ion-app,
ion-nav,
ion-tabs {
display: flex;
flex-direction: column;
overflow: hidden;
height: 100%;
max-width: 100%;
max-height: 100%;
margin: 0;
padding: 0;
}
ion-nav {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
ion-pane,
ion-view.pane-view {
ion-navbar-section {
display: block;
width: 100%;
min-height: 50px;
}
ion-content-section {
display: block;
flex: 1;
position: relative;
width: 100%;
height: 100%;
}
ion-page {
display: flex;
flex-direction: column;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
transform: translateZ(0);
}
ion-content {
position: relative;
display: block;
width: 100%;
height: 100%;
flex: 1;
background-color: $background-color;
}
scroll-content {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
display: block;
overflow-y: scroll;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
will-change: scroll-position;
}
ion-view.pane-view {
background: transparent;
}
ion-view {
display: none;
ion-tab-bar {
display: block;
position: absolute;
top: 0;
left: 0;
width: 100%;
min-height: 50px;
}
ion-tab-section {
display: block;
position: relative;
top: 0;
left: 0;
width: 100%;
height: 100%;
flex-direction: column;
background-color: $background-color;
transform: translateZ(0px);
&.show-view {
display: flex;
}
overflow: hidden;
}
[no-navbar] > ion-navbar-section {
display: none;
ion-page.tab-subpage {
position: fixed;
z-index: 10;
}
ion-navbar-section {
position: relative;
min-height: 4.4rem;
z-index: $z-index-navbar-section;
ion-navbar {
display: block;
position: absolute;
top: 0;
left: 0;
width: 100%;
min-height: 50px;
z-index: 10;
}
ion-content-section {
position: relative;
z-index: $z-index-content-section;
flex: 1;
order: $flex-order-view-content;
ion-navbar-section ion-navbar.toolbar {
position: absolute;
}
[hidden],
template,
root-anchor {
display: none !important;
ion-toolbar {
display: block;
width: 100%;
height: 50px;
}
ion-toolbar[position=bottom] {
bottom: 0;
z-index: 10;
}
ion-scrollbar {
position: fixed;
top: 0;
right: 0;
z-index: 10;
width: 10px;
}
.hide-navtive-scrollbar ion-content {
padding-right: 20px;
width: calc(100% + 20px);
}

View File

@ -29,7 +29,6 @@ button,
align-items: center;
justify-content: center;
will-change: background-color, opacity;
transition: background-color, opacity 100ms linear;
margin: $button-margin;

View File

@ -1,65 +0,0 @@
// Content
// --------------------------------------------------
$content-background-color: $background-color !default;
ion-content {
position: relative;
display: block;
flex: 1;
background-color: $content-background-color;
}
scroll-content {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: block;
overflow-y: scroll; // has to be scroll for momentum scrolling, not auto
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
will-change: scroll-position;
}
// Content Padding
// --------------------------------------------------
$content-padding: 16px !default;
[padding],
[padding] > scroll-content {
padding: $content-padding;
}
[padding-top] {
padding-top: $content-padding;
}
[padding-right] {
padding-right: $content-padding;
}
[padding-bottom] {
padding-bottom: $content-padding;
}
[padding-left] {
padding-left: $content-padding;
}
[padding-vertical] {
padding-top: $content-padding;
padding-bottom: $content-padding;
}
[padding-horizontal] {
padding-right: $content-padding;
padding-left: $content-padding;
}

View File

@ -42,6 +42,7 @@ export class Content extends Ion {
if (viewCtrl) {
viewCtrl.setContent(this);
viewCtrl.setContentRef(elementRef);
}
}

View File

@ -138,6 +138,11 @@ export class Menu extends Ion {
this._type = new menuTypeCls(this);
this.type = type;
if (this.config.get('animate') === false) {
this._type.open.duration(33);
this._type.close.duration(33);
}
}
/**

View File

@ -10,22 +10,3 @@ $modal-inset-mode-right: 20% !default;
$modal-inset-mode-bottom: 20% !default;
$modal-inset-mode-left: 20% !default;
$modal-inset-mode-min-height: 240px !default;
ion-modal {
position: absolute;
top: 0;
z-index: $z-index-overlay;
overflow: hidden;
min-height: 100%;
width: 100%;
background-color: $modal-background-color;
display: flex;
flex-direction: column;
transform: translate3d(0px, 100%, 0px);
&.show-overlay {
transform: translate3d(0px, 0px, 0px);
}
}

View File

@ -3,7 +3,6 @@ import {Injectable} from 'angular2/angular2';
import {OverlayController} from '../overlay/overlay-controller';
import {Config} from '../../config/config';
import {Animation} from '../../animations/animation';
import {makeComponent} from '../../config/decorators';
import * as util from 'ionic/util';
/**
@ -46,11 +45,7 @@ export class Modal {
* @returns {TODO} TODO
*/
open(componentType: Type, opts={}) {
let modalComponent = makeComponent(componentType, {
selector: 'ion-modal'
});
return this.ctrl.open(OVERLAY_TYPE, modalComponent, util.extend(this._defaults, opts));
return this.ctrl.open(OVERLAY_TYPE, componentType, util.extend(this._defaults, opts));
}
/**

View File

@ -8,3 +8,7 @@ $navbar-ios-height: 4.4rem !default;
ion-navbar-section {
min-height: $navbar-ios-height;
}
.back-button {
transform: translateZ(0px);
}

View File

@ -4,17 +4,11 @@
ion-navbar.toolbar {
position: absolute;
display: none;
&.show-navbar {
display: flex;
}
display: flex;
}
.back-button {
order: map-get($toolbar-order, backButton);
transform: translateZ(0px);
order: map-get($toolbar-order, backButton);
display: none;
&.show-back-button {

View File

@ -1,4 +1,4 @@
import {Component, Directive, Optional, ElementRef, Renderer, TemplateRef, forwardRef, Inject} from 'angular2/angular2';
import {Component, Directive, Optional, ElementRef, Renderer, TemplateRef, forwardRef, Inject, ViewContainerRef} from 'angular2/angular2';
import {Ion} from '../ion';
import {Icon} from '../icon/icon';
@ -65,6 +65,9 @@ class BackButtonText extends Ion {
'<ng-content select="ion-nav-items[secondary]"></ng-content>' +
'</div>' +
'<div class="toolbar-background"></div>',
host: {
'[hidden]': '_hidden'
},
directives: [BackButton, BackButtonText, Icon]
})
export class Navbar extends ToolbarBase {
@ -105,6 +108,10 @@ export class Navbar extends ToolbarBase {
this.app.setTitle(this.getTitleText());
}
setHidden(isHidden) {
this._hidden = isHidden
}
}
@ -118,9 +125,13 @@ export class Navbar extends ToolbarBase {
})
export class NavbarTemplate {
constructor(
@Optional() viewCtrl: ViewController,
@Optional() templateRef: TemplateRef
viewContainerRef: ViewContainerRef,
templateRef: TemplateRef,
@Optional() viewCtrl: ViewController
) {
viewCtrl && viewCtrl.setNavbarTemplateRef(templateRef);
if (viewCtrl) {
viewCtrl.setNavbarTemplateRef(templateRef);
viewCtrl.setNavbarViewRef(viewContainerRef);
}
}
}

View File

@ -1,7 +1,6 @@
import {Compiler, ElementRef, Injector, provide, NgZone, DynamicComponentLoader, AppViewManager, Renderer} from 'angular2/angular2';
import {Ion} from '../ion';
import {makeComponent} from '../../config/decorators';
import {IonicApp} from '../app/app';
import {Config} from '../../config/config';
import {ViewController} from './view-controller';
@ -445,6 +444,9 @@ export class NavController extends Ion {
if (!opts.animation) {
opts.animation = this.config.get('viewTransition');
}
if (this.config.get('animate') === false) {
opts.animate = false;
}
// wait for the new view to complete setup
enteringView.stage(() => {
@ -508,34 +510,51 @@ export class NavController extends Ion {
}
/**
* @private
* TODO
*/
compileView(componentType) {
// create a new ion-view annotation
let viewComponentType = makeComponent(componentType, {
selector: 'ion-view',
host: {
'[class.pane-view]': '_paneView'
}
});
// compile the Component
return this._compiler.compileInHost(viewComponentType);
}
/**
* @private
* TODO
*/
loadNextToAnchor(type, location, viewCtrl) {
loadPage(viewCtrl, navbarContainerRef, done) {
let providers = this.providers.concat(Injector.resolve([
provide(ViewController, {useValue: viewCtrl}),
provide(NavParams, {useValue: viewCtrl.params})
]));
return this._loader.loadNextToLocation(type, location, providers);
this._loader.loadIntoLocation(viewCtrl.componentType, this.elementRef, 'contents', providers).then(componentRef => {
viewCtrl.addDestroy(() => {
componentRef.dispose();
});
// a new ComponentRef has been created
// set the ComponentRef's instance to this ViewController
viewCtrl.setInstance(componentRef.instance);
// remember the ElementRef to the ion-page elementRef that was just created
viewCtrl.setPageRef(componentRef.location);
if (!navbarContainerRef) {
navbarContainerRef = viewCtrl.getNavbarViewRef();
}
let navbarTemplateRef = viewCtrl.getNavbarTemplateRef();
if (navbarContainerRef && navbarTemplateRef) {
let navbarView = navbarContainerRef.createEmbeddedView(navbarTemplateRef);
viewCtrl.addDestroy(() => {
let index = navbarContainerRef.indexOf(navbarView);
if (index > -1) {
navbarContainerRef.remove(index);
}
});
}
if (this._views.length === 1) {
this._zone.runOutsideAngular(() => {
setTimeout(() => {
this.renderer.setElementClass(this.elementRef, 'has-views', true);
}, 200);
});
}
done(viewCtrl);
});
}
/**
@ -543,6 +562,7 @@ export class NavController extends Ion {
* TODO
*/
swipeBackStart() {
return;
if (!this.app.isEnabled() || !this.canSwipeBack()) {
return;
}
@ -594,6 +614,7 @@ export class NavController extends Ion {
* @param {TODO} progress TODO
*/
swipeBackProgress(value) {
return;
if (this._sbTrans) {
// continue to disable the app while actively dragging
this.app.setEnabled(false, 4000);
@ -610,6 +631,7 @@ export class NavController extends Ion {
* @param {number} rate How fast it closes
*/
swipeBackEnd(completeSwipeBack, rate) {
return;
if (!this._sbTrans) return;
// disables the app during the transition
@ -672,6 +694,7 @@ export class NavController extends Ion {
* TODO
*/
_sbComplete() {
return;
if (this.canSwipeBack()) {
// it is possible to swipe back
@ -789,16 +812,6 @@ export class NavController extends Ion {
});
}
addHasViews() {
if (this._views.length === 1) {
this._zone.runOutsideAngular(() => {
setTimeout(() => {
this.renderer.setElementClass(this.elementRef, 'has-views', true);
}, 200);
});
}
}
/**
* TODO
* @param {TODO} nbContainer TODO

View File

@ -130,18 +130,10 @@ import {NavController} from './nav-controller';
defaultInputs: {
'swipeBackEnabled': true
},
template: '<template pane-anchor></template>',
directives: [forwardRef(() => NavPaneAnchor)]
template: '<template #contents></template>'
})
export class Nav extends NavController {
/**
* TODO
* @param {NavController} hostNavCtrl TODO
* @param {Injector} injector TODO
* @param {ElementRef} elementRef TODO
* @param {NgZone} zone TODO
*/
constructor(
@Optional() hostNavCtrl: NavController,
app: IonicApp,
@ -154,7 +146,6 @@ export class Nav extends NavController {
renderer: Renderer
) {
super(hostNavCtrl, app, config, elementRef, compiler, loader, viewManager, zone, renderer);
this.panes = [];
}
/**
@ -171,263 +162,7 @@ export class Nav extends NavController {
}
// default the swipe back to be enabled
let isSwipeBackEnabled = (this.swipeBackEnabled || '').toString() !== 'false';
this.isSwipeBackEnabled( isSwipeBackEnabled );
}
/**
* @private
* TODO
* @param {TODO} componentType TODO
* @param {TODO} hostProtoViewRef TODO
* @param {TODO} viewCtrl TODO
* @param {Function} done TODO
* @return {TODO} TODO
*/
loadContainer(componentType, hostProtoViewRef, 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.getStructure(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
this.loadNextToAnchor(componentType, this.anchorElementRef(), viewCtrl).then(componentRef => {
componentRef.instance._paneView = true;
viewCtrl.disposals.push(() => {
componentRef.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
this.loadNextToAnchor(componentType, pane.contentAnchorRef, viewCtrl).then(componentRef => {
viewCtrl.disposals.push(() => {
componentRef.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(componentRef.instance);
// remember the ElementRef to the content that was just created
viewCtrl.setContentRef(componentRef.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);
}
});
}
this.addHasViews();
done();
});
});
}
}
/**
* @private
* TODO
* @param {TODO} structure TODO
* @param {TODO} viewCtrl TODO
* @param {Function} done TODO
* @return {TODO} TODO
*/
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.bindings).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);
});
}
}
/**
* @private
* TODO
* @param {TODO} pane TODO
* @return {TODO} TODO
*/
addPane(pane) {
this.newPane = pane;
}
/**
* @private
* TODO
* @param {TODO} componentProtoViewRef TODO
* @return {TODO} TODO
*/
getStructure(componentProtoViewRef) {
let templateCmds = componentProtoViewRef._protoView.templateCmds;
let compiledTemplateData, directives;
let i, ii, j, jj, k, kk;
for (i = 0, ii = templateCmds.length; i < ii; i++) {
if (templateCmds[i].template) {
compiledTemplateData = templateCmds[i].template.getData(templateCmds[i].templateId);
if (compiledTemplateData) {
for (j = 0, jj = compiledTemplateData.commands.length; j < jj; j++) {
directives = compiledTemplateData.commands[j].directives;
if (directives && (kk = directives.length)) {
for (k = 0; k < kk; k++) {
if (directives[k].name == 'NavbarTemplate') {
return { navbar: true };
}
if (directives[k].name == 'Tabs') {
return { tabs: true };
}
}
}
}
}
}
}
return {};
}
}
/**
* @private
*/
@Directive({selector: 'template[pane-anchor]'})
class NavPaneAnchor {
constructor(@Host() nav: Nav, elementRef: ElementRef) {
nav.anchorElementRef(elementRef);
}
}
/**
* @private
*/
@Directive({selector: 'template[navbar-anchor]'})
class NavBarAnchor {
constructor(
@Host() @Inject(forwardRef(() => Pane)) pane: Pane,
viewContainerRef: ViewContainerRef
) {
pane.navbarContainerRef = viewContainerRef;
}
}
/**
* @private
*/
@Directive({selector: 'template[content-anchor]'})
class ContentAnchor {
constructor(
@Host() @Inject(forwardRef(() => Pane)) pane: Pane,
elementRef: ElementRef
) {
pane.contentAnchorRef = elementRef;
}
}
/**
* @private
*/
@Component({
selector: 'ion-pane',
template:
'<ion-navbar-section>' +
'<template navbar-anchor></template>' +
'</ion-navbar-section>' +
'<ion-content-section>' +
'<template content-anchor></template>' +
'</ion-content-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.navbar = hasNavbar;
this.renderer.setElementAttribute(this.elementRef, 'no-navbar', hasNavbar ? null : '' );
this.isSwipeBackEnabled( (this.swipeBackEnabled || '').toString() !== 'false' );
}
}

View File

@ -1,16 +1,16 @@
it('should go from 1 to 2', function() {
element(by.css('#from1To2')).click();
element(by.css('.e2eFrom1To2')).click();
});
it('should go from 2 to 3', function() {
element(by.css('#from2To3')).click();
element(by.css('.e2eFrom2To3')).click();
});
it('should go from 3 to 2', function() {
element(by.css('#from3To2')).click();
element(by.css('.e2eFrom3To2')).click();
});
it('should go from 2 to 1', function() {
element(by.css('#from2To1')).click();
element(by.css('.e2eFrom2To1')).click();
});

View File

@ -6,7 +6,7 @@ import {NavParams, NavController} from 'ionic/ionic';
@Page({
template: `
<ion-navbar *navbar>
<ion-title><ion-segment><ion-segment-button>Friends</ion-segment-button><ion-segment-button>Enemies</ion-segment-button></ion-segment></ion-title>
<ion-title>{{title}}</ion-title>
<ion-nav-items primary>
<button><icon star></icon></button>
</ion-nav-items>
@ -16,14 +16,15 @@ import {NavParams, NavController} from 'ionic/ionic';
</ion-navbar>
<ion-content padding>
<p>{{title}}</p>
<p><button id="from1To2" primary (click)="push()">Push (Go to 2nd)</button></p>
<p><button [nav-push]="[pushPage, {id: 42}]">Push w/ [nav-push] array (Go to 2nd)</button></p>
<p><button [nav-push]="pushPage" [nav-params]="{id:40}">Push w/ [nav-push] and [nav-params] (Go to 2nd)</button></p>
<p><button [nav-push]="[\'FirstPage\', {id: 22}]">Push w/ [nav-push] array and string view name (Go to 2nd)</button></p>
<p><button nav-push="FirstPage" [nav-params]="{id: 23}">Push w/ nav-push and [nav-params] (Go to 2nd)</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>
<p><button class="e2eFrom1To2" (click)="pushFullPage()">Push to FullPage</button></p>
<p><button (click)="pushPrimaryHeaderPage()">Push to PrimaryHeaderPage</button></p>
<p><button (click)="pushAnother()">Push to AnotherPage</button></p>
<p><button [nav-push]="[pushPage, {id: 42}]">Push FullPage w/ [nav-push] array</button></p>
<p><button [nav-push]="pushPage" [nav-params]="{id:40}">Push w/ [nav-push] and [nav-params]</button></p>
<p><button [nav-push]="[\'FirstPage\', {id: 22}]">Push w/ [nav-push] array and string view name</button></p>
<p><button nav-push="FirstPage" [nav-params]="{id: 23}">Push w/ nav-push and [nav-params]</button></p>
<p><button (click)="setViews()">setViews() (Go to PrimaryHeaderPage)</button></p>
<p><button (click)="nav.pop()">Pop</button></p>
<f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f>
</ion-content>`
})
@ -36,19 +37,27 @@ class FirstPage {
this.nav = nav;
this.title = 'First Page';
this.pushPage = SecondPage;
this.pushPage = FullPage;
}
setViews() {
let items = [
ThirdPage
PrimaryHeaderPage
];
this.nav.setViews(items);
}
push() {
this.nav.push(SecondPage, { id: 8675309, myData: [1,2,3,4] } );
pushPrimaryHeaderPage() {
this.nav.push(PrimaryHeaderPage);
}
pushFullPage() {
this.nav.push(FullPage, { id: 8675309, myData: [1,2,3,4] } );
}
pushAnother() {
this.nav.push(AnotherPage);
}
}
@ -56,42 +65,45 @@ class FirstPage {
@Page({
template: `
<ion-content padding>
<h1>Second page</h1>
<h1>Full page</h1>
<p>This page does not have a nav bar!</p>
<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)="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>
<p><button (click)="nav.pop()">Pop</button></p>
<p><button class="e2eFrom2To3" (click)="pushPrimaryHeaderPage()">Push to PrimaryHeaderPage</button></p>
<p><button (click)="pushAnother()">Push to AnotherPage</button></p>
<p><button (click)="pushFirstPage()">Push to FirstPage</button></p>
<p><button class="e2eFrom2To1" nav-pop>Pop with NavPop (Go back to 1st)</button></p>
<p><button (click)="setViews()">setViews() (Go to PrimaryHeaderPage, FirstPage 1st in history)</button></p>
</ion-content>
`
})
class SecondPage {
class FullPage {
constructor(
nav: NavController,
params: NavParams
) {
this.nav = nav;
this.params = params;
console.log('Second page params:', params);
}
setViews() {
let items = [
FirstPage,
ThirdPage
PrimaryHeaderPage
];
this.nav.setViews(items);
}
pop() {
this.nav.pop();
pushPrimaryHeaderPage() {
this.nav.push(PrimaryHeaderPage);
}
push() {
this.nav.push(ThirdPage);
pushAnother() {
this.nav.push(AnotherPage);
}
pushFirstPage() {
this.nav.push(FirstPage);
}
}
@ -99,29 +111,32 @@ class SecondPage {
@Page({
template: `
<ion-navbar *navbar><ion-title>Third Page Header</ion-title></ion-navbar>
<ion-navbar *navbar primary>
<ion-title>Primary Color Page Header</ion-title>
</ion-navbar>
<ion-content padding>
<p><button (click)="push()">Push (Go to 4th)</button></p>
<p><button id="from3To2" (click)="pop()">Pop (Go back to 2nd)</button></p>
<p><button class="e2eFrom3To2" (click)="nav.pop()">Pop</button></p>
<p><button (click)="pushAnother()">Push to AnotherPage</button></p>
<p><button (click)="pushFullPage()">Push to FullPage</button></p>
<p><button id="insert" (click)="insert()">Insert first page into history before this</button></p>
<p><button id="remove" (click)="removeSecond()">Remove second page in history</button></p>
<div class="yellow"><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>
`
})
class ThirdPage {
class PrimaryHeaderPage {
constructor(
nav: NavController
) {
this.nav = nav
}
push() {
this.nav.push(FourthPage);
pushAnother() {
this.nav.push(AnotherPage);
}
pop() {
this.nav.pop()
pushFullPage() {
this.nav.push(FullPage, { id: 8675309, myData: [1,2,3,4] } );
}
insert() {
@ -137,28 +152,40 @@ class ThirdPage {
@Page({
template: `
<ion-navbar *navbar><ion-title>Fourth Page Header</ion-title></ion-navbar>
<ion-navbar *navbar>
<ion-title>Another Page Header</ion-title>
</ion-navbar>
<ion-content padding>
<p>
<button (click)="nav.pop()">Pop (Go back to 3rd)</button>
</p>
<p><button (click)="nav.pop()">Pop</button></p>
<p><button (click)="pushFullPage()">Push to FullPage</button></p>
<p><button (click)="pushPrimaryHeaderPage()">Push to PrimaryHeaderPage</button></p>
<p><button (click)="pushFirstPage()">Push to FirstPage</button></p>
</ion-content>
`
})
class FourthPage {
class AnotherPage {
constructor(
nav: NavController
) {
this.nav = nav
}
pushFullPage() {
this.nav.push(FullPage);
}
pushPrimaryHeaderPage() {
this.nav.push(PrimaryHeaderPage);
}
pushFirstPage() {
this.nav.push(FirstPage);
}
}
@App({
template: `
<ion-nav [root]="root"></ion-nav>
`,
views: [FirstPage, SecondPage, ThirdPage]
template: `<ion-nav [root]="root"></ion-nav>`
})
class E2EApp {
constructor() {

View File

@ -12,15 +12,7 @@ export class ViewController {
this.params = new NavParams(params);
this.instance = null;
this.state = 0;
this.disposals = [];
}
setContent(content) {
this._content = content;
}
getContent() {
return this._content;
this._destroys = [];
}
/**
@ -34,25 +26,18 @@ export class ViewController {
return done();
}
// compile the component and create a ProtoViewRef
navCtrl.compileView(this.componentType).then(hostProtoViewRef => {
// get the pane the NavController wants to use
// the pane is where all this content will be placed into
navCtrl.loadPage(this, null, () => {
if (this.shouldDestroy) return done();
// get the pane the NavController wants to use
// the pane is where all this content will be placed into
navCtrl.loadContainer(this.componentType, hostProtoViewRef, this, () => {
// this ViewController instance has finished loading
try {
this.loaded();
} catch (e) {
console.error(e);
}
done();
});
// this ViewController instance has finished loading
try {
this.loaded();
} catch (e) {
console.error(e);
}
done();
});
}
@ -87,55 +72,66 @@ export class ViewController {
return this.index === 0;
}
addDestroy(destroyFn) {
this._destroys.push(destroyFn);
}
/**
* TODO
*/
destroy() {
for (let i = 0; i < this.disposals.length; i++) {
this.disposals[i]();
for (let i = 0; i < this._destroys.length; i++) {
this._destroys[i]();
}
this._destroys = [];
}
/**
* @private
*/
setNavbarTemplateRef(templateRef) {
this._nbTmpRef = templateRef;
}
/**
* @private
*/
getNavbarTemplateRef() {
return this._nbTmpRef;
}
/**
* TODO
* @returns {TODO} TODO
*/
setContentRef(contentElementRef) {
this._cntRef = contentElementRef;
getNavbarViewRef() {
return this._nbVwRef;
}
setNavbarViewRef(viewContainerRef) {
this._nbVwRef = viewContainerRef;
}
setPageRef(elementRef) {
this._pgRef = elementRef;
}
pageRef() {
return this._pgRef;
}
setContentRef(elementRef) {
this._cntRef = elementRef;
}
/**
* TODO
* @returns {TODO} TODO
*/
contentRef() {
return this._cntRef;
}
setNavbar(navbarView) {
this._nbVw = navbarView;
setContent(directive) {
this._cntDir = directive;
}
getContent() {
return this._cntDir;
}
setNavbar(directive) {
this._nbDir = directive;
}
/**
* TODO
* @returns {TODO} TODO
*/
getNavbar() {
return this._nbVw;
return this._nbDir;
}
/**
@ -263,18 +259,8 @@ export class ViewController {
}
domCache(isActiveView, isPreviousView) {
let renderInDom = (isActiveView || isPreviousView);
let contentRef = this.contentRef();
if (contentRef) {
// the active view, and the previous view should have the 'show-view' css class
// all others, like a cached page 2 back, should now have 'show-view' so it's not rendered
contentRef.nativeElement.classList[renderInDom ? 'add' : 'remove' ]('show-view');
}
let navbarRef = this.getNavbar();
if (navbarRef) {
navbarRef.elementRef.nativeElement.classList[renderInDom ? 'add' : 'remove' ]('show-navbar');
if (this.instance) {
this.instance._hidden = (!isActiveView && !isPreviousView);
}
}

View File

@ -1,6 +1,7 @@
import {Component, NgZone, Injectable, Renderer} from 'angular2/angular2';
import {IonicApp} from '../app/app';
import {Config} from '../../config/config';
import {Animation} from '../../animations/animation';
import * as util from 'ionic/util';
@ -8,8 +9,9 @@ import * as util from 'ionic/util';
@Injectable()
export class OverlayController {
constructor(app: IonicApp, zone: NgZone, renderer: Renderer) {
constructor(app: IonicApp, config: Config, zone: NgZone, renderer: Renderer) {
this.app = app;
this.config = config;
this.zone = zone;
this.renderer = renderer;
this.refs = [];
@ -54,6 +56,9 @@ export class OverlayController {
instance.onPageWillEnter && instance.onPageWillEnter();
let animation = Animation.create(ref.location.nativeElement, opts.enterAnimation);
if (this.config.get('animate') === false) {
animation.duration(0);
}
animation.before.addClass('show-overlay');
this.app.setEnabled(false, animation.duration());
@ -95,6 +100,9 @@ export class OverlayController {
instance.onPageWillUnload && instance.onPageWillUnload();
let animation = Animation.create(ref.location.nativeElement, opts.leaveAnimation);
if (this.config.get('animate') === false) {
animation.duration(0);
}
animation.after.removeClass('show-overlay');
this.app.setEnabled(false, animation.duration());

View File

@ -15,15 +15,11 @@ it('custom cancel button should focus', function() {
element(by.css('.e2eCustomCancelButtonFloatingSearchBar input')).sendKeys("DD");
});
it('custom cancel button long text should focus', function() {
element(by.css('.e2eCustomCancelButtonLongTextFloatingSearchBar input')).sendKeys("EE");
});
it('custom cancel action should focus', function() {
element(by.css('.e2eCustomCancelActionFloatingSearchBar input')).sendKeys("FF");
});
// TODO - this test will work on iOS but fail on Android
// TODO - this test will work on iOS but fail on Android
// it('custom cancel action should alert', function() {
// element(by.css('.e2eCustomCancelActionFloatingSearchBar .search-bar-cancel')).click();
// });

View File

@ -11,9 +11,6 @@
<h5 padding-left> Search - Custom Cancel Button </h5>
<ion-search-bar [(ng-model)]="customCancel" show-cancel="true" cancel-text="Done" class="e2eCustomCancelButtonFloatingSearchBar"></ion-search-bar>
<h5 padding-left> Search - Custom Cancel Button Long</h5>
<ion-search-bar [(ng-model)]="customCancelLong" show-cancel="true" cancel-text="I Am So Done" class="e2eCustomCancelButtonLongTextFloatingSearchBar"></ion-search-bar>
<h5 padding-left> Search - Custom Cancel Action</h5>
<ion-search-bar [(ng-model)]="customCancelAction" show-cancel="true" cancel-text="Done" [cancel-action]="myCancelAction" class="e2eCustomCancelActionFloatingSearchBar"></ion-search-bar>

View File

@ -1,16 +1,10 @@
it('friends should be selected', function() {
it('friends and standard should be selected', function() {
element(by.css('.e2eSegmentFriends')).click();
element(by.css('.e2eSegmentStandard')).click();
});
it('standard should be selected', function() {
element(by.css('.e2eSegmentStandard')).click();
});
it('model c should be selected', function() {
it('model c and top grossing should be selected', function() {
element(by.css('.e2eSegmentModelC')).click();
});
it('top grossing should be selected', function() {
element(by.css('.e2eSegmentTopGrossing')).click();
element(by.css('.e2eSegmentTopGrossing')).click();
});

View File

@ -1,4 +1,4 @@
import {Component, Directive, Host, ElementRef, Compiler, DynamicComponentLoader, AppViewManager, forwardRef, NgZone, Renderer} from 'angular2/angular2';
import {Component, Directive, Host, ElementRef, Compiler, DynamicComponentLoader, AppViewManager, NgZone, Renderer} from 'angular2/angular2';
import {IonicApp} from '../app/app';
import {Config} from '../../config/config';
@ -64,8 +64,7 @@ import {Tabs} from './tabs';
'[class.show-tab]': 'isSelected',
'role': 'tabpanel'
},
template: '<template content-anchor></template><ng-content></ng-content>',
directives: [forwardRef(() => TabContentAnchor)]
template: '<template #contents></template>'
})
export class Tab extends NavController {
@ -83,91 +82,71 @@ export class Tab extends NavController {
// A Tab is a NavController for its child pages
super(tabs, app, config, elementRef, compiler, loader, viewManager, zone, renderer);
this.tabs = tabs;
this._isInitial = tabs.add(this);
}
onInit() {
console.debug('Tab onInit', this.getIndex());
console.debug('Tab onInit', this.index);
if (this._isInitial) {
this.tabs.select(this);
} else if (this.tabs.preloadTabs) {
// setTimeout(() => {
// this.load(() => {
// console.debug('preloaded tab', this.getIndex());
// });
// }, 500 * this.getIndex());
setTimeout(() => {
this.load(() => {
console.debug('preloaded tab', this.index);
this.hideNavbars(true);
});
}, 500 * this.index);
}
}
load(callback) {
load(done) {
if (!this._loaded && this.root) {
let opts = {
animate: false
};
this.push(this.root, null, opts).then(callback);
this.push(this.root, null, opts).then(done);
this._loaded = true;
} else {
callback();
done();
}
}
loadContainer(componentType, hostProtoViewRef, viewCtrl, done) {
loadPage(viewCtrl, navbarContainerRef, done) {
// by default a page's navbar goes into the shared tab's navbar section
navbarContainerRef = this.tabs.navbarContainerRef;
this.loadNextToAnchor(componentType, this.contentAnchorRef, viewCtrl).then(componentRef => {
let isTabSubPage = (this.tabs.subPages && viewCtrl.index > 0);
if (isTabSubPage) {
// a subpage, that's not the first index
// should not use the shared tabs navbar section, but use it's own
navbarContainerRef = null;
}
viewCtrl.disposals.push(() => {
componentRef.dispose();
});
// a new ComponentRef has been created
// set the ComponentRef's instance to this ViewController
viewCtrl.setInstance(componentRef.instance);
// remember the ElementRef to the content that was just created
viewCtrl.setContentRef(componentRef.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);
}
});
super.loadPage(viewCtrl, navbarContainerRef, () => {
if (viewCtrl.instance) {
viewCtrl.instance._tabSubPage = isTabSubPage;
}
this.addHasViews();
done();
});
}
getIndex() {
setSelected(isSelected) {
this.isSelected = isSelected;
this.hideNavbars(!isSelected);
}
hideNavbars(shouldHideNavbars) {
this._views.forEach(viewCtrl => {
let navbar = viewCtrl.getNavbar();
navbar && navbar.setHidden(shouldHideNavbars);
});
}
get index() {
return this.tabs.getIndex(this);
}
}
@Directive({selector: 'template[content-anchor]'})
class TabContentAnchor {
constructor(@Host() tab: Tab, elementRef: ElementRef) {
tab.contentAnchorRef = elementRef;
}
}

View File

@ -46,10 +46,6 @@ ion-tabs > ion-navbar-section {
order: $flex-order-tab-bar-navbar;
}
ion-tabs ion-navbar.toolbar.deselected-tab {
display: none;
}
ion-tab-bar-section {
position: relative;
order: $flex-order-tab-bar-bottom;
@ -161,4 +157,3 @@ tab-highlight {
[tab-bar-icons=hide] .tab-button-icon {
display: none;
}

View File

@ -1,4 +1,4 @@
import {Directive, ElementRef, Optional, Host, NgFor, forwardRef, ViewContainerRef} from 'angular2/angular2';
import {Directive, ElementRef, Optional, Host, NgFor, NgIf, forwardRef, ViewContainerRef} from 'angular2/angular2';
import {Ion} from '../ion';
import {IonicApp} from '../app/app';
@ -71,7 +71,7 @@ import {Icon} from '../icon/icon';
'</ion-navbar-section>' +
'<ion-tab-bar-section>' +
'<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">' +
'<icon [name]="t.tabIcon" [is-active]="t.isSelected" class="tab-button-icon"></icon>' +
'<span class="tab-button-text">{{t.tabTitle}}</span>' +
'</a>' +
@ -84,6 +84,7 @@ import {Icon} from '../icon/icon';
directives: [
Icon,
NgFor,
NgIf,
forwardRef(() => TabButton),
forwardRef(() => TabHighlight),
forwardRef(() => TabNavBarAnchor)
@ -108,18 +109,22 @@ export class Tabs extends Ion {
super(elementRef, config);
this.app = app;
this.preload = config.get('preloadTabs');
this.subPages = config.get('tabSubPages');
// collection of children "Tab" instances, which extends NavController
this._tabs = [];
this.tabs = [];
// 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._ready = new Promise(res => { this._isReady = res; });
viewCtrl.setContent(this);
viewCtrl.setContentRef(elementRef);
// TODO: improve how this works, probably not use promises here
this.readyPromise = new Promise(res => { this.isReady = res; });
viewCtrl.onReady = () => {
return this._ready;
return this.readyPromise;
};
}
}
@ -131,9 +136,9 @@ export class Tabs extends Ion {
tab.id = ++_tabIds;
tab.btnId = 'tab-' + tab.id;
tab.panelId = 'tabpanel-' + tab.id;
this._tabs.push(tab);
this.tabs.push(tab);
return (this._tabs.length === 1);
return (this.tabs.length === 1);
}
/**
@ -142,40 +147,25 @@ export class Tabs extends Ion {
* @returns {TODO} TODO
*/
select(tabOrIndex) {
let selectedTab = null;
if (typeof tabOrIndex === 'number') {
selectedTab = this.getByIndex(tabOrIndex);
} else {
selectedTab = tabOrIndex;
}
if (!selectedTab || !this.app.isEnabled()) {
let selectedTab = (typeof tabOrIndex === 'number' ? this.getByIndex(tabOrIndex) : tabOrIndex);
if (!selectedTab) {
return Promise.reject();
}
console.debug('select tab', selectedTab.id);
let deselectedTab = this.getSelected();
if (selectedTab === deselectedTab) {
// no change
return this._touchActive(selectedTab);
return this.touchActive(selectedTab);
}
console.debug('select tab', selectedTab.id);
selectedTab.load(() => {
this._isReady && this._isReady();
this.isReady && this.isReady();
this._tabs.forEach(tab => {
tab.isSelected = (tab === selectedTab);
tab._views.forEach(viewCtrl => {
let navbarRef = viewCtrl.navbarRef();
if (navbarRef) {
navbarRef.nativeElement.classList[tab.isSelected ? 'remove': 'add']('deselected-tab');
}
});
this.tabs.forEach(tab => {
tab.setSelected(tab === selectedTab);
});
this.highlight && this.highlight.select(selectedTab);
@ -188,23 +178,23 @@ export class Tabs extends Ion {
* @returns {TODO} TODO
*/
getByIndex(index) {
if (index < this._tabs.length && index > -1) {
return this._tabs[index];
if (index < this.tabs.length && index > -1) {
return this.tabs[index];
}
return null;
}
getSelected() {
for (let i = 0; i < this._tabs.length; i++) {
if (this._tabs[i].isSelected) {
return this._tabs[i];
for (let i = 0; i < this.tabs.length; i++) {
if (this.tabs[i].isSelected) {
return this.tabs[i];
}
}
return null;
}
getIndex(tab) {
return this._tabs.indexOf(tab);
return this.tabs.indexOf(tab);
}
/**
@ -212,10 +202,8 @@ export class Tabs extends Ion {
* "Touch" the active tab, either going back to the root view of the tab
* or scrolling the tab to the top
*/
_touchActive(tab) {
let stateLen = tab.length();
if(stateLen > 1) {
touchActive(tab) {
if (tab.length() > 1) {
// Pop to the root view
return tab.popToRoot();
}
@ -230,7 +218,6 @@ let _tabIds = -1;
/**
* @private
* TODO
*/
@Directive({
selector: '.tab-button',
@ -243,17 +230,15 @@ let _tabIds = -1;
'[class.has-icon]': 'hasIcon',
'[class.has-title-only]': 'hasTitleOnly',
'[class.icon-only]': 'hasIconOnly',
'(click)': 'onClick($event)',
'[class.disable-hover]': 'disHover',
'(click)': 'onClick()',
}
})
class TabButton extends Ion {
constructor(@Host() tabs: Tabs, config: Config, elementRef: ElementRef) {
super(elementRef, config);
this.tabs = tabs;
if (config.get('hoverCSS') === false) {
elementRef.nativeElement.classList.add('disable-hover');
}
this.disHover = (config.get('hoverCSS') === false);
}
onInit() {
@ -264,16 +249,14 @@ class TabButton extends Ion {
this.hasIconOnly = (this.hasIcon && !this.hasTitle);
}
onClick(ev) {
ev.stopPropagation();
ev.preventDefault();
onClick() {
this.tabs.select(this.tab);
}
}
/**
* @private
* TODO
*/
@Directive({
selector: 'tab-highlight'
@ -306,14 +289,10 @@ class TabHighlight {
/**
* @private
* TODO
*/
@Directive({selector: 'template[navbar-anchor]'})
class TabNavBarAnchor {
constructor(
@Host() tabs: Tabs,
viewContainerRef: ViewContainerRef
) {
constructor(@Host() tabs: Tabs, viewContainerRef: ViewContainerRef) {
tabs.navbarContainerRef = viewContainerRef;
}
}

View File

@ -5,3 +5,5 @@ import {App} from 'ionic/ionic';
templateUrl: 'main.html'
})
class E2EApp {}
document.body.innerHTML += '<link href="styles.css" rel="stylesheet">'

View File

@ -59,11 +59,3 @@
<ion-tab tab-title="Indiana Jones and the Temple of Doom"></ion-tab>
<ion-tab tab-title="Indiana Jones and the Last Crusade"></ion-tab>
</ion-tabs>
<style>
ion-tabs {
height: auto;
border-top: 1px solid gray;
}
</style>

View File

@ -0,0 +1,12 @@
ion-tabs {
position: relative;
top: auto;
height: auto;
margin-bottom: 20px;
}
ion-navbar-section,
ion-content-section {
display: none !important;
}

View File

@ -84,6 +84,7 @@ ion-title {
height: 100%;
padding: 0px 90px 1px 90px;
pointer-events: none;
transform: translateZ(0px);
}
.toolbar-title {
@ -96,6 +97,7 @@ ion-title {
ion-nav-items {
flex: 1;
order: map-get($toolbar-order-ios, primary);
transform: translateZ(0px);
}
ion-nav-items[secondary] {

View File

@ -75,7 +75,6 @@ ion-title {
order: map-get($toolbar-order, title);
display: flex;
align-items: center;
transform: translateZ(0px);
}
.toolbar-title {
@ -109,7 +108,6 @@ ion-title {
ion-nav-items {
display: block;
margin: 0 0.2rem;
transform: translateZ(0px);
pointer-events: none;
order: map-get($toolbar-order, primary);
}

View File

@ -127,6 +127,11 @@ export class Config {
let configObj = null;
if (this._platform) {
let queryStringValue = this._platform.query('ionic' + key.toLowerCase());
if (isDefined(queryStringValue)) {
return this._c[key] = (queryStringValue === 'true' ? true : queryStringValue === 'false' ? false : queryStringValue);
}
// check the platform settings object for this value
// loop though each of the active platforms

View File

@ -1,18 +1,9 @@
import {Component, Directive, View, bootstrap} from 'angular2/angular2'
import {Component, bootstrap} from 'angular2/angular2'
import * as util from 'ionic/util';
import {ionicProviders} from './bootstrap';
import {IONIC_DIRECTIVES} from './directives';
/**
* @private
*/
class PageImpl extends View {
constructor(args = {}) {
args.directives = (args.directives || []).concat(IONIC_DIRECTIVES);
super(args);
}
}
/**
* _For more information on how pages are created, see the [NavController API
@ -72,30 +63,30 @@ class PageImpl extends View {
* you may see these tags if you inspect your markup, you don't need to include
* them in your templates.
*/
export function Page(args) {
export function Page(config={}) {
return function(cls) {
config.selector = 'ion-page';
config.directives = config.directives ? config.directives.concat(IONIC_DIRECTIVES) : IONIC_DIRECTIVES;
config.host = config.host || {};
config.host['[hidden]'] = '_hidden';
config.host['[class.tab-subpage]'] = '_tabSubPage';
var annotations = Reflect.getMetadata('annotations', cls) || [];
annotations.push(new PageImpl(args));
annotations.push(new Component(config));
Reflect.defineMetadata('annotations', annotations, cls);
return cls;
}
}
/**
* TODO
*/
export function ConfigComponent(config) {
return function(cls) {
return makeComponent(cls, appendConfig(cls, config));
var annotations = Reflect.getMetadata('annotations', cls) || [];
annotations.push(new Component(config));
Reflect.defineMetadata('annotations', annotations, cls);
return cls;
}
}
export function makeComponent(cls, config) {
var annotations = Reflect.getMetadata('annotations', cls) || [];
annotations.push(new Component(config));
Reflect.defineMetadata('annotations', annotations, cls);
return cls;
}
function appendConfig(cls, config) {
config.host = config.host || {};

View File

@ -48,6 +48,7 @@ Config.setModeConfig('md', {
popupPopIn: 'popup-md-pop-in',
popupPopOut: 'popup-md-pop-out',
tabSubPages: true,
type: 'overlay',
mdRipple: true,
});

View File

@ -57,6 +57,42 @@ export function run() {
expect(config.get('tabBarPlacement')).toEqual('top');
});
it('should get boolean value from querystring', () => {
let config = new Config();
let platform = new Platform();
platform.url('http://biff.com/?ionicanimate=true')
config.setPlatform(platform);
expect(config.get('animate')).toEqual(true);
config = new Config();
platform = new Platform();
platform.url('http://biff.com/?ionicanimate=false')
config.setPlatform(platform);
expect(config.get('animate')).toEqual(false);
});
it('should get value from case insensitive querystring key', () => {
let config = new Config({
mode: 'a'
});
let platform = new Platform();
platform.url('http://biff.com/?ionicConfigKey=b')
config.setPlatform(platform);
expect(config.get('configKey')).toEqual('b');
});
it('should get value from querystring', () => {
let config = new Config({
mode: 'modeA'
});
let platform = new Platform();
platform.url('http://biff.com/?ionicmode=modeB')
config.setPlatform(platform);
expect(config.get('mode')).toEqual('modeB');
});
it('should override mode platform', () => {
let config = new Config({
mode: 'modeA',

View File

@ -165,7 +165,7 @@ function ifUndefined(val1, val2) {
*/
function addEventListeners(target, types, handler) {
each(splitStr(types), function(type) {
console.debug('hammer addEventListener', type, target.tagName);
//console.debug('hammer addEventListener', type, target.tagName);
target.addEventListener(type, handler, false);
});
}
@ -178,7 +178,7 @@ function addEventListeners(target, types, handler) {
*/
function removeEventListeners(target, types, handler) {
each(splitStr(types), function(type) {
console.debug('hammer removeEventListener', type, target.tagName);
//console.debug('hammer removeEventListener', type, target.tagName);
target.removeEventListener(type, handler, false);
});
}
@ -394,7 +394,7 @@ Input.prototype = {
* bind the events
*/
init: function() {
console.debug('hammer Input init')
//console.debug('hammer Input init')
this.evEl && addEventListeners(this.element, this.evEl, this.domHandler);
this.evTarget && addEventListeners(this.target, this.evTarget, this.domHandler);
this.evWin && addEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler);

View File

@ -24,7 +24,6 @@
"components/button/button-icon",
"components/button/button-fab",
"components/checkbox/checkbox",
"components/content/content",
"components/icon/icon",
"components/item/item",
"components/item/item-media",

View File

@ -21,6 +21,20 @@ export function run() {
expect(platform.is('ios')).toEqual(false);
});
it('should get case insensitive querystring value', () => {
let platform = new Platform();
platform.url('/?KEY=value');
expect(platform.query('key')).toEqual('value');
});
it('should get querystring value', () => {
let platform = new Platform();
platform.url('/?key=value');
expect(platform.query('key')).toEqual('value');
});
it('should set ios via platformOverride, despite android querystring', () => {
let platform = new Platform();
platform.url('/?ionicplatform=android');

View File

@ -9,8 +9,6 @@ const OFF_RIGHT = '99.5%';
const OFF_LEFT = '-33%';
const CENTER = '0%'
const OFF_OPACITY = 0.8;
const SHOW_NAVBAR_CSS = 'show-navbar';
const SHOW_VIEW_CSS = 'show-view';
const SHOW_BACK_BTN_CSS = 'show-back-button';
@ -36,29 +34,24 @@ class IOSTransition extends Animation {
// entering content
let enteringContent = new Animation(enteringView.contentRef());
enteringContent
.before.addClass(SHOW_VIEW_CSS)
.before.setStyles({ zIndex: enteringView.index });
this.add(enteringContent);
if (backDirection) {
// back direction
// entering content, back direction
enteringContent
.fromTo(TRANSLATEX, OFF_LEFT, CENTER)
.fromTo(OPACITY, OFF_OPACITY, 1);
} else {
// forward direction
// entering content, forward direction
enteringContent
.fromTo(TRANSLATEX, OFF_RIGHT, CENTER)
.fromTo(OPACITY, 1, 1);
}
// entering navbar
if (enteringHasNavbar) {
// entering page has a navbar
let enteringNavBar = new Animation(enteringView.navbarRef());
enteringNavBar.before.addClass(SHOW_NAVBAR_CSS);
this.add(enteringNavBar);
let enteringTitle = new Animation(enteringView.titleRef());
@ -76,62 +69,68 @@ class IOSTransition extends Animation {
// set properties depending on direction
if (backDirection) {
// back direction
// entering navbar, back direction
enteringTitle.fromTo(TRANSLATEX, OFF_LEFT, CENTER);
if (enteringView.enableBack()) {
enteringBackButton.before.addClass(SHOW_BACK_BTN_CSS);
// back direction, entering page has a back button
enteringBackButton.fadeIn();
}
} else {
// forward direction
// entering navbar, forward direction
enteringTitle.fromTo(TRANSLATEX, OFF_RIGHT, CENTER);
if (enteringView.enableBack()) {
enteringBackButton.before.addClass(SHOW_BACK_BTN_CSS);
enteringBackButton.fadeIn();
let enteringBackBtnText = new Animation(enteringView.backBtnTextRef());
enteringBackBtnText.fromTo(TRANSLATEX, '150px', '0px');
enteringNavBar.add(enteringBackBtnText);
}
if (leavingHasNavbar) {
// if there is a leaving navbar, then just fade this one in
// entering navbar, forward direction, and there's a leaving navbar
// should just fade in, no sliding
enteringNavbarBg
.fromTo(TRANSLATEX, CENTER, CENTER)
.fadeIn();
} else {
enteringNavbarBg.fromTo(TRANSLATEX, OFF_RIGHT, CENTER);
// entering navbar, forward direction, and there's no leaving navbar
// should just slide in, no fading in
enteringNavbarBg
.fromTo(TRANSLATEX, OFF_RIGHT, CENTER)
.fromTo(OPACITY, 1, 1);
}
if (enteringView.enableBack()) {
// forward direction, entering page has a back button
enteringBackButton
.before.addClass(SHOW_BACK_BTN_CSS)
.fadeIn();
let enteringBackBtnText = new Animation(enteringView.backBtnTextRef());
enteringBackBtnText.fromTo(TRANSLATEX, '100px', '0px');
enteringNavBar.add(enteringBackBtnText);
}
}
}
// setup leaving view
if (leavingView) {
// leaving content
let leavingContent = new Animation(leavingView.contentRef());
this.add(leavingContent);
leavingContent
.before.addClass(SHOW_VIEW_CSS)
.before.setStyles({ zIndex: leavingView.index });
if (backDirection) {
// leaving content, back direction
leavingContent
.fromTo(TRANSLATEX, CENTER, '100%')
.fromTo(OPACITY, 1, 1);
} else {
// leaving content, forward direction
leavingContent
.fromTo(TRANSLATEX, CENTER, OFF_LEFT)
.fromTo(OPACITY, 1, OFF_OPACITY);
}
if (leavingHasNavbar) {
// leaving page has a navbar
let leavingNavBar = new Animation(leavingView.navbarRef());
let leavingBackButton = new Animation(leavingView.backBtnRef());
let leavingTitle = new Animation(leavingView.titleRef());
@ -145,25 +144,28 @@ class IOSTransition extends Animation {
.add(leavingNavbarBg);
this.add(leavingNavBar);
leavingBackButton
.after.removeClass(SHOW_BACK_BTN_CSS)
.fadeOut();
// fade out leaving navbar items
leavingBackButton.fadeOut();
leavingTitle.fadeOut();
leavingNavbarItems.fadeOut();
// set properties depending on direction
if (backDirection) {
// back direction
// leaving navbar, back direction
leavingTitle.fromTo(TRANSLATEX, CENTER, '100%');
if (enteringHasNavbar) {
// this is an entering navbar, just fade this out
// leaving navbar, back direction, and there's an entering navbar
// should just fade out, no sliding
leavingNavbarBg
.fromTo(TRANSLATEX, CENTER, CENTER)
.fadeOut();
} else {
leavingNavbarBg.fromTo(TRANSLATEX, CENTER, '100%');
// leaving navbar, back direction, and there's no entering navbar
// should just slide out, no fading out
leavingNavbarBg
.fromTo(TRANSLATEX, CENTER, '100%')
.fromTo(OPACITY, 1, 1);
}
let leavingBackBtnText = new Animation(leavingView.backBtnTextRef());
@ -171,7 +173,7 @@ class IOSTransition extends Animation {
leavingNavBar.add(leavingBackBtnText);
} else {
// forward direction
// leaving navbar, forward direction
leavingTitle.fromTo(TRANSLATEX, CENTER, OFF_LEFT);
}
}

View File

@ -4,16 +4,13 @@ import {Animation} from '../animations/animation';
const TRANSLATEY = 'translateY';
const OFF_BOTTOM = '40px';
const CENTER = '0px'
const SHOW_NAVBAR_CSS = 'show-navbar';
const SHOW_VIEW_CSS = 'show-view';
const SHOW_BACK_BTN_CSS = 'show-back-button';
const TABBAR_HEIGHT = '69px';
class MDTransition extends Animation {
constructor(navCtrl, opts) {
opts.renderDelay = 160;
//opts.renderDelay = 80;
super(null, opts);
// what direction is the transition going
@ -27,98 +24,34 @@ class MDTransition extends Animation {
let enteringHasNavbar = enteringView.hasNavbar();
let leavingHasNavbar = leavingView && leavingView.hasNavbar();
// entering content item moves in bottom to center
let enteringContent = new Animation(enteringView.contentRef());
enteringContent
.before.addClass(SHOW_VIEW_CSS)
.before.setStyles({ zIndex: enteringView.index });
this.add(enteringContent);
let enteringPage = new Animation(enteringView.pageRef());
this.add(enteringPage);
if (backDirection) {
this.duration(200).easing('cubic-bezier(0.47,0,0.745,0.715)');
enteringContent.fromTo(TRANSLATEY, CENTER, CENTER);
enteringPage.fromTo(TRANSLATEY, CENTER, CENTER);
} else {
this.duration(280).easing('cubic-bezier(0.36,0.66,0.04,1)');
enteringContent
enteringPage
.fromTo(TRANSLATEY, OFF_BOTTOM, CENTER)
.fadeIn();
}
// entering navbar
if (enteringHasNavbar) {
let enteringNavBar = new Animation(enteringView.navbarRef());
enteringNavBar
.before.addClass(SHOW_NAVBAR_CSS)
.before.setStyles({ zIndex: enteringView.index + 10 });
this.add(enteringNavBar);
if (backDirection) {
enteringNavBar.fromTo(TRANSLATEY, CENTER, CENTER);
} else {
enteringNavBar
.fromTo(TRANSLATEY, OFF_BOTTOM, CENTER)
.fadeIn();
}
if (enteringView.enableBack()) {
let enteringBackButton = new Animation(enteringView.backBtnRef());
enteringBackButton.before.addClass(SHOW_BACK_BTN_CSS);
enteringNavBar.add(enteringBackButton);
}
if (enteringHasNavbar && enteringView.enableBack()) {
let enteringBackButton = new Animation(enteringView.backBtnRef());
enteringBackButton.before.addClass(SHOW_BACK_BTN_CSS);
this.add(enteringBackButton);
}
// setup leaving view
if (leavingView) {
if (leavingView && backDirection) {
// leaving content
let leavingContent = new Animation(leavingView.contentRef());
this.add(leavingContent);
leavingContent
.before.addClass(SHOW_VIEW_CSS)
.before.setStyles({ zIndex: leavingView.index });
if (backDirection) {
this.duration(200).easing('cubic-bezier(0.47,0,0.745,0.715)');
leavingContent
.fromTo(TRANSLATEY, CENTER, OFF_BOTTOM)
.fadeOut();
}
if (leavingHasNavbar) {
if (backDirection) {
let leavingNavBar = new Animation(leavingView.navbarRef());
this.add(leavingNavBar);
leavingNavBar
.before.setStyles({ zIndex: leavingView.index + 10 })
.fadeOut();
}
}
}
let viewLength = navCtrl.length();
if ((viewLength === 1 || viewLength === 2) && navCtrl.tabs) {
let tabBarEle = navCtrl.tabs.elementRef.nativeElement.querySelector('ion-tab-bar-section');
let tabBar = new Animation(tabBarEle);
if (viewLength === 1 && backDirection) {
tabBar
.fromTo('height', '0px', TABBAR_HEIGHT)
.fadeIn();
} else if (viewLength === 2 && !backDirection) {
tabBar
.fromTo('height', TABBAR_HEIGHT, '0px')
.fadeOut();
}
this.add(tabBar);
this.duration(200).easing('cubic-bezier(0.47,0,0.745,0.715)');
let leavingPage = new Animation(leavingView.pageRef());
this.add(leavingPage.fromTo(TRANSLATEY, CENTER, OFF_BOTTOM).fadeOut());
}
}

View File

@ -20,8 +20,49 @@
background: none !important;
}
.hide.hide.hide {
display: none;
.hide,
[hidden],
template,
root-anchor {
display: none !important;
}
// Content Padding
// --------------------------------------------------
$content-padding: 16px !default;
[padding],
[padding] > scroll-content {
padding: $content-padding;
}
[padding-top] {
padding-top: $content-padding;
}
[padding-right] {
padding-right: $content-padding;
}
[padding-bottom] {
padding-bottom: $content-padding;
}
[padding-left] {
padding-left: $content-padding;
}
[padding-vertical] {
padding-top: $content-padding;
padding-bottom: $content-padding;
}
[padding-horizontal] {
padding-right: $content-padding;
padding-left: $content-padding;
}

View File

@ -167,12 +167,10 @@ export function getQuerystring(url, key) {
const startIndex = url.indexOf('?');
if (startIndex !== -1) {
const queries = url.slice(startIndex + 1).split('&');
if (queries.length) {
queries.forEach((param) => {
var split = param.split('=');
queryParams[split[0]] = split[1].split('#')[0];
});
}
queries.forEach((param) => {
var split = param.split('=');
queryParams[split[0].toLowerCase()] = split[1].split('#')[0];
});
}
if (key) {
return queryParams[key] || '';

View File

@ -53,7 +53,6 @@
"mkdirp": "^0.5.1",
"node-html-encoder": "0.0.2",
"node-libs-browser": "^0.5.2",
"node-uuid": "^1.4.1",
"q": "^1.4.1",
"request": "2.53.0",
"run-sequence": "^1.1.0",

View File

@ -2,22 +2,10 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<!-- https://www.chromium.org/developers/design-documents/chromium-graphics/how-to-get-gpu-rasterization -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<link href="../../../css/ionic.css" rel="stylesheet">
<script>
// dynamically load the stylesheet depending on theme querystring
// defaults to use the light-theme
var theme = 'ionic.css';
if (location.href.indexOf('ionictheme=dark') > -1) {
theme = 'ionic.dark.css';
}
var stylesheet = document.createElement('link');
stylesheet.setAttribute('rel', 'stylesheet');
stylesheet.setAttribute('href', '../../../css/' + theme);
document.getElementsByTagName('head')[0].appendChild(stylesheet);
if (location.href.indexOf('snapshot=true') > -1) {
document.documentElement.classList.add('snapshot');
} else {
@ -27,7 +15,12 @@
<style>
.snapshot body {
max-height: 700px;
/* crop an exact size */
max-height: 700px !important;
}
.snapshot scroll-content {
/* disable scrollbars */
overflow: hidden !important;
}
/* hack to create tall scrollable areas for testing using <f></f> */

View File

@ -1,7 +1,7 @@
describe('<%= relativePath %>: <%= platform %>', function() {
it('should init', function() {
browser.get('http://localhost:<%= buildConfig.protractorPort %>/dist/e2e/<%= relativePath %>/index.html?ionicplatform=<%= platform %>&snapshot=true');
browser.get('http://localhost:<%= buildConfig.protractorPort %>/dist/e2e/<%= relativePath %>/index.html?ionicplatform=<%= platform %>&ionicanimate=false&snapshot=true');
});
<%= contents %>

View File

@ -116,7 +116,7 @@ var IonicSnapshot = function(options) {
self.testData.description = spec.getFullName();
self.testData.highest_mismatch = self.highestMismatch;
self.testData.png_base64 = pngBase64;
self.testData.url = currentUrl.replace('dist/', '/');
self.testData.url = currentUrl.replace('dist/', '/').replace('&ionicanimate=false', '');
pngBase64 = null;
var requestDeferred = q.defer();

View File

@ -9,9 +9,9 @@ exports.config = {
//domain: 'localhost:8080',
specs: 'dist/e2e/**/*e2e.js',
//specs: 'dist/e2e/tabs/**/*e2e.js',
//specs: 'dist/e2e/search-bar/**/*e2e.js',
sleepBetweenSpecs: 600,
sleepBetweenSpecs: 300,
platformDefauls: {
browser: 'chrome',

View File

@ -8,7 +8,6 @@ module.exports = function(gulp, argv, buildConfig) {
var serveStatic = require('serve-static');
var cp = require('child_process');
var path = require('canonical-path');
var uuid = require('node-uuid');
var projectRoot = path.resolve(__dirname, '../..');
var protractorHttpServer;
@ -29,7 +28,7 @@ module.exports = function(gulp, argv, buildConfig) {
});
gulp.task('e2e-publish', function(done) {
var testId = uuid.v4().split('-')[0];
var testId = generateTestId();
e2ePublish(testId, true);
});
@ -40,7 +39,7 @@ module.exports = function(gulp, argv, buildConfig) {
return done();
}
var testId = uuid.v4().split('-')[0];
var testId = generateTestId();
var protractorConfigFile = path.resolve(projectRoot, 'scripts/snapshot/protractor.config.js');
@ -91,4 +90,14 @@ module.exports = function(gulp, argv, buildConfig) {
require('../e2e/e2e-publish')(snapshotConfig);
}
function generateTestId() {
var chars = 'abcdefghijklmnopqrstuvwxyz';
var id = chars.charAt(Math.floor(Math.random() * chars.length));
chars += '0123456789';
while (id.length < 3) {
id += chars.charAt(Math.floor(Math.random() * chars.length));
}
return id;
}
};