mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-18 19:21:34 +08:00
router updates
This commit is contained in:
@ -26,7 +26,11 @@ export class IonicApp {
|
||||
|
||||
load(appRef) {
|
||||
this.ref(appRef);
|
||||
this.zone(this.injector().get(NgZone));
|
||||
this._zone = this.injector().get(NgZone);
|
||||
}
|
||||
|
||||
title(val) {
|
||||
document.title = val;
|
||||
}
|
||||
|
||||
ref(val) {
|
||||
@ -40,11 +44,8 @@ export class IonicApp {
|
||||
return this._ref.injector;
|
||||
}
|
||||
|
||||
zone(val) {
|
||||
if (arguments.length) {
|
||||
this._zone = val;
|
||||
}
|
||||
return this._zone;
|
||||
zoneRun(fn) {
|
||||
this._zone.run(fn);
|
||||
}
|
||||
|
||||
stateChange(type, activeView) {
|
||||
@ -74,10 +75,10 @@ export class IonicApp {
|
||||
* Create and append the given component into the root
|
||||
* element of the app.
|
||||
*
|
||||
* @param Component the cls to create and insert
|
||||
* @param Component the component to create and insert
|
||||
* @return Promise that resolves with the ContainerRef created
|
||||
*/
|
||||
appendComponent(cls: Type, context=null) {
|
||||
appendComponent(component: Type, context=null) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let injector = this.injector();
|
||||
let compiler = injector.get(Compiler);
|
||||
@ -85,7 +86,7 @@ export class IonicApp {
|
||||
let rootComponentRef = this._ref._hostComponent;
|
||||
let viewContainerLocation = rootComponentRef.location;
|
||||
|
||||
compiler.compileInHost(cls).then(protoViewRef => {
|
||||
compiler.compileInHost(component).then(protoViewRef => {
|
||||
let atIndex = 0;
|
||||
|
||||
let hostViewRef = viewMngr.createViewInContainer(
|
||||
@ -158,8 +159,8 @@ function initApp(window, document, config) {
|
||||
return app;
|
||||
}
|
||||
|
||||
export function ionicBootstrap(cls, config, router) {
|
||||
return new Promise((resolve, reject) => {
|
||||
export function ionicBootstrap(component, config, router) {
|
||||
return new Promise(resolve => {
|
||||
try {
|
||||
// get the user config, or create one if wasn't passed in
|
||||
if (typeof config !== IonicConfig) {
|
||||
@ -202,7 +203,7 @@ export function ionicBootstrap(cls, config, router) {
|
||||
bind(Modal).toValue(modal)
|
||||
];
|
||||
|
||||
bootstrap(cls, injectableBindings).then(appRef => {
|
||||
bootstrap(component, injectableBindings).then(appRef => {
|
||||
app.load(appRef);
|
||||
|
||||
router.load(window, app, config).then(() => {
|
||||
@ -212,12 +213,10 @@ export function ionicBootstrap(cls, config, router) {
|
||||
|
||||
}).catch(err => {
|
||||
console.error('ionicBootstrap', err);
|
||||
reject(err);
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
console.error('ionicBootstrap', err);
|
||||
reject(err);
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import {ProtoViewRef} from 'angular2/src/core/compiler/view_ref';
|
||||
import {Ion} from '../ion';
|
||||
import {IonicConfig} from '../../config/config';
|
||||
import {IonicComponent} from '../../config/annotations';
|
||||
import {IonicApp} from '../app/app';
|
||||
import {ViewItem} from '../view/view-item';
|
||||
import * as dom from '../../util/dom';
|
||||
|
||||
@ -42,9 +43,10 @@ import * as dom from '../../util/dom';
|
||||
]
|
||||
})
|
||||
export class Navbar extends Ion {
|
||||
constructor(item: ViewItem, elementRef: ElementRef, ionicConfig: IonicConfig) {
|
||||
constructor(item: ViewItem, elementRef: ElementRef, ionicConfig: IonicConfig, app: IonicApp) {
|
||||
super(elementRef, ionicConfig);
|
||||
|
||||
this.app = app;
|
||||
this.eleRef = elementRef;
|
||||
this.itemEles = [];
|
||||
item.navbarView(this);
|
||||
@ -130,8 +132,10 @@ export class Navbar extends Ion {
|
||||
}
|
||||
|
||||
didEnter() {
|
||||
setTimeout(() => {
|
||||
const titleEle = this._ttEle || (this._ttEle = this.eleRef.nativeElement.querySelector('ion-title'));
|
||||
this.app.title(titleEle.textContent);
|
||||
|
||||
setTimeout(() => {
|
||||
//this.titleText((titleEle && titleEle.textContent) || '');
|
||||
}, 32);
|
||||
}
|
||||
|
@ -1,21 +1,25 @@
|
||||
import {App} from 'ionic/ionic';
|
||||
|
||||
import {FirstPage} from './pages/first-page';
|
||||
import {SecondPage} from './pages/second-page';
|
||||
import {ThirdPage} from './pages/third-page';
|
||||
|
||||
|
||||
@App({
|
||||
routes: {
|
||||
'FirstPage': {
|
||||
'path': '/firstpage',
|
||||
'module': 'dist/examples/nav/basic/pages/first-page',
|
||||
'root': true
|
||||
routes: [
|
||||
{
|
||||
path: '/firstpage',
|
||||
component: FirstPage,
|
||||
root: true
|
||||
},
|
||||
'SecondPage': {
|
||||
'path': '/secondpage',
|
||||
'module': 'dist/examples/nav/basic/pages/second-page',
|
||||
},
|
||||
'ThirdPage': {
|
||||
'path': '/thirdpage',
|
||||
'module': 'dist/examples/nav/basic/pages/third-page',
|
||||
{
|
||||
path: '/secondpage',
|
||||
component: SecondPage,
|
||||
},
|
||||
{
|
||||
path: '/thirdpage',
|
||||
component: ThirdPage,
|
||||
}
|
||||
]
|
||||
})
|
||||
class MyApp {}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import {IonicView, IonicConfig, IonicApp} from 'ionic/ionic';
|
||||
import {NavParams, Routable, NavController} from 'ionic/ionic';
|
||||
import {NavParams, NavController} from 'ionic/ionic';
|
||||
|
||||
import {SecondPage} from './second-page';
|
||||
import {ThirdPage} from './third-page';
|
||||
@ -52,39 +52,35 @@ export class FirstPage {
|
||||
this.nav.setItems(items);
|
||||
}
|
||||
|
||||
viewLoaded() {
|
||||
console.log('viewLoaded first page');
|
||||
}
|
||||
// viewLoaded() {
|
||||
// console.log('viewLoaded first page');
|
||||
// }
|
||||
|
||||
viewWillEnter() {
|
||||
console.log('viewWillEnter first page');
|
||||
}
|
||||
// viewWillEnter() {
|
||||
// console.log('viewWillEnter first page');
|
||||
// }
|
||||
|
||||
viewDidEnter() {
|
||||
console.log('viewDidEnter first page');
|
||||
}
|
||||
// viewDidEnter() {
|
||||
// console.log('viewDidEnter first page');
|
||||
// }
|
||||
|
||||
viewWillLeave() {
|
||||
console.log('viewWillLeave first page');
|
||||
}
|
||||
// viewWillLeave() {
|
||||
// console.log('viewWillLeave first page');
|
||||
// }
|
||||
|
||||
viewDidLeave() {
|
||||
console.log('viewDidLeave first page');
|
||||
}
|
||||
// viewDidLeave() {
|
||||
// console.log('viewDidLeave first page');
|
||||
// }
|
||||
|
||||
viewWillUnload() {
|
||||
console.log('viewWillUnload first page');
|
||||
}
|
||||
// viewWillUnload() {
|
||||
// console.log('viewWillUnload first page');
|
||||
// }
|
||||
|
||||
viewDidUnload() {
|
||||
console.log('viewDidUnload first page');
|
||||
}
|
||||
// viewDidUnload() {
|
||||
// console.log('viewDidUnload first page');
|
||||
// }
|
||||
|
||||
push() {
|
||||
this.nav.push(SecondPage, { id: 8675309, myData: [1,2,3,4] }, { animation: 'ios' });
|
||||
}
|
||||
}
|
||||
|
||||
new Routable(FirstPage, {
|
||||
path: '/firstpage'
|
||||
});
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {IonicView, Routable, NavController, NavParams} from 'ionic/ionic';
|
||||
import {IonicView, NavController, NavParams} from 'ionic/ionic';
|
||||
import {ThirdPage} from './third-page';
|
||||
import {FirstPage} from './first-page';
|
||||
|
||||
@ -45,36 +45,32 @@ export class SecondPage {
|
||||
this.nav.push(ThirdPage);
|
||||
}
|
||||
|
||||
viewLoaded() {
|
||||
console.log('viewLoaded second page');
|
||||
}
|
||||
// viewLoaded() {
|
||||
// console.log('viewLoaded second page');
|
||||
// }
|
||||
|
||||
viewWillEnter() {
|
||||
console.log('viewWillEnter second page');
|
||||
}
|
||||
// viewWillEnter() {
|
||||
// console.log('viewWillEnter second page');
|
||||
// }
|
||||
|
||||
viewDidEnter() {
|
||||
console.log('viewDidEnter second page');
|
||||
}
|
||||
// viewDidEnter() {
|
||||
// console.log('viewDidEnter second page');
|
||||
// }
|
||||
|
||||
viewWillLeave() {
|
||||
console.log('viewWillLeave second page');
|
||||
}
|
||||
// viewWillLeave() {
|
||||
// console.log('viewWillLeave second page');
|
||||
// }
|
||||
|
||||
viewDidLeave() {
|
||||
console.log('viewDidLeave second page');
|
||||
}
|
||||
// viewDidLeave() {
|
||||
// console.log('viewDidLeave second page');
|
||||
// }
|
||||
|
||||
viewWillUnload() {
|
||||
console.log('viewWillUnload second page');
|
||||
}
|
||||
// viewWillUnload() {
|
||||
// console.log('viewWillUnload second page');
|
||||
// }
|
||||
|
||||
viewDidUnload() {
|
||||
console.log('viewDidUnload second page');
|
||||
}
|
||||
// viewDidUnload() {
|
||||
// console.log('viewDidUnload second page');
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
new Routable(SecondPage, {
|
||||
path: '/secondpage'
|
||||
});
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {IonicView, Routable, NavController} from 'ionic/ionic';
|
||||
import {IonicView, NavController} from 'ionic/ionic';
|
||||
|
||||
|
||||
@IonicView({
|
||||
@ -23,36 +23,32 @@ export class ThirdPage {
|
||||
this.nav.pop()
|
||||
}
|
||||
|
||||
viewLoaded() {
|
||||
console.log('viewLoaded third page');
|
||||
}
|
||||
// viewLoaded() {
|
||||
// console.log('viewLoaded third page');
|
||||
// }
|
||||
|
||||
viewWillEnter() {
|
||||
console.log('viewWillEnter third page');
|
||||
}
|
||||
// viewWillEnter() {
|
||||
// console.log('viewWillEnter third page');
|
||||
// }
|
||||
|
||||
viewDidEnter() {
|
||||
console.log('viewDidEnter third page');
|
||||
}
|
||||
// viewDidEnter() {
|
||||
// console.log('viewDidEnter third page');
|
||||
// }
|
||||
|
||||
viewWillLeave() {
|
||||
console.log('viewWillLeave third page');
|
||||
}
|
||||
// viewWillLeave() {
|
||||
// console.log('viewWillLeave third page');
|
||||
// }
|
||||
|
||||
viewDidLeave() {
|
||||
console.log('viewDidLeave third page');
|
||||
}
|
||||
// viewDidLeave() {
|
||||
// console.log('viewDidLeave third page');
|
||||
// }
|
||||
|
||||
viewWillUnload() {
|
||||
console.log('viewWillUnload third page');
|
||||
}
|
||||
// viewWillUnload() {
|
||||
// console.log('viewWillUnload third page');
|
||||
// }
|
||||
|
||||
viewDidUnload() {
|
||||
console.log('viewDidUnload third page');
|
||||
}
|
||||
// viewDidUnload() {
|
||||
// console.log('viewDidUnload third page');
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
new Routable(ThirdPage, {
|
||||
path: '/thirdpage'
|
||||
});
|
||||
|
@ -49,8 +49,8 @@ export class ViewController extends Ion {
|
||||
]);
|
||||
}
|
||||
|
||||
push(ComponentType, params = {}, opts = {}) {
|
||||
if (!ComponentType || this.isTransitioning()) {
|
||||
push(component, params = {}, opts = {}) {
|
||||
if (!component || this.isTransitioning()) {
|
||||
return Promise.reject();
|
||||
}
|
||||
|
||||
@ -74,7 +74,7 @@ export class ViewController extends Ion {
|
||||
}
|
||||
|
||||
// create a new ViewItem
|
||||
let enteringItem = new ViewItem(this, ComponentType, params);
|
||||
let enteringItem = new ViewItem(this, component, params);
|
||||
|
||||
// add the item to the stack
|
||||
this.add(enteringItem);
|
||||
@ -136,6 +136,10 @@ export class ViewController extends Ion {
|
||||
* Set the item stack to reflect the given component classes.
|
||||
*/
|
||||
setItems(components, opts = {}) {
|
||||
if (!components || !components.length) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// if animate has not been set then default to false
|
||||
opts.animate = opts.animate || false;
|
||||
|
||||
@ -162,6 +166,7 @@ export class ViewController extends Ion {
|
||||
let newBeforeItems = components.slice(0, components.length - 1);
|
||||
for (let j = 0; j < newBeforeItems.length; j++) {
|
||||
component = newBeforeItems[j];
|
||||
if (component) {
|
||||
viewItem = new ViewItem(this, component.component || component, component.params);
|
||||
viewItem.state = CACHED_STATE;
|
||||
viewItem.shouldDestroy = false;
|
||||
@ -171,19 +176,20 @@ export class ViewController extends Ion {
|
||||
this.add(viewItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get the component that will become the active item
|
||||
// it'll be the last one in the given components array
|
||||
component = components[ components.length - 1 ];
|
||||
|
||||
// transition the leaving and entering
|
||||
return this.push(component.component || component, component.params, opts);
|
||||
return this.push((component && component.component) || component, (component && component.params), opts);
|
||||
}
|
||||
|
||||
setRoot(ComponentType, params = {}, opts = {}) {
|
||||
setRoot(component, params = {}, opts = {}) {
|
||||
return this.setItems([{
|
||||
component: ComponentType,
|
||||
params: params
|
||||
component,
|
||||
params
|
||||
}], opts);
|
||||
}
|
||||
|
||||
@ -485,7 +491,7 @@ export class ViewController extends Ion {
|
||||
}
|
||||
|
||||
add(item) {
|
||||
item.id = this.id + '' + (++this._ids);
|
||||
item.id = this.id + '-' + (++this._ids);
|
||||
this.items.push(item);
|
||||
}
|
||||
|
||||
|
@ -6,9 +6,9 @@ import {NavParams} from '../nav/nav-controller';
|
||||
|
||||
export class ViewItem {
|
||||
|
||||
constructor(viewCtrl, cls, params = {}) {
|
||||
constructor(viewCtrl, component, params = {}) {
|
||||
this.viewCtrl = viewCtrl;
|
||||
this.cls = cls;
|
||||
this.component = component;
|
||||
this.params = new NavParams(params);
|
||||
this.instance = null;
|
||||
this.state = 0;
|
||||
@ -37,7 +37,7 @@ export class ViewItem {
|
||||
'class': 'nav-item'
|
||||
}
|
||||
});
|
||||
let ionViewComponent = DirectiveBinding.createFromType(this.cls, annotation);
|
||||
let ionViewComponent = DirectiveBinding.createFromType(this.component, annotation);
|
||||
|
||||
// compile the Component
|
||||
viewCtrl.compiler.compileInHost(ionViewComponent).then(componentProtoViewRef => {
|
||||
|
@ -12,7 +12,7 @@ export * from 'ionic/platform/platform'
|
||||
export * from 'ionic/platform/registry'
|
||||
|
||||
export * from 'ionic/routing/router'
|
||||
export * from 'ionic/routing/hash-url-state'
|
||||
export * from 'ionic/routing/url-state'
|
||||
|
||||
export * from 'ionic/util/click-block'
|
||||
export * from 'ionic/util/focus'
|
||||
|
@ -1,105 +0,0 @@
|
||||
import {IonicRouter} from './router';
|
||||
import * as util from '../util/util';
|
||||
|
||||
|
||||
class HashUrlStateManager {
|
||||
|
||||
constructor(window, router) {
|
||||
this.location = window.location;
|
||||
this.history = window.history;
|
||||
this.router = router;
|
||||
|
||||
window.addEventListener('popstate', ev => {
|
||||
this.onPopState(ev);
|
||||
});
|
||||
}
|
||||
|
||||
stateChange(path, type, activeView) {
|
||||
if (type == 'pop') {
|
||||
// if the popstate came from the browser's back button (and not Ionic)
|
||||
// then we shouldn't force another browser history.back()
|
||||
// only do a history.back() if the URL hasn't been updated yet
|
||||
if (this.isDifferentPath(path)) {
|
||||
this.history.back();
|
||||
}
|
||||
|
||||
} else {
|
||||
// push state change
|
||||
let enteringState = {
|
||||
path: path,
|
||||
backPath: this.router.lastPath(),
|
||||
forwardPath: null
|
||||
};
|
||||
|
||||
if (this._hasInit) {
|
||||
// update the leaving state to know what it's forward state will be
|
||||
let leavingState = util.extend(this.history.state, {
|
||||
forwardPath: enteringState.path
|
||||
});
|
||||
if (leavingState.path !== enteringState.path) {
|
||||
this.history.replaceState(leavingState, '', '#' + leavingState.path);
|
||||
}
|
||||
|
||||
if (this.isDifferentPath(path)) {
|
||||
// push the new state to the history stack since the path
|
||||
// isn't already in the location hash
|
||||
this.history.pushState(enteringState, '', '#' + enteringState.path);
|
||||
}
|
||||
|
||||
} else {
|
||||
// replace the very first load with the correct entering state info
|
||||
this.history.replaceState(enteringState, '', '#' + enteringState.path);
|
||||
this._hasInit = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onPopState(ev) {
|
||||
let newState = ev.state || {};
|
||||
let newStatePath = newState.path;
|
||||
let newStateBackPath = newState.backPath;
|
||||
let newStateForwardPath = newState.forwardPath;
|
||||
let lastLoadedStatePath = this.router.lastPath();
|
||||
|
||||
if (newStatePath === lastLoadedStatePath) {
|
||||
// do nothing if the last path is the same
|
||||
// as the "new" current state
|
||||
return;
|
||||
}
|
||||
|
||||
let activeViewCtrl = this.router.activeViewController();
|
||||
if (activeViewCtrl) {
|
||||
|
||||
if (newStateForwardPath === lastLoadedStatePath) {
|
||||
// if the last loaded state path is the same as the new
|
||||
// state's forward path then the user is moving back
|
||||
activeViewCtrl.pop();
|
||||
|
||||
} else if (newStateBackPath === lastLoadedStatePath) {
|
||||
// if the last loaded state path is the new state's
|
||||
// back path, then the user is moving forward
|
||||
this.router.loadByPath(newStatePath);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
getCurrentPath() {
|
||||
// Grab the path without the leading hash
|
||||
return new Promise(resolve => {
|
||||
resolve({
|
||||
path: this.location.hash.slice(1),
|
||||
priority: 0
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
isDifferentPath(path) {
|
||||
// check if the given path is different than the current location
|
||||
return (this.location.hash !== ('#' + path));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
IonicRouter.registerStateManager('hashurl', HashUrlStateManager);
|
@ -30,8 +30,8 @@ class StaticSegment {
|
||||
this.regex = escapeRegex(string);
|
||||
}
|
||||
|
||||
generate(params) {
|
||||
return this.string;
|
||||
generate() {
|
||||
return this.regex;
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,7 +49,6 @@ class DynamicSegment {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class StarSegment {
|
||||
constructor(name) {
|
||||
this.regex = "(.+)";
|
||||
@ -124,8 +123,6 @@ export class PathRecognizer {
|
||||
constructor(path) {
|
||||
this.segments = [];
|
||||
|
||||
// TODO: use destructuring assignment
|
||||
// see https://github.com/angular/ts2dart/issues/158
|
||||
var parsed = parsePathString(path);
|
||||
var specificity = parsed['specificity'];
|
||||
var segments = parsed['segments'];
|
||||
|
@ -6,165 +6,189 @@ import {PathRecognizer} from './path-recognizer';
|
||||
|
||||
export class IonicRouter {
|
||||
constructor(config) {
|
||||
this._routes = {};
|
||||
this._routes = [];
|
||||
this._viewCtrls = [];
|
||||
this.config(config);
|
||||
}
|
||||
|
||||
app(app) {
|
||||
this._app = app;
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
config(config) {
|
||||
if (config) {
|
||||
for (let routeName in config) {
|
||||
this.addRoute(routeName, config[routeName]);
|
||||
for (let i = 0; i < config.length; i++) {
|
||||
this.addRoute(config[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addRoute(routeName, routeConfig) {
|
||||
if (routeName && routeConfig && routeConfig.path) {
|
||||
this._routes[routeName] = new Route(routeName, routeConfig);
|
||||
addRoute(routeConfig) {
|
||||
if (routeConfig && routeConfig.path && routeConfig.component) {
|
||||
let route = new Route(routeConfig);
|
||||
if (routeConfig.root) {
|
||||
this.otherwise(routeName);
|
||||
this.otherwise(route);
|
||||
}
|
||||
this._routes.push(route);
|
||||
}
|
||||
}
|
||||
|
||||
load(window, ionicApp, ionicConfig) {
|
||||
// create each of the state manager classes
|
||||
for (let name in stateManagerClasses) {
|
||||
stateManagers[name] = new stateManagerClasses[name](window, this, ionicApp, ionicConfig);
|
||||
}
|
||||
stateManagerClasses = {};
|
||||
|
||||
return new Promise(resolve => {
|
||||
this.getCurrentPath().then(path => {
|
||||
this.loadByPath(path, this.otherwise()).then(resolve);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
loadByPath(path, fallbackRoute) {
|
||||
return new Promise(resolve => {
|
||||
let self = this;
|
||||
let activeViewCtrl = self.activeViewController();
|
||||
let matchedRoute = self.match(path) || fallbackRoute;
|
||||
|
||||
function zoneLoad() {
|
||||
self._app.zone().run(() => {
|
||||
activeViewCtrl.push(matchedRoute.cls);
|
||||
self.lastPath(matchedRoute.path);
|
||||
resolve();
|
||||
}, err => {
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
if (activeViewCtrl && matchedRoute) {
|
||||
|
||||
if (matchedRoute.cls) {
|
||||
zoneLoad();
|
||||
|
||||
} else if (matchedRoute.module) {
|
||||
System.import(matchedRoute.module).then(m => {
|
||||
if (m) {
|
||||
matchedRoute.cls = m[matchedRoute.name];
|
||||
zoneLoad();
|
||||
}
|
||||
}, err => {
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getCurrentPath() {
|
||||
// check each of the state managers and the one with the
|
||||
// highest priority wins of knowing what path we are currently at
|
||||
return new Promise(resolve => {
|
||||
|
||||
let promises = [];
|
||||
for (let name in stateManagers) {
|
||||
promises.push(stateManagers[name].getCurrentPath());
|
||||
}
|
||||
|
||||
// when all the promises have resolved then see which one wins
|
||||
Promise.all(promises).then(results => {
|
||||
let rtnPath = null;
|
||||
let highestPriority = -1;
|
||||
let state = null;
|
||||
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
state = results[i];
|
||||
if (state.path && state.priority > highestPriority) {
|
||||
rtnPath = state.path;
|
||||
}
|
||||
}
|
||||
|
||||
resolve(rtnPath);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
stateChange(type, activeView) {
|
||||
if (activeView && activeView.cls) {
|
||||
|
||||
let routeConfig = activeView.cls.route;
|
||||
if (routeConfig) {
|
||||
let matchedRoute = this.match(routeConfig.path);
|
||||
|
||||
if (matchedRoute) {
|
||||
// this fires when the app's state has changed stateChange will
|
||||
// tell each of the state managers that the state has changed, and
|
||||
// each state manager will decide what to do with this info
|
||||
// (the url state manager updates the url bar if a route was setup)
|
||||
if (activeView && activeView.component) {
|
||||
|
||||
let componentRoute = activeView.component.route;
|
||||
if (componentRoute) {
|
||||
let path = componentRoute.generate(activeView.params);
|
||||
if (path) {
|
||||
for (let name in stateManagers) {
|
||||
stateManagers[name].stateChange(matchedRoute.path, type, activeView);
|
||||
}
|
||||
|
||||
this.lastPath(matchedRoute.path);
|
||||
}
|
||||
stateManagers[name].stateChange(path, type, activeView);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lastPath(val) {
|
||||
if (arguments.length) {
|
||||
this._lastPath = val;
|
||||
}
|
||||
return this._lastPath;
|
||||
}
|
||||
|
||||
match(path) {
|
||||
matchPaths(paths) {
|
||||
// load each of paths to a component
|
||||
let components = [];
|
||||
let route;
|
||||
|
||||
if (paths) {
|
||||
for (let i = 0; i < paths.length; i++) {
|
||||
route = this.matchPath(paths[i]);
|
||||
if (route && route.component) {
|
||||
components.push(route.component);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return components;
|
||||
}
|
||||
|
||||
matchPath(path) {
|
||||
// takes a string path and loops through each of the setup
|
||||
// routes to see if the path matches any of the routes
|
||||
// the matched path with the highest specifity wins
|
||||
let matchedRoute = null;
|
||||
let route = null;
|
||||
let routeMatch = null;
|
||||
let highestSpecifity = 0;
|
||||
|
||||
for (let routeName in this._routes) {
|
||||
routeMatch = this._routes[routeName].match(path);
|
||||
for (let i = 0; i < this._routes.length; i++) {
|
||||
route = this._routes[i];
|
||||
routeMatch = route.match(path);
|
||||
|
||||
if (routeMatch.match && (!matchedRoute || routeMatch.specificity > highestSpecifity)) {
|
||||
matchedRoute = this._routes[routeName];
|
||||
highestSpecifity = routeMatch.specificity;
|
||||
if (routeMatch && (!matchedRoute || route.specificity > matchedRoute.specificity)) {
|
||||
matchedRoute = route;
|
||||
}
|
||||
}
|
||||
return matchedRoute;
|
||||
}
|
||||
|
||||
load(window, ionicApp, ionicConfig) {
|
||||
// load is called when the app has finished loading each state
|
||||
// manager gets a chance to say what path the app should be at
|
||||
let viewCtrl = this.viewController();
|
||||
if (!viewCtrl || !this._routes.length) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
let resolve;
|
||||
let promise = new Promise(res => { resolve = res; });
|
||||
|
||||
// get the initial load paths from the state manager with the highest priorty
|
||||
this.getManagerPaths(window, ionicApp, ionicConfig).then(paths => {
|
||||
|
||||
// load all of the paths the highest priority state manager has given
|
||||
let components = this.matchPaths(paths);
|
||||
|
||||
if (!components.length && this.otherwise()) {
|
||||
// the state manager did not find and loaded components
|
||||
// use the "otherwise" path
|
||||
components = [this.otherwise().component];
|
||||
}
|
||||
|
||||
this.app.zoneRun(() => {
|
||||
viewCtrl.setItems(components).then(resolve);
|
||||
});
|
||||
});
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
getManagerPaths(window, ionicApp, ionicConfig) {
|
||||
// loop through all of the state managers and load their paths
|
||||
// the state manager with valid paths and highest priority wins
|
||||
let resolve;
|
||||
let promise = new Promise(res => { resolve = res; });
|
||||
|
||||
// load each of the state managers
|
||||
let stateManagerPromises = [];
|
||||
for (let name in stateManagerClasses) {
|
||||
stateManagers[name] = new stateManagerClasses[name](window, this, ionicApp, ionicConfig);
|
||||
stateManagerPromises.push( stateManagers[name].load() );
|
||||
}
|
||||
|
||||
// when all the state manager loads have resolved then see which one wins
|
||||
Promise.all(stateManagerPromises).then(stateManagerLoadResults => {
|
||||
|
||||
// now that all the state managers are loaded
|
||||
// get the highest priority state manager's paths
|
||||
let stateLoadResult = null;
|
||||
let paths = null;
|
||||
let highestPriority = -1;
|
||||
|
||||
for (let i = 0; i < stateManagerLoadResults.length; i++) {
|
||||
stateLoadResult = stateManagerLoadResults[i];
|
||||
if (stateLoadResult && stateLoadResult.paths.length && stateLoadResult.priority > highestPriority) {
|
||||
paths = stateLoadResult.paths;
|
||||
highestPriority = stateLoadResult.priority;
|
||||
}
|
||||
}
|
||||
|
||||
resolve(paths);
|
||||
});
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
push(path) {
|
||||
let viewCtrl = this.viewController();
|
||||
if (viewCtrl) {
|
||||
let matchedRoute = this.matchPath(path);
|
||||
if (matchedRoute && matchedRoute.component) {
|
||||
this.app.zoneRun(() => {
|
||||
viewCtrl.push(matchedRoute.component, matchedRoute.params, {});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pop() {
|
||||
let viewCtrl = this.viewController();
|
||||
if (viewCtrl) {
|
||||
this.app.zoneRun(() => {
|
||||
viewCtrl.pop();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
otherwise(val) {
|
||||
if (arguments.length) {
|
||||
this._otherwise = val;
|
||||
|
||||
} else if (this._otherwise) {
|
||||
return this._routes[this._otherwise];
|
||||
}
|
||||
return this._otherwise
|
||||
}
|
||||
|
||||
addViewController(viewCtrl) {
|
||||
this._viewCtrls.push(viewCtrl);
|
||||
}
|
||||
|
||||
activeViewController() {
|
||||
viewController() {
|
||||
if (this._viewCtrls.length) {
|
||||
return this._viewCtrls[ this._viewCtrls.length - 1 ];
|
||||
}
|
||||
@ -185,35 +209,21 @@ let stateManagerClasses = {};
|
||||
let stateManagers = {};
|
||||
|
||||
|
||||
export class Routable {
|
||||
constructor(cls, routeConfig) {
|
||||
cls.route = routeConfig;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Route {
|
||||
constructor(name, routeConfig) {
|
||||
this.name = name;
|
||||
this.cls = null;
|
||||
constructor(routeConfig) {
|
||||
util.extend(this, routeConfig);
|
||||
this.recognizer = new PathRecognizer(this.path);
|
||||
this.specificity = this.recognizer.specificity;
|
||||
|
||||
this.component.route = this;
|
||||
}
|
||||
|
||||
match(matchPath) {
|
||||
let routeMatch = new RouteMatch(this, matchPath);
|
||||
if (routeMatch) {
|
||||
return routeMatch;
|
||||
match(path) {
|
||||
return RegExpWrapper.firstMatch(this.recognizer.regex, path);
|
||||
}
|
||||
return false;
|
||||
|
||||
generate(params) {
|
||||
return this.recognizer.generate(params);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class RouteMatch {
|
||||
constructor(route, matchPath) {
|
||||
this.route = route;
|
||||
this.specificity = route.recognizer.specificity;
|
||||
this.match = RegExpWrapper.firstMatch(route.recognizer.regex, matchPath);
|
||||
}
|
||||
}
|
||||
|
173
ionic/routing/url-state.ts
Normal file
173
ionic/routing/url-state.ts
Normal file
@ -0,0 +1,173 @@
|
||||
import {IonicRouter} from './router';
|
||||
import * as util from '../util/util';
|
||||
|
||||
|
||||
class UrlStateManager {
|
||||
|
||||
constructor(window, router) {
|
||||
this.location = window.location;
|
||||
this.history = window.history;
|
||||
this.ls = window.localStorage;
|
||||
this.router = router;
|
||||
|
||||
// overkill for location change listeners, but ensures we
|
||||
// know when the location has changed. Only 1 of the listeners
|
||||
// will actually do the work, the other will be skipped.
|
||||
window.addEventListener('popstate', () => {
|
||||
this.onLocationChange();
|
||||
});
|
||||
window.addEventListener('hashchange', () => {
|
||||
this.onLocationChange();
|
||||
});
|
||||
}
|
||||
|
||||
load() {
|
||||
let paths = [this.getCurrentPath()];
|
||||
let savedPaths = this.paths();
|
||||
|
||||
if (savedPaths[savedPaths.length - 1] == paths[0]) {
|
||||
// the last path in the saved paths is the same as the
|
||||
// current path, so use the saved paths to rebuild the history
|
||||
paths = savedPaths;
|
||||
|
||||
} else {
|
||||
// the current path is not the same as the last path in the
|
||||
// saved history, so the saved history is no good, erase it
|
||||
this.paths([]);
|
||||
}
|
||||
|
||||
return Promise.resolve({
|
||||
paths: paths,
|
||||
priority: 0
|
||||
});
|
||||
}
|
||||
|
||||
stateChange(path, type, activeView) {
|
||||
let savedPaths = this.paths();
|
||||
|
||||
// check if the given path is different than the current location
|
||||
let isDifferentPath = (this.getCurrentPath() !== path);
|
||||
|
||||
if (type == 'pop') {
|
||||
// if the popstate came from the browser's back button (and not Ionic)
|
||||
// then we shouldn't force another browser history.back()
|
||||
// only do a history.back() if the URL hasn't been updated yet
|
||||
if (isDifferentPath) {
|
||||
this.history.back();
|
||||
}
|
||||
|
||||
if (savedPaths.length && savedPaths[savedPaths.length - 1] != path) {
|
||||
// only if the last item in the saved paths
|
||||
// equals this path then it can be removed
|
||||
savedPaths.pop();
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
if (this._hasInit) {
|
||||
if (isDifferentPath) {
|
||||
// push the new state to the history stack since the path
|
||||
// isn't already in the location hash
|
||||
this.history.pushState(path, '', '#' + path);
|
||||
}
|
||||
|
||||
} else {
|
||||
// replace the very first load with the correct entering state info
|
||||
this.history.replaceState(path, '', '#' + path);
|
||||
this._hasInit = true;
|
||||
}
|
||||
|
||||
if (savedPaths[savedPaths.length - 1] != path) {
|
||||
// only if the last item in the saved paths does
|
||||
// not equal this path then it can be added
|
||||
savedPaths.push(path);
|
||||
|
||||
// don't allow the history to grow too large
|
||||
if (savedPaths.length > MAX_PATH_STORE) {
|
||||
savedPaths = savedPaths.slice( savedPaths.length - MAX_PATH_STORE );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// save the new path data
|
||||
this.paths(savedPaths);
|
||||
|
||||
// ensure this resets
|
||||
this._currentPath = null;
|
||||
}
|
||||
|
||||
onLocationChange() {
|
||||
let currentPath = this.getCurrentPath();
|
||||
|
||||
if (currentPath == this._currentPath) {
|
||||
// absolutely no change since last onLocationChange
|
||||
return;
|
||||
}
|
||||
|
||||
// keep in-memory the current path to quickly tell if things have changed
|
||||
this._currentPath = currentPath;
|
||||
|
||||
// load up the saved paths
|
||||
let savedPaths = this.paths();
|
||||
|
||||
if (currentPath === savedPaths[savedPaths.length - 1]) {
|
||||
// do nothing if the last saved path is
|
||||
// the same as the current path
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentPath === savedPaths[savedPaths.length - 2]) {
|
||||
// the user is moving back
|
||||
this.router.pop();
|
||||
|
||||
} else {
|
||||
// the user is moving forward
|
||||
this.router.push(currentPath);
|
||||
}
|
||||
}
|
||||
|
||||
paths(val) {
|
||||
if (arguments.length) {
|
||||
// set in-memory data
|
||||
this._paths = val;
|
||||
|
||||
// set localStorage data
|
||||
try {
|
||||
this.ls.setItem(PATH_STORE_KEY, JSON.stringify(val));
|
||||
} catch(e) {}
|
||||
|
||||
} else {
|
||||
|
||||
if (!this._paths) {
|
||||
// we don't already have data in-memory
|
||||
|
||||
// see if we have data in localStorage
|
||||
try {
|
||||
let strData = this.ls.getItem(PATH_STORE_KEY);
|
||||
if (strData) {
|
||||
this._paths = JSON.parse(strData);
|
||||
}
|
||||
} catch(e) {}
|
||||
|
||||
// if not in localStorage yet then create new path data
|
||||
if (!this._paths) {
|
||||
this._paths = [];
|
||||
}
|
||||
}
|
||||
|
||||
// return the in-memory data
|
||||
return this._paths;
|
||||
}
|
||||
}
|
||||
|
||||
getCurrentPath() {
|
||||
// remove leading # to get the path
|
||||
return this.location.hash.slice(1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const PATH_STORE_KEY = 'ionic:history';
|
||||
const MAX_PATH_STORE = 20;
|
||||
|
||||
IonicRouter.registerStateManager('url', UrlStateManager);
|
Reference in New Issue
Block a user