mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-23 22:17:40 +08:00
refactor(nav): create NavControllerBase and public abstract class
Use NavController as the public API, and NavControllerBase as the internal API. Refactored all app/nav/tabs unit tests and created centralized mocking functions.
This commit is contained in:
@ -4,6 +4,7 @@ import { Title } from '@angular/platform-browser';
|
||||
import { ClickBlock } from '../../util/click-block';
|
||||
import { Config } from '../../config/config';
|
||||
import { NavController } from '../nav/nav-controller';
|
||||
import { isTabs, isNav } from '../nav/nav-controller-base';
|
||||
import { NavOptions } from '../nav/nav-interfaces';
|
||||
import { NavPortal } from '../nav/nav-portal';
|
||||
import { Platform } from '../../platform/platform';
|
||||
@ -195,13 +196,7 @@ export class App {
|
||||
// function used to climb up all parent nav controllers
|
||||
function navPop(nav: any): Promise<any> {
|
||||
if (nav) {
|
||||
if (nav.length && nav.length() > 1) {
|
||||
// this nav controller has more than one view
|
||||
// pop the current view on this nav and we're done here
|
||||
console.debug('app, goBack pop nav');
|
||||
return nav.pop();
|
||||
|
||||
} else if (nav.previousTab) {
|
||||
if (isTabs(nav)) {
|
||||
// FYI, using "nav instanceof Tabs" throws a Promise runtime error for whatever reason, idk
|
||||
// this is a Tabs container
|
||||
// see if there is a valid previous tab to go to
|
||||
@ -211,6 +206,12 @@ export class App {
|
||||
nav.select(prevTab);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
} else if (isNav(nav) && nav.length() > 1) {
|
||||
// this nav controller has more than one view
|
||||
// pop the current view on this nav and we're done here
|
||||
console.debug('app, goBack pop nav');
|
||||
return nav.pop();
|
||||
}
|
||||
|
||||
// try again using the parent nav (if there is one)
|
||||
@ -244,10 +245,9 @@ export class App {
|
||||
console.debug('app, goBack exitApp');
|
||||
this._platform.exitApp();
|
||||
}
|
||||
|
||||
} else {
|
||||
return navPromise;
|
||||
}
|
||||
|
||||
return navPromise;
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
|
@ -1,16 +1,16 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {App, Nav, Tabs, Tab, NavOptions, Config, ViewController, Platform} from '../../../../src';
|
||||
import { Component } from '@angular/core';
|
||||
import { App, Config, Nav, NavOptions, Platform, Tab, Tabs, ViewController } from '../../../../src';
|
||||
import { mockNavController, mockTab, mockTabs } from '../../../../src/util/mock-providers';
|
||||
|
||||
export function run() {
|
||||
|
||||
|
||||
describe('App', () => {
|
||||
|
||||
describe('navPop', () => {
|
||||
|
||||
it('should select the previous tab', () => {
|
||||
let nav = mockNav();
|
||||
let portal = mockNav();
|
||||
let nav = mockNavController();
|
||||
let portal = mockNavController();
|
||||
app.setPortal(portal);
|
||||
app.setRootNav(nav);
|
||||
|
||||
@ -40,8 +40,8 @@ describe('App', () => {
|
||||
});
|
||||
|
||||
it('should pop from the active tab, when tabs is nested is the root nav', () => {
|
||||
let nav = mockNav();
|
||||
let portal = mockNav();
|
||||
let nav = mockNavController();
|
||||
let portal = mockNavController();
|
||||
app.setPortal(portal);
|
||||
app.setRootNav(nav);
|
||||
|
||||
@ -91,9 +91,9 @@ describe('App', () => {
|
||||
});
|
||||
|
||||
it('should pop the root nav when nested nav has less than 2 views', () => {
|
||||
let rootNav = mockNav();
|
||||
let nestedNav = mockNav();
|
||||
let portal = mockNav();
|
||||
let rootNav = mockNavController();
|
||||
let nestedNav = mockNavController();
|
||||
let portal = mockNavController();
|
||||
app.setPortal(portal);
|
||||
rootNav.registerChildNav(nestedNav);
|
||||
nestedNav.parent = rootNav;
|
||||
@ -120,9 +120,9 @@ describe('App', () => {
|
||||
});
|
||||
|
||||
it('should pop a view from the nested nav that has more than 1 view', () => {
|
||||
let rootNav = mockNav();
|
||||
let nestedNav = mockNav();
|
||||
let portal = mockNav();
|
||||
let rootNav = mockNavController();
|
||||
let nestedNav = mockNavController();
|
||||
let portal = mockNavController();
|
||||
app.setPortal(portal);
|
||||
app.setRootNav(rootNav);
|
||||
rootNav.registerChildNav(nestedNav);
|
||||
@ -149,8 +149,8 @@ describe('App', () => {
|
||||
});
|
||||
|
||||
it('should pop the overlay in the portal of the root nav', () => {
|
||||
let nav = mockNav();
|
||||
let portal = mockNav();
|
||||
let nav = mockNavController();
|
||||
let portal = mockNavController();
|
||||
app.setPortal(portal);
|
||||
app.setRootNav(nav);
|
||||
|
||||
@ -173,8 +173,8 @@ describe('App', () => {
|
||||
});
|
||||
|
||||
it('should pop the second view in the root nav', () => {
|
||||
let nav = mockNav();
|
||||
let portal = mockNav();
|
||||
let nav = mockNavController();
|
||||
let portal = mockNavController();
|
||||
app.setPortal(portal);
|
||||
app.setRootNav(nav);
|
||||
|
||||
@ -194,8 +194,8 @@ describe('App', () => {
|
||||
});
|
||||
|
||||
it('should exit app when only one view in the root nav', () => {
|
||||
let nav = mockNav();
|
||||
let portal = mockNav();
|
||||
let nav = mockNavController();
|
||||
let portal = mockNavController();
|
||||
app.setPortal(portal);
|
||||
app.setRootNav(nav);
|
||||
|
||||
@ -217,8 +217,8 @@ describe('App', () => {
|
||||
});
|
||||
|
||||
it('should not exit app when only one view in the root nav, but navExitApp config set', () => {
|
||||
let nav = mockNav();
|
||||
let portal = mockNav();
|
||||
let nav = mockNavController();
|
||||
let portal = mockNavController();
|
||||
app.setPortal(portal);
|
||||
app.setRootNav(nav);
|
||||
|
||||
@ -242,8 +242,8 @@ describe('App', () => {
|
||||
});
|
||||
|
||||
it('should not go back if app is not enabled', () => {
|
||||
let nav = mockNav();
|
||||
let portal = mockNav();
|
||||
let nav = mockNavController();
|
||||
let portal = mockNavController();
|
||||
app.setPortal(portal);
|
||||
app.setRootNav(nav);
|
||||
|
||||
@ -276,7 +276,7 @@ describe('App', () => {
|
||||
describe('getActiveNav', () => {
|
||||
|
||||
it('should get active NavController when using tabs with nested nav', () => {
|
||||
let nav = mockNav();
|
||||
let nav = mockNavController();
|
||||
app.setRootNav(nav);
|
||||
|
||||
let tabs = mockTabs();
|
||||
@ -285,9 +285,9 @@ describe('App', () => {
|
||||
nav.registerChildNav(tabs);
|
||||
|
||||
tab2.setSelected(true);
|
||||
let nav2 = mockNav();
|
||||
let nav3 = mockNav();
|
||||
let nav4 = mockNav();
|
||||
let nav2 = mockNavController();
|
||||
let nav3 = mockNavController();
|
||||
let nav4 = mockNavController();
|
||||
tab1.registerChildNav(nav4);
|
||||
tab2.registerChildNav(nav2);
|
||||
tab2.registerChildNav(nav3);
|
||||
@ -296,7 +296,7 @@ describe('App', () => {
|
||||
});
|
||||
|
||||
it('should get active NavController when using tabs, nested in a root nav', () => {
|
||||
let nav = mockNav();
|
||||
let nav = mockNavController();
|
||||
app.setRootNav(nav);
|
||||
|
||||
let tabs = mockTabs();
|
||||
@ -331,9 +331,9 @@ describe('App', () => {
|
||||
});
|
||||
|
||||
it('should get active NavController when nested 3 deep', () => {
|
||||
let nav1 = mockNav();
|
||||
let nav2 = mockNav();
|
||||
let nav3 = mockNav();
|
||||
let nav1 = mockNavController();
|
||||
let nav2 = mockNavController();
|
||||
let nav3 = mockNavController();
|
||||
app.setRootNav(nav1);
|
||||
|
||||
nav1.registerChildNav(nav2);
|
||||
@ -343,8 +343,8 @@ describe('App', () => {
|
||||
});
|
||||
|
||||
it('should get active NavController when nested 2 deep', () => {
|
||||
let nav1 = mockNav();
|
||||
let nav2 = mockNav();
|
||||
let nav1 = mockNavController();
|
||||
let nav2 = mockNavController();
|
||||
app.setRootNav(nav1);
|
||||
|
||||
nav1.registerChildNav(nav2);
|
||||
@ -352,13 +352,13 @@ describe('App', () => {
|
||||
});
|
||||
|
||||
it('should get active NavController when only one nav controller', () => {
|
||||
let nav = mockNav();
|
||||
let nav = mockNavController();
|
||||
app.setRootNav(nav);
|
||||
expect(app.getActiveNav()).toBe(nav);
|
||||
});
|
||||
|
||||
it('should set/get the root nav controller', () => {
|
||||
let nav = mockNav();
|
||||
let nav = mockNavController();
|
||||
app.setRootNav(nav);
|
||||
expect(app.getRootNav()).toBe(nav);
|
||||
});
|
||||
@ -443,40 +443,13 @@ describe('App', () => {
|
||||
var app: App;
|
||||
var config: Config;
|
||||
var platform: Platform;
|
||||
var _cd: any;
|
||||
|
||||
function mockNav(): Nav {
|
||||
return new Nav(null, null, null, config, null, null, null, null, null, null);
|
||||
}
|
||||
|
||||
function mockTabs(): Tabs {
|
||||
return new Tabs(null, null, null, config, null, null, null);
|
||||
}
|
||||
|
||||
function mockTab(parentTabs: Tabs): Tab {
|
||||
var tab = new Tab(parentTabs, app, config, null, null, null, null, null, _cd, null);
|
||||
parentTabs.add(tab);
|
||||
tab.root = SomePage;
|
||||
tab.load = function(opts: any, cb: Function) {
|
||||
cb();
|
||||
};
|
||||
return tab;
|
||||
}
|
||||
|
||||
@Component({})
|
||||
class SomePage {}
|
||||
|
||||
beforeEach(() => {
|
||||
config = new Config();
|
||||
platform = new Platform();
|
||||
app = new App(config, platform);
|
||||
_cd = {
|
||||
reattach: function(){},
|
||||
detach: function(){}
|
||||
};
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import { isTrueProperty } from '../../util/util';
|
||||
import { Item } from '../item/item';
|
||||
import { NativeInput, NextInput } from './native-input';
|
||||
import { NavController } from '../nav/nav-controller';
|
||||
import { NavControllerBase } from '../nav/nav-controller-base';
|
||||
import { Platform } from '../../platform/platform';
|
||||
|
||||
|
||||
@ -27,6 +28,7 @@ export class InputBase {
|
||||
protected _autoFocusAssist: string;
|
||||
protected _autoComplete: string;
|
||||
protected _autoCorrect: string;
|
||||
protected _nav: NavControllerBase;
|
||||
|
||||
inputControl: NgControl;
|
||||
|
||||
@ -44,9 +46,10 @@ export class InputBase {
|
||||
protected _platform: Platform,
|
||||
protected _elementRef: ElementRef,
|
||||
protected _scrollView: Content,
|
||||
protected _nav: NavController,
|
||||
nav: NavController,
|
||||
ngControl: NgControl
|
||||
) {
|
||||
this._nav = <NavControllerBase>nav;
|
||||
this._useAssist = config.getBoolean('scrollAssist', false);
|
||||
this._usePadding = config.getBoolean('scrollPadding', this._useAssist);
|
||||
this._keyboardHeight = config.getNumber('keyboardHeight');
|
||||
|
1303
src/components/nav/nav-controller-base.ts
Normal file
1303
src/components/nav/nav-controller-base.ts
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -13,3 +13,6 @@ export interface NavOptions {
|
||||
climbNav?: boolean;
|
||||
ev?: any;
|
||||
}
|
||||
|
||||
export const DIRECTION_BACK = 'back';
|
||||
export const DIRECTION_FORWARD = 'forward';
|
||||
|
@ -1,11 +1,12 @@
|
||||
import { Directive, Optional } from '@angular/core';
|
||||
import { Directive, HostListener, Input, Optional } from '@angular/core';
|
||||
import { NavController } from './nav-controller';
|
||||
|
||||
import { noop } from '../../util/util';
|
||||
|
||||
/**
|
||||
* @name NavPop
|
||||
* @description
|
||||
* Directive for declaratively pop the current page off from the navigation stack.
|
||||
* Directive to declaratively pop the current page off from the
|
||||
* navigation stack.
|
||||
*
|
||||
* @usage
|
||||
* ```html
|
||||
@ -22,11 +23,7 @@ import { NavController } from './nav-controller';
|
||||
* @see {@link ../NavPush NavPush API Docs}
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[nav-pop]',
|
||||
host: {
|
||||
'(click)': 'onClick()',
|
||||
'role': 'link'
|
||||
}
|
||||
selector: '[navPop]'
|
||||
})
|
||||
export class NavPop {
|
||||
|
||||
@ -36,10 +33,15 @@ export class NavPop {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
onClick() {
|
||||
this._nav && this._nav.pop();
|
||||
@HostListener('click')
|
||||
onClick(): boolean {
|
||||
// If no target, or if target is _self, prevent default browser behavior
|
||||
if (this._nav) {
|
||||
this._nav.pop(null, noop);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import { App } from '../app/app';
|
||||
import { Config } from '../../config/config';
|
||||
import { GestureController } from '../../gestures/gesture-controller';
|
||||
import { Keyboard } from '../../util/keyboard';
|
||||
import { NavController } from '../nav/nav-controller';
|
||||
import { NavControllerBase } from '../nav/nav-controller-base';
|
||||
|
||||
/**
|
||||
* @private
|
||||
@ -12,7 +12,7 @@ import { NavController } from '../nav/nav-controller';
|
||||
@Directive({
|
||||
selector: '[nav-portal]'
|
||||
})
|
||||
export class NavPortal extends NavController {
|
||||
export class NavPortal extends NavControllerBase {
|
||||
constructor(
|
||||
@Inject(forwardRef(() => App)) app: App,
|
||||
config: Config,
|
||||
@ -25,7 +25,7 @@ export class NavPortal extends NavController {
|
||||
viewPort: ViewContainerRef
|
||||
) {
|
||||
super(null, app, config, keyboard, elementRef, zone, renderer, compiler, gestureCtrl);
|
||||
this.isPortal = true;
|
||||
this._isPortal = true;
|
||||
this.setViewport(viewPort);
|
||||
app.setPortal(this);
|
||||
|
||||
|
@ -1,28 +1,35 @@
|
||||
import { Directive, Input, Optional } from '@angular/core';
|
||||
import { Directive, HostListener, Input, Optional } from '@angular/core';
|
||||
|
||||
import { NavController } from './nav-controller';
|
||||
import { noop } from '../../util/util';
|
||||
|
||||
/**
|
||||
* @name NavPush
|
||||
* @description
|
||||
* Directive for declaratively linking to a new page instead of using
|
||||
* {@link ../NavController/#push NavController.push}. Similar to ui-router's `ui-sref`.
|
||||
* Directive to declaratively push a new page to the current nav
|
||||
* stack.
|
||||
*
|
||||
* @usage
|
||||
* ```html
|
||||
* <button [navPush]="pushPage"></button>
|
||||
* ```
|
||||
* To specify parameters you can use array syntax or the `nav-params` property:
|
||||
*
|
||||
* To specify parameters you can use array syntax or the `navParams`
|
||||
* property:
|
||||
*
|
||||
* ```html
|
||||
* <button [navPush]="pushPage" [navParams]="params"></button>
|
||||
* <button [navPush]="pushPage" [navParams]="params">Go</button>
|
||||
* ```
|
||||
* Where `pushPage` and `params` are specified in your component, and `pushPage`
|
||||
* contains a reference to a [@Page component](../../../config/Page/):
|
||||
*
|
||||
* Where `pushPage` and `params` are specified in your component,
|
||||
* and `pushPage` contains a reference to a
|
||||
* [@Page component](../../../config/Page/):
|
||||
*
|
||||
* ```ts
|
||||
* import {LoginPage} from 'login';
|
||||
* import { LoginPage } from './login';
|
||||
*
|
||||
* @Component({
|
||||
* template: `<button [navPush]="pushPage" [navParams]="params"></button>`
|
||||
* template: `<button [navPush]="pushPage" [navParams]="params">Go</button>`
|
||||
* })
|
||||
* class MyPage {
|
||||
* constructor(){
|
||||
@ -32,61 +39,42 @@ import { NavController } from './nav-controller';
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* ### Alternate syntax
|
||||
* You can also use syntax similar to Angular2's router, passing an array to
|
||||
* NavPush:
|
||||
* ```html
|
||||
* <button [navPush]="[pushPage, params]"></button>
|
||||
* ```
|
||||
* @demo /docs/v2/demos/navigation/
|
||||
* @see {@link /docs/v2/components#navigation Navigation Component Docs}
|
||||
* @see {@link ../NavPop NavPop API Docs}
|
||||
*
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[navPush]',
|
||||
host: {
|
||||
'(click)': 'onClick()',
|
||||
'role': 'link'
|
||||
}
|
||||
selector: '[navPush]'
|
||||
})
|
||||
export class NavPush {
|
||||
|
||||
/**
|
||||
* @input {Page} the page you want to push
|
||||
*/
|
||||
@Input() navPush: any;
|
||||
|
||||
/**
|
||||
* @input {any} Any parameters you want to pass along
|
||||
*/
|
||||
@Input() navParams: any;
|
||||
|
||||
constructor(
|
||||
@Optional() private _nav: NavController
|
||||
) {
|
||||
if (!_nav) {
|
||||
console.error('nav-push must be within a NavController');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @input {Page} The Page to push onto the Nav.
|
||||
*/
|
||||
onClick() {
|
||||
let destination: any, params: any;
|
||||
@Input() navPush: any[]|string;
|
||||
|
||||
if (this.navPush instanceof Array) {
|
||||
if (this.navPush.length > 2) {
|
||||
throw 'Too many [navPush] arguments, expects [View, { params }]';
|
||||
}
|
||||
destination = this.navPush[0];
|
||||
params = this.navPush[1] || this.navParams;
|
||||
/**
|
||||
* @input {any} Parameters to pass to the page.
|
||||
*/
|
||||
@Input() navParams: {[k: string]: any};
|
||||
|
||||
} else {
|
||||
destination = this.navPush;
|
||||
params = this.navParams;
|
||||
|
||||
constructor(@Optional() private _nav: NavController) {
|
||||
if (!_nav) {
|
||||
console.error('navPush must be within a NavController');
|
||||
}
|
||||
}
|
||||
|
||||
@HostListener('click')
|
||||
onClick(): boolean {
|
||||
// If no target, or if target is _self, prevent default browser behavior
|
||||
if (this._nav) {
|
||||
this._nav.push(this.navPush, this.navParams, noop);
|
||||
return false;
|
||||
}
|
||||
|
||||
this._nav && this._nav.push(destination, params);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import { Config } from '../../config/config';
|
||||
import { Keyboard } from '../../util/keyboard';
|
||||
import { GestureController } from '../../gestures/gesture-controller';
|
||||
import { isTrueProperty } from '../../util/util';
|
||||
import { NavController } from './nav-controller';
|
||||
import { NavControllerBase } from './nav-controller-base';
|
||||
import { ViewController } from './view-controller';
|
||||
|
||||
/**
|
||||
@ -114,13 +114,13 @@ import { ViewController } from './view-controller';
|
||||
`,
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
})
|
||||
export class Nav extends NavController implements AfterViewInit {
|
||||
export class Nav extends NavControllerBase implements AfterViewInit {
|
||||
private _root: any;
|
||||
private _hasInit: boolean = false;
|
||||
|
||||
constructor(
|
||||
@Optional() viewCtrl: ViewController,
|
||||
@Optional() parent: NavController,
|
||||
@Optional() parent: NavControllerBase,
|
||||
app: App,
|
||||
config: Config,
|
||||
keyboard: Keyboard,
|
||||
@ -164,9 +164,6 @@ export class Nav extends NavController implements AfterViewInit {
|
||||
this._hasInit = true;
|
||||
|
||||
if (this._root) {
|
||||
if (typeof this._root !== 'function') {
|
||||
throw 'The [root] property in <ion-nav> must be given a reference to a component class from within the constructor.';
|
||||
}
|
||||
this.push(this._root);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { assign } from '../../util/util';
|
||||
import { GestureController, GestureDelegate, GesturePriority } from '../../gestures/gesture-controller';
|
||||
import { MenuController } from '../menu/menu-controller';
|
||||
import { NavController } from './nav-controller';
|
||||
import { NavControllerBase } from './nav-controller-base';
|
||||
import { SlideData } from '../../gestures/slide-gesture';
|
||||
import { SlideEdgeGesture } from '../../gestures/slide-edge-gesture';
|
||||
|
||||
@ -11,7 +11,7 @@ export class SwipeBackGesture extends SlideEdgeGesture {
|
||||
constructor(
|
||||
element: HTMLElement,
|
||||
options: any,
|
||||
private _nav: NavController,
|
||||
private _nav: NavControllerBase,
|
||||
gestureCtlr: GestureController
|
||||
) {
|
||||
super(element, assign({
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,11 +1,11 @@
|
||||
import { ChangeDetectorRef, Component, ComponentResolver, ElementRef, EventEmitter, forwardRef, Input, Inject, NgZone, Output, Renderer, ViewChild, ViewEncapsulation, ViewContainerRef } from '@angular/core';
|
||||
import { ChangeDetectorRef, Component, ComponentResolver, ElementRef, EventEmitter, forwardRef, Input, Inject, NgZone, Optional, Output, Renderer, ViewChild, ViewEncapsulation, ViewContainerRef } from '@angular/core';
|
||||
|
||||
import { App } from '../app/app';
|
||||
import { Config } from '../../config/config';
|
||||
import { GestureController } from '../../gestures/gesture-controller';
|
||||
import { isTrueProperty} from '../../util/util';
|
||||
import { Keyboard} from '../../util/keyboard';
|
||||
import { NavController } from '../nav/nav-controller';
|
||||
import { NavControllerBase } from '../nav/nav-controller-base';
|
||||
import { NavOptions} from '../nav/nav-interfaces';
|
||||
import { TabButton} from './tab-button';
|
||||
import { Tabs} from './tabs';
|
||||
@ -128,7 +128,7 @@ import { ViewController} from '../nav/view-controller';
|
||||
template: '<div #viewport></div><div class="nav-decor"></div>',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
})
|
||||
export class Tab extends NavController {
|
||||
export class Tab extends NavControllerBase {
|
||||
private _isInitial: boolean;
|
||||
private _isEnabled: boolean = true;
|
||||
private _isShown: boolean = true;
|
||||
@ -236,10 +236,6 @@ export class Tab extends NavController {
|
||||
|
||||
parent.add(this);
|
||||
|
||||
if (parent.rootNav) {
|
||||
this._sbEnabled = parent.rootNav.isSwipeBackEnabled();
|
||||
}
|
||||
|
||||
this._tabId = 'tabpanel-' + this.id;
|
||||
this._btnId = 'tab-' + this.id;
|
||||
}
|
||||
@ -264,7 +260,7 @@ export class Tab extends NavController {
|
||||
*/
|
||||
load(opts: NavOptions, done?: Function) {
|
||||
if (!this._loaded && this.root) {
|
||||
this.push(this.root, this.rootParams, opts).then(() => {
|
||||
this.push(this.root, this.rootParams, opts, () => {
|
||||
done(true);
|
||||
});
|
||||
this._loaded = true;
|
||||
|
@ -7,9 +7,11 @@ import { Config } from '../../config/config';
|
||||
import { Content } from '../content/content';
|
||||
import { Icon } from '../icon/icon';
|
||||
import { Ion } from '../ion';
|
||||
import { isBlank, isTrueProperty } from '../../util/util';
|
||||
import { isBlank, isPresent, isTrueProperty } from '../../util/util';
|
||||
import { nativeRaf } from '../../util/dom';
|
||||
import { NavController, DIRECTION_FORWARD } from '../nav/nav-controller';
|
||||
import { NavController } from '../nav/nav-controller';
|
||||
import { NavControllerBase } from '../nav/nav-controller-base';
|
||||
import { NavOptions, DIRECTION_FORWARD } from '../nav/nav-interfaces';
|
||||
import { Platform } from '../../platform/platform';
|
||||
import { Tab } from './tab';
|
||||
import { TabButton } from './tab-button';
|
||||
@ -164,7 +166,7 @@ export class Tabs extends Ion {
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
id: number;
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* @private
|
||||
@ -219,7 +221,7 @@ export class Tabs extends Ion {
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
parent: NavController;
|
||||
parent: NavControllerBase;
|
||||
|
||||
constructor(
|
||||
@Optional() parent: NavController,
|
||||
@ -232,8 +234,8 @@ export class Tabs extends Ion {
|
||||
) {
|
||||
super(_elementRef);
|
||||
|
||||
this.parent = parent;
|
||||
this.id = ++tabIds;
|
||||
this.parent = <NavControllerBase>parent;
|
||||
this.id = 't' + (++tabIds);
|
||||
this._sbPadding = _config.getBoolean('statusbarPadding');
|
||||
this._useHighlight = _config.getBoolean('tabsHighlight');
|
||||
|
||||
@ -248,9 +250,9 @@ export class Tabs extends Ion {
|
||||
this._useHighlight = _config.getBoolean('tabbarHighlight');
|
||||
}
|
||||
|
||||
if (parent) {
|
||||
if (this.parent) {
|
||||
// this Tabs has a parent Nav
|
||||
parent.registerChildNav(this);
|
||||
this.parent.registerChildNav(this);
|
||||
|
||||
} else if (this._app) {
|
||||
// this is the root navcontroller for the entire app
|
||||
@ -320,41 +322,32 @@ export class Tabs extends Ion {
|
||||
* @private
|
||||
*/
|
||||
initTabs() {
|
||||
// first check if preloadTab is set as an input @Input, then check the config
|
||||
let preloadTabs = (isBlank(this.preloadTabs) ? this._config.getBoolean('preloadTabs') : isTrueProperty(this.preloadTabs));
|
||||
// get the selected index from the input
|
||||
// otherwise default it to use the first index
|
||||
let selectedIndex = (isBlank(this.selectedIndex) ? 0 : parseInt(this.selectedIndex, 10));
|
||||
|
||||
// get the selected index
|
||||
let selectedIndex = this.selectedIndex ? parseInt(this.selectedIndex, 10) : 0;
|
||||
|
||||
// ensure the selectedIndex isn't a hidden or disabled tab
|
||||
// also find the first available index incase we need it later
|
||||
let availableIndex = -1;
|
||||
this._tabs.forEach((tab, index) => {
|
||||
if (tab.enabled && tab.show && availableIndex < 0) {
|
||||
// we know this tab index is safe to show
|
||||
availableIndex = index;
|
||||
}
|
||||
|
||||
if (index === selectedIndex && (!tab.enabled || !tab.show)) {
|
||||
// the selectedIndex is not safe to show
|
||||
selectedIndex = -1;
|
||||
}
|
||||
});
|
||||
|
||||
if (selectedIndex < 0) {
|
||||
// the selected index wasn't safe to show
|
||||
// instead use an available index found to be safe to show
|
||||
selectedIndex = availableIndex;
|
||||
// get the selectedIndex and ensure it isn't hidden or disabled
|
||||
let selectedTab = this._tabs.find((t, i) => i === selectedIndex && t.enabled && t.show);
|
||||
if (!selectedTab) {
|
||||
// wasn't able to select the tab they wanted
|
||||
// try to find the first tab that's available
|
||||
selectedTab = this._tabs.find(t => t.enabled && t.show);
|
||||
}
|
||||
|
||||
this._tabs.forEach((tab, index) => {
|
||||
if (index === selectedIndex) {
|
||||
this.select(tab);
|
||||
if (selectedTab) {
|
||||
// we found a tab to select
|
||||
this.select(selectedTab);
|
||||
}
|
||||
|
||||
} else if (preloadTabs) {
|
||||
tab.preload(1000 * index);
|
||||
}
|
||||
});
|
||||
// check if preloadTab is set as an input @Input
|
||||
// otherwise check the preloadTabs config
|
||||
let shouldPreloadTabs = (isBlank(this.preloadTabs) ? this._config.getBoolean('preloadTabs') : isTrueProperty(this.preloadTabs));
|
||||
if (shouldPreloadTabs) {
|
||||
// preload all the tabs which isn't the selected tab
|
||||
this._tabs.filter((t) => t !== selectedTab).forEach((tab, index) => {
|
||||
tab.preload(this._config.getNumber('tabsPreloadDelay', 1000) * index);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -379,31 +372,33 @@ export class Tabs extends Ion {
|
||||
/**
|
||||
* @param {number|Tab} tabOrIndex Index, or the Tab instance, of the tab to select.
|
||||
*/
|
||||
select(tabOrIndex: number | Tab) {
|
||||
select(tabOrIndex: number | Tab, opts: NavOptions = {}, done?: Function): Promise<any> {
|
||||
let promise: Promise<any>;
|
||||
if (!done) {
|
||||
promise = new Promise(res => { done = res; });
|
||||
}
|
||||
|
||||
let selectedTab: Tab = (typeof tabOrIndex === 'number' ? this.getByIndex(tabOrIndex) : tabOrIndex);
|
||||
if (isBlank(selectedTab)) {
|
||||
return;
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
let deselectedTab = this.getSelected();
|
||||
|
||||
if (selectedTab === deselectedTab) {
|
||||
// no change
|
||||
return this._touchActive(selectedTab);
|
||||
this._touchActive(selectedTab);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
console.debug(`Tabs, select: ${selectedTab.id}`);
|
||||
|
||||
let opts = {
|
||||
animate: false
|
||||
};
|
||||
|
||||
let deselectedPage: ViewController;
|
||||
if (deselectedTab) {
|
||||
deselectedPage = deselectedTab.getActive();
|
||||
deselectedPage && deselectedPage.fireWillLeave();
|
||||
}
|
||||
|
||||
opts.animate = false;
|
||||
|
||||
let selectedPage = selectedTab.getActive();
|
||||
selectedPage && selectedPage.fireWillEnter();
|
||||
|
||||
@ -451,7 +446,11 @@ export class Tabs extends Ion {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -513,6 +512,13 @@ export class Tabs extends Ion {
|
||||
return this._tabs.indexOf(tab);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
length(): number {
|
||||
return this._tabs.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* "Touch" the active tab, going back to the root view of the tab
|
||||
@ -548,20 +554,6 @@ export class Tabs extends Ion {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Returns the root NavController. Returns `null` if Tabs is not
|
||||
* within a NavController.
|
||||
* @returns {NavController}
|
||||
*/
|
||||
get rootNav(): NavController {
|
||||
let nav = this.parent;
|
||||
while (nav && nav.parent) {
|
||||
nav = nav.parent;
|
||||
}
|
||||
return nav;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* DOM WRITE
|
||||
|
@ -1,10 +1,89 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {App, Nav, Tabs, Tab, NavOptions, Config, ViewController, Platform} from '../../../../src';
|
||||
import { Component } from '@angular/core';
|
||||
import { App, Config, Nav, NavOptions, Platform, Tab, Tabs, ViewController } from '../../../../src';
|
||||
import { mockTab, mockTabs } from '../../../../src/util/mock-providers';
|
||||
|
||||
export function run() {
|
||||
|
||||
describe('Tabs', () => {
|
||||
|
||||
describe('initTabs', () => {
|
||||
|
||||
it('should preload all tabs', () => {
|
||||
var tabs = mockTabs();
|
||||
var tab0 = mockTab(tabs);
|
||||
var tab1 = mockTab(tabs);
|
||||
tab0.root = SomePage;
|
||||
tab1.root = SomePage;
|
||||
|
||||
tab0.preload = () => {};
|
||||
tab1.preload = () => {};
|
||||
|
||||
spyOn(tab0, 'preload');
|
||||
spyOn(tab1, 'preload');
|
||||
|
||||
tabs.preloadTabs = true;
|
||||
|
||||
tabs.initTabs();
|
||||
|
||||
expect(tab0.isSelected).toEqual(true);
|
||||
expect(tab1.isSelected).toEqual(false);
|
||||
|
||||
expect(tab0.preload).not.toHaveBeenCalled();
|
||||
expect(tab1.preload).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not select a hidden or disabled tab', () => {
|
||||
var tabs = mockTabs();
|
||||
var tab0 = mockTab(tabs);
|
||||
var tab1 = mockTab(tabs);
|
||||
tab0.root = SomePage;
|
||||
tab1.root = SomePage;
|
||||
|
||||
tab1.enabled = false;
|
||||
tab1.show = false;
|
||||
|
||||
tabs.selectedIndex = '1';
|
||||
tabs.initTabs();
|
||||
|
||||
expect(tab0.isSelected).toEqual(true);
|
||||
expect(tab1.isSelected).toEqual(false);
|
||||
});
|
||||
|
||||
it('should select the second tab from selectedIndex input', () => {
|
||||
var tabs = mockTabs();
|
||||
var tab0 = mockTab(tabs);
|
||||
var tab1 = mockTab(tabs);
|
||||
tab0.root = SomePage;
|
||||
tab1.root = SomePage;
|
||||
|
||||
tabs.selectedIndex = '1';
|
||||
tabs.initTabs();
|
||||
|
||||
expect(tab0.isSelected).toEqual(false);
|
||||
expect(tab1.isSelected).toEqual(true);
|
||||
});
|
||||
|
||||
it('should select the first tab by default', () => {
|
||||
var tabs = mockTabs();
|
||||
var tab0 = mockTab(tabs);
|
||||
var tab1 = mockTab(tabs);
|
||||
tab0.root = SomePage;
|
||||
tab1.root = SomePage;
|
||||
|
||||
spyOn(tab0, 'preload');
|
||||
spyOn(tab1, 'preload');
|
||||
|
||||
tabs.initTabs();
|
||||
|
||||
expect(tab0.isSelected).toEqual(true);
|
||||
expect(tab1.isSelected).toEqual(false);
|
||||
|
||||
expect(tab0.preload).not.toHaveBeenCalled();
|
||||
expect(tab1.preload).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('previousTab', () => {
|
||||
|
||||
it('should find the previous tab when there has been 3 selections', () => {
|
||||
@ -12,9 +91,6 @@ describe('Tabs', () => {
|
||||
var tab0 = mockTab(tabs);
|
||||
var tab1 = mockTab(tabs);
|
||||
var tab2 = mockTab(tabs);
|
||||
tabs.add(tab0);
|
||||
tabs.add(tab1);
|
||||
tabs.add(tab2);
|
||||
tab0.root = SomePage;
|
||||
tab1.root = SomePage;
|
||||
tab2.root = SomePage;
|
||||
@ -36,8 +112,6 @@ describe('Tabs', () => {
|
||||
var tabs = mockTabs();
|
||||
var tab0 = mockTab(tabs);
|
||||
var tab1 = mockTab(tabs);
|
||||
tabs.add(tab0);
|
||||
tabs.add(tab1);
|
||||
tab0.root = SomePage;
|
||||
tab1.root = SomePage;
|
||||
|
||||
@ -56,8 +130,6 @@ describe('Tabs', () => {
|
||||
var tabs = mockTabs();
|
||||
var tab0 = mockTab(tabs);
|
||||
var tab1 = mockTab(tabs);
|
||||
tabs.add(tab0);
|
||||
tabs.add(tab1);
|
||||
tab0.root = SomePage;
|
||||
tab1.root = SomePage;
|
||||
|
||||
@ -87,8 +159,6 @@ describe('Tabs', () => {
|
||||
var tabs = mockTabs();
|
||||
var tab0 = mockTab(tabs);
|
||||
var tab1 = mockTab(tabs);
|
||||
tabs.add(tab0);
|
||||
tabs.add(tab1);
|
||||
|
||||
tab0.root = SomePage;
|
||||
tab1.root = SomePage;
|
||||
@ -103,12 +173,11 @@ describe('Tabs', () => {
|
||||
var tabs = mockTabs();
|
||||
var tab0 = mockTab(tabs);
|
||||
var tab1 = mockTab(tabs);
|
||||
tabs.add(tab0);
|
||||
tabs.add(tab1);
|
||||
|
||||
tab0.root = SomePage;
|
||||
tab1.root = SomePage;
|
||||
|
||||
expect(tabs.length()).toEqual(2);
|
||||
expect(tab0.isSelected).toBeUndefined();
|
||||
expect(tab1.isSelected).toBeUndefined();
|
||||
|
||||
@ -118,16 +187,6 @@ describe('Tabs', () => {
|
||||
expect(tab1.isSelected).toEqual(false);
|
||||
});
|
||||
|
||||
it('should not select an invalid tab index', () => {
|
||||
var tabs = mockTabs();
|
||||
var tab0 = mockTab(tabs);
|
||||
var tab1 = mockTab(tabs);
|
||||
tabs.add(tab0);
|
||||
tabs.add(tab1);
|
||||
|
||||
expect(tabs.select(22)).toBeUndefined();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('getByIndex', () => {
|
||||
@ -137,8 +196,6 @@ describe('Tabs', () => {
|
||||
var tab0 = mockTab(tabs);
|
||||
tab0.setRoot(<any>{});
|
||||
var tab1 = mockTab(tabs);
|
||||
tabs.add(tab0);
|
||||
tabs.add(tab1);
|
||||
|
||||
expect(tabs.getIndex(tab0)).toEqual(0);
|
||||
expect(tabs.getIndex(tab1)).toEqual(1);
|
||||
@ -152,8 +209,6 @@ describe('Tabs', () => {
|
||||
var tabs = mockTabs();
|
||||
var tab0 = mockTab(tabs);
|
||||
var tab1 = mockTab(tabs);
|
||||
tabs.add(tab0);
|
||||
tabs.add(tab1);
|
||||
|
||||
tab1.setSelected(true);
|
||||
|
||||
@ -164,48 +219,15 @@ describe('Tabs', () => {
|
||||
var tabs = mockTabs();
|
||||
var tab0 = mockTab(tabs);
|
||||
var tab1 = mockTab(tabs);
|
||||
tabs.add(tab0);
|
||||
tabs.add(tab1);
|
||||
|
||||
expect(tabs.getSelected()).toEqual(null);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
var app: App;
|
||||
var config: Config;
|
||||
var platform: Platform;
|
||||
var _cd: any;
|
||||
|
||||
function mockNav(): Nav {
|
||||
return new Nav(null, null, null, config, null, null, null, null, null);
|
||||
}
|
||||
|
||||
function mockTabs(): Tabs {
|
||||
return new Tabs(null, null, null, config, null, null, null);
|
||||
}
|
||||
|
||||
function mockTab(parentTabs: Tabs): Tab {
|
||||
var tab = new Tab(parentTabs, app, config, null, null, null, null, null, _cd);
|
||||
tab.load = function(opts: any, cb: Function) {
|
||||
cb();
|
||||
};
|
||||
return tab;
|
||||
}
|
||||
|
||||
@Component({})
|
||||
class SomePage {}
|
||||
|
||||
beforeEach(() => {
|
||||
config = new Config();
|
||||
platform = new Platform();
|
||||
app = new App(config, platform);
|
||||
_cd = {
|
||||
reattach: function(){},
|
||||
detach: function(){}
|
||||
};
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
171
src/util/mock-providers.ts
Normal file
171
src/util/mock-providers.ts
Normal file
@ -0,0 +1,171 @@
|
||||
import { ChangeDetectorRef, ElementRef, NgZone, Renderer } from '@angular/core';
|
||||
import { Location } from '@angular/common';
|
||||
|
||||
import { App, Config, Form, GestureController, Keyboard, MenuController, NavOptions, Platform, Tab, Tabs, Transition, ViewController } from '../../src';
|
||||
import { NavControllerBase } from '../../src/components/nav/nav-controller-base';
|
||||
|
||||
|
||||
export const mockConfig = function(config?: any) {
|
||||
return new Config(config);
|
||||
};
|
||||
|
||||
export const mockPlatform = function(platforms?: string[]) {
|
||||
return new Platform(platforms);
|
||||
};
|
||||
|
||||
export const mockApp = function(config?: Config, platform?: Platform) {
|
||||
config = config || mockConfig();
|
||||
platform = platform || mockPlatform();
|
||||
return new App(config, platform);
|
||||
};
|
||||
|
||||
export const mockZone = function(): NgZone {
|
||||
let zone: any = {
|
||||
run: function(cb: any) {
|
||||
cb();
|
||||
},
|
||||
runOutsideAngular: function(cb: any) {
|
||||
cb();
|
||||
}
|
||||
};
|
||||
return zone;
|
||||
};
|
||||
|
||||
export const mockChangeDetectorRef = function(): ChangeDetectorRef {
|
||||
let cd: any = {
|
||||
reattach: () => {},
|
||||
detach: () => {}
|
||||
};
|
||||
return cd;
|
||||
};
|
||||
|
||||
export const mockElementRef = function(): ElementRef {
|
||||
return {
|
||||
nativeElement: document.createElement('div')
|
||||
};
|
||||
};
|
||||
|
||||
export const mockRenderer = function(): Renderer {
|
||||
let renderer: any = {
|
||||
setElementAttribute: () => {},
|
||||
setElementClass: () => {},
|
||||
setElementStyle: () => {}
|
||||
};
|
||||
return renderer;
|
||||
};
|
||||
|
||||
export const mockLocation = function(): Location {
|
||||
let location: any = {
|
||||
path: () => { return ''; },
|
||||
subscribe: () => {},
|
||||
go: () => {},
|
||||
back: () => {}
|
||||
};
|
||||
return location;
|
||||
};
|
||||
|
||||
export const mockTransition = function(playCallback: Function, duration: number) {
|
||||
return function _createTrans(enteringView: ViewController, leavingView: ViewController, transitionOpts: any): Transition {
|
||||
let transition: any = {
|
||||
play: () => {
|
||||
playCallback();
|
||||
},
|
||||
getDuration: () => { return duration; },
|
||||
onFinish: () => {}
|
||||
};
|
||||
return transition;
|
||||
};
|
||||
};
|
||||
|
||||
export const mockNavController = function(): NavControllerBase {
|
||||
let platform = mockPlatform();
|
||||
|
||||
let config = mockConfig();
|
||||
config.setPlatform(platform);
|
||||
|
||||
let app = mockApp(config, platform);
|
||||
|
||||
let form = new Form();
|
||||
|
||||
let zone = mockZone();
|
||||
|
||||
let keyboard = new Keyboard(config, form, zone);
|
||||
|
||||
let elementRef = mockElementRef();
|
||||
|
||||
let renderer = mockRenderer();
|
||||
|
||||
let compiler: any = null;
|
||||
|
||||
let gestureCtrl = new GestureController(app);
|
||||
|
||||
let location = mockLocation();
|
||||
|
||||
return new NavControllerBase(
|
||||
null,
|
||||
app,
|
||||
config,
|
||||
keyboard,
|
||||
elementRef,
|
||||
zone,
|
||||
renderer,
|
||||
compiler,
|
||||
gestureCtrl
|
||||
);
|
||||
};
|
||||
|
||||
export const mockTab = function(parentTabs: Tabs): Tab {
|
||||
let platform = mockPlatform();
|
||||
|
||||
let config = mockConfig();
|
||||
config.setPlatform(platform);
|
||||
|
||||
let app = (<any>parentTabs)._app || mockApp(config, platform);
|
||||
|
||||
let form = new Form();
|
||||
|
||||
let zone = mockZone();
|
||||
|
||||
let keyboard = new Keyboard(config, form, zone);
|
||||
|
||||
let elementRef = mockElementRef();
|
||||
|
||||
let renderer = mockRenderer();
|
||||
|
||||
let changeDetectorRef = mockChangeDetectorRef();
|
||||
|
||||
let compiler: any = null;
|
||||
|
||||
let gestureCtrl = new GestureController(app);
|
||||
|
||||
let location = mockLocation();
|
||||
|
||||
let tab = new Tab(
|
||||
parentTabs,
|
||||
app,
|
||||
config,
|
||||
keyboard,
|
||||
elementRef,
|
||||
zone,
|
||||
renderer,
|
||||
compiler,
|
||||
changeDetectorRef,
|
||||
gestureCtrl
|
||||
);
|
||||
|
||||
tab.load = (opts: any, cb: Function) => {
|
||||
cb();
|
||||
};
|
||||
|
||||
return tab;
|
||||
};
|
||||
|
||||
export const mockTabs = function(app?: App): Tabs {
|
||||
let config = mockConfig();
|
||||
let platform = mockPlatform();
|
||||
app = app || mockApp(config, platform);
|
||||
let elementRef = mockElementRef();
|
||||
let renderer = mockRenderer();
|
||||
|
||||
return new Tabs(null, null, app, config, elementRef, platform, renderer);
|
||||
};
|
@ -1,4 +1,6 @@
|
||||
|
||||
export function noop() {}
|
||||
|
||||
/**
|
||||
* Given a min and max, restrict the given number
|
||||
* to the range.
|
||||
|
Reference in New Issue
Block a user