router updates

This commit is contained in:
Adam Bradley
2015-07-13 10:00:57 -05:00
parent 1427c254c5
commit 6576dd450e
13 changed files with 448 additions and 372 deletions

View File

@ -26,7 +26,11 @@ export class IonicApp {
load(appRef) { load(appRef) {
this.ref(appRef); this.ref(appRef);
this.zone(this.injector().get(NgZone)); this._zone = this.injector().get(NgZone);
}
title(val) {
document.title = val;
} }
ref(val) { ref(val) {
@ -40,11 +44,8 @@ export class IonicApp {
return this._ref.injector; return this._ref.injector;
} }
zone(val) { zoneRun(fn) {
if (arguments.length) { this._zone.run(fn);
this._zone = val;
}
return this._zone;
} }
stateChange(type, activeView) { stateChange(type, activeView) {
@ -74,10 +75,10 @@ export class IonicApp {
* Create and append the given component into the root * Create and append the given component into the root
* element of the app. * 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 * @return Promise that resolves with the ContainerRef created
*/ */
appendComponent(cls: Type, context=null) { appendComponent(component: Type, context=null) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let injector = this.injector(); let injector = this.injector();
let compiler = injector.get(Compiler); let compiler = injector.get(Compiler);
@ -85,7 +86,7 @@ export class IonicApp {
let rootComponentRef = this._ref._hostComponent; let rootComponentRef = this._ref._hostComponent;
let viewContainerLocation = rootComponentRef.location; let viewContainerLocation = rootComponentRef.location;
compiler.compileInHost(cls).then(protoViewRef => { compiler.compileInHost(component).then(protoViewRef => {
let atIndex = 0; let atIndex = 0;
let hostViewRef = viewMngr.createViewInContainer( let hostViewRef = viewMngr.createViewInContainer(
@ -158,8 +159,8 @@ function initApp(window, document, config) {
return app; return app;
} }
export function ionicBootstrap(cls, config, router) { export function ionicBootstrap(component, config, router) {
return new Promise((resolve, reject) => { return new Promise(resolve => {
try { try {
// get the user config, or create one if wasn't passed in // get the user config, or create one if wasn't passed in
if (typeof config !== IonicConfig) { if (typeof config !== IonicConfig) {
@ -202,7 +203,7 @@ export function ionicBootstrap(cls, config, router) {
bind(Modal).toValue(modal) bind(Modal).toValue(modal)
]; ];
bootstrap(cls, injectableBindings).then(appRef => { bootstrap(component, injectableBindings).then(appRef => {
app.load(appRef); app.load(appRef);
router.load(window, app, config).then(() => { router.load(window, app, config).then(() => {
@ -212,12 +213,10 @@ export function ionicBootstrap(cls, config, router) {
}).catch(err => { }).catch(err => {
console.error('ionicBootstrap', err); console.error('ionicBootstrap', err);
reject(err);
}); });
} catch (err) { } catch (err) {
console.error('ionicBootstrap', err); console.error(err);
reject(err);
} }
}); });
} }

View File

@ -4,6 +4,7 @@ import {ProtoViewRef} from 'angular2/src/core/compiler/view_ref';
import {Ion} from '../ion'; import {Ion} from '../ion';
import {IonicConfig} from '../../config/config'; import {IonicConfig} from '../../config/config';
import {IonicComponent} from '../../config/annotations'; import {IonicComponent} from '../../config/annotations';
import {IonicApp} from '../app/app';
import {ViewItem} from '../view/view-item'; import {ViewItem} from '../view/view-item';
import * as dom from '../../util/dom'; import * as dom from '../../util/dom';
@ -42,9 +43,10 @@ import * as dom from '../../util/dom';
] ]
}) })
export class Navbar extends Ion { export class Navbar extends Ion {
constructor(item: ViewItem, elementRef: ElementRef, ionicConfig: IonicConfig) { constructor(item: ViewItem, elementRef: ElementRef, ionicConfig: IonicConfig, app: IonicApp) {
super(elementRef, ionicConfig); super(elementRef, ionicConfig);
this.app = app;
this.eleRef = elementRef; this.eleRef = elementRef;
this.itemEles = []; this.itemEles = [];
item.navbarView(this); item.navbarView(this);
@ -130,8 +132,10 @@ export class Navbar extends Ion {
} }
didEnter() { didEnter() {
setTimeout(() => {
const titleEle = this._ttEle || (this._ttEle = this.eleRef.nativeElement.querySelector('ion-title')); const titleEle = this._ttEle || (this._ttEle = this.eleRef.nativeElement.querySelector('ion-title'));
this.app.title(titleEle.textContent);
setTimeout(() => {
//this.titleText((titleEle && titleEle.textContent) || ''); //this.titleText((titleEle && titleEle.textContent) || '');
}, 32); }, 32);
} }

View File

@ -1,21 +1,25 @@
import {App} from 'ionic/ionic'; import {App} from 'ionic/ionic';
import {FirstPage} from './pages/first-page';
import {SecondPage} from './pages/second-page';
import {ThirdPage} from './pages/third-page';
@App({ @App({
routes: { routes: [
'FirstPage': { {
'path': '/firstpage', path: '/firstpage',
'module': 'dist/examples/nav/basic/pages/first-page', component: FirstPage,
'root': true root: true
}, },
'SecondPage': { {
'path': '/secondpage', path: '/secondpage',
'module': 'dist/examples/nav/basic/pages/second-page', component: SecondPage,
},
'ThirdPage': {
'path': '/thirdpage',
'module': 'dist/examples/nav/basic/pages/third-page',
}, },
{
path: '/thirdpage',
component: ThirdPage,
} }
]
}) })
class MyApp {} class MyApp {}

View File

@ -1,5 +1,5 @@
import {IonicView, IonicConfig, IonicApp} from 'ionic/ionic'; 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 {SecondPage} from './second-page';
import {ThirdPage} from './third-page'; import {ThirdPage} from './third-page';
@ -52,39 +52,35 @@ export class FirstPage {
this.nav.setItems(items); this.nav.setItems(items);
} }
viewLoaded() { // viewLoaded() {
console.log('viewLoaded first page'); // console.log('viewLoaded first page');
} // }
viewWillEnter() { // viewWillEnter() {
console.log('viewWillEnter first page'); // console.log('viewWillEnter first page');
} // }
viewDidEnter() { // viewDidEnter() {
console.log('viewDidEnter first page'); // console.log('viewDidEnter first page');
} // }
viewWillLeave() { // viewWillLeave() {
console.log('viewWillLeave first page'); // console.log('viewWillLeave first page');
} // }
viewDidLeave() { // viewDidLeave() {
console.log('viewDidLeave first page'); // console.log('viewDidLeave first page');
} // }
viewWillUnload() { // viewWillUnload() {
console.log('viewWillUnload first page'); // console.log('viewWillUnload first page');
} // }
viewDidUnload() { // viewDidUnload() {
console.log('viewDidUnload first page'); // console.log('viewDidUnload first page');
} // }
push() { push() {
this.nav.push(SecondPage, { id: 8675309, myData: [1,2,3,4] }, { animation: 'ios' }); this.nav.push(SecondPage, { id: 8675309, myData: [1,2,3,4] }, { animation: 'ios' });
} }
} }
new Routable(FirstPage, {
path: '/firstpage'
});

View File

@ -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 {ThirdPage} from './third-page';
import {FirstPage} from './first-page'; import {FirstPage} from './first-page';
@ -45,36 +45,32 @@ export class SecondPage {
this.nav.push(ThirdPage); this.nav.push(ThirdPage);
} }
viewLoaded() { // viewLoaded() {
console.log('viewLoaded second page'); // console.log('viewLoaded second page');
} // }
viewWillEnter() { // viewWillEnter() {
console.log('viewWillEnter second page'); // console.log('viewWillEnter second page');
} // }
viewDidEnter() { // viewDidEnter() {
console.log('viewDidEnter second page'); // console.log('viewDidEnter second page');
} // }
viewWillLeave() { // viewWillLeave() {
console.log('viewWillLeave second page'); // console.log('viewWillLeave second page');
} // }
viewDidLeave() { // viewDidLeave() {
console.log('viewDidLeave second page'); // console.log('viewDidLeave second page');
} // }
viewWillUnload() { // viewWillUnload() {
console.log('viewWillUnload second page'); // console.log('viewWillUnload second page');
} // }
viewDidUnload() { // viewDidUnload() {
console.log('viewDidUnload second page'); // console.log('viewDidUnload second page');
} // }
} }
new Routable(SecondPage, {
path: '/secondpage'
});

View File

@ -1,4 +1,4 @@
import {IonicView, Routable, NavController} from 'ionic/ionic'; import {IonicView, NavController} from 'ionic/ionic';
@IonicView({ @IonicView({
@ -23,36 +23,32 @@ export class ThirdPage {
this.nav.pop() this.nav.pop()
} }
viewLoaded() { // viewLoaded() {
console.log('viewLoaded third page'); // console.log('viewLoaded third page');
} // }
viewWillEnter() { // viewWillEnter() {
console.log('viewWillEnter third page'); // console.log('viewWillEnter third page');
} // }
viewDidEnter() { // viewDidEnter() {
console.log('viewDidEnter third page'); // console.log('viewDidEnter third page');
} // }
viewWillLeave() { // viewWillLeave() {
console.log('viewWillLeave third page'); // console.log('viewWillLeave third page');
} // }
viewDidLeave() { // viewDidLeave() {
console.log('viewDidLeave third page'); // console.log('viewDidLeave third page');
} // }
viewWillUnload() { // viewWillUnload() {
console.log('viewWillUnload third page'); // console.log('viewWillUnload third page');
} // }
viewDidUnload() { // viewDidUnload() {
console.log('viewDidUnload third page'); // console.log('viewDidUnload third page');
} // }
} }
new Routable(ThirdPage, {
path: '/thirdpage'
});

View File

@ -49,8 +49,8 @@ export class ViewController extends Ion {
]); ]);
} }
push(ComponentType, params = {}, opts = {}) { push(component, params = {}, opts = {}) {
if (!ComponentType || this.isTransitioning()) { if (!component || this.isTransitioning()) {
return Promise.reject(); return Promise.reject();
} }
@ -74,7 +74,7 @@ export class ViewController extends Ion {
} }
// create a new ViewItem // create a new ViewItem
let enteringItem = new ViewItem(this, ComponentType, params); let enteringItem = new ViewItem(this, component, params);
// add the item to the stack // add the item to the stack
this.add(enteringItem); this.add(enteringItem);
@ -136,6 +136,10 @@ export class ViewController extends Ion {
* Set the item stack to reflect the given component classes. * Set the item stack to reflect the given component classes.
*/ */
setItems(components, opts = {}) { setItems(components, opts = {}) {
if (!components || !components.length) {
return Promise.resolve();
}
// if animate has not been set then default to false // if animate has not been set then default to false
opts.animate = opts.animate || false; opts.animate = opts.animate || false;
@ -162,6 +166,7 @@ export class ViewController extends Ion {
let newBeforeItems = components.slice(0, components.length - 1); let newBeforeItems = components.slice(0, components.length - 1);
for (let j = 0; j < newBeforeItems.length; j++) { for (let j = 0; j < newBeforeItems.length; j++) {
component = newBeforeItems[j]; component = newBeforeItems[j];
if (component) {
viewItem = new ViewItem(this, component.component || component, component.params); viewItem = new ViewItem(this, component.component || component, component.params);
viewItem.state = CACHED_STATE; viewItem.state = CACHED_STATE;
viewItem.shouldDestroy = false; viewItem.shouldDestroy = false;
@ -171,19 +176,20 @@ export class ViewController extends Ion {
this.add(viewItem); this.add(viewItem);
} }
} }
}
// get the component that will become the active item // get the component that will become the active item
// it'll be the last one in the given components array // it'll be the last one in the given components array
component = components[ components.length - 1 ]; component = components[ components.length - 1 ];
// transition the leaving and entering // 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([{ return this.setItems([{
component: ComponentType, component,
params: params params
}], opts); }], opts);
} }
@ -485,7 +491,7 @@ export class ViewController extends Ion {
} }
add(item) { add(item) {
item.id = this.id + '' + (++this._ids); item.id = this.id + '-' + (++this._ids);
this.items.push(item); this.items.push(item);
} }

View File

@ -6,9 +6,9 @@ import {NavParams} from '../nav/nav-controller';
export class ViewItem { export class ViewItem {
constructor(viewCtrl, cls, params = {}) { constructor(viewCtrl, component, params = {}) {
this.viewCtrl = viewCtrl; this.viewCtrl = viewCtrl;
this.cls = cls; this.component = component;
this.params = new NavParams(params); this.params = new NavParams(params);
this.instance = null; this.instance = null;
this.state = 0; this.state = 0;
@ -37,7 +37,7 @@ export class ViewItem {
'class': 'nav-item' 'class': 'nav-item'
} }
}); });
let ionViewComponent = DirectiveBinding.createFromType(this.cls, annotation); let ionViewComponent = DirectiveBinding.createFromType(this.component, annotation);
// compile the Component // compile the Component
viewCtrl.compiler.compileInHost(ionViewComponent).then(componentProtoViewRef => { viewCtrl.compiler.compileInHost(ionViewComponent).then(componentProtoViewRef => {

View File

@ -12,7 +12,7 @@ export * from 'ionic/platform/platform'
export * from 'ionic/platform/registry' export * from 'ionic/platform/registry'
export * from 'ionic/routing/router' 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/click-block'
export * from 'ionic/util/focus' export * from 'ionic/util/focus'

View File

@ -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);

View File

@ -30,8 +30,8 @@ class StaticSegment {
this.regex = escapeRegex(string); this.regex = escapeRegex(string);
} }
generate(params) { generate() {
return this.string; return this.regex;
} }
} }
@ -49,7 +49,6 @@ class DynamicSegment {
} }
} }
class StarSegment { class StarSegment {
constructor(name) { constructor(name) {
this.regex = "(.+)"; this.regex = "(.+)";
@ -124,8 +123,6 @@ export class PathRecognizer {
constructor(path) { constructor(path) {
this.segments = []; this.segments = [];
// TODO: use destructuring assignment
// see https://github.com/angular/ts2dart/issues/158
var parsed = parsePathString(path); var parsed = parsePathString(path);
var specificity = parsed['specificity']; var specificity = parsed['specificity'];
var segments = parsed['segments']; var segments = parsed['segments'];

View File

@ -6,165 +6,189 @@ import {PathRecognizer} from './path-recognizer';
export class IonicRouter { export class IonicRouter {
constructor(config) { constructor(config) {
this._routes = {}; this._routes = [];
this._viewCtrls = []; this._viewCtrls = [];
this.config(config); this.config(config);
} }
app(app) { app(app) {
this._app = app; this.app = app;
} }
config(config) { config(config) {
if (config) { if (config) {
for (let routeName in config) { for (let i = 0; i < config.length; i++) {
this.addRoute(routeName, config[routeName]); this.addRoute(config[i]);
} }
} }
} }
addRoute(routeName, routeConfig) { addRoute(routeConfig) {
if (routeName && routeConfig && routeConfig.path) { if (routeConfig && routeConfig.path && routeConfig.component) {
this._routes[routeName] = new Route(routeName, routeConfig); let route = new Route(routeConfig);
if (routeConfig.root) { 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) { stateChange(type, activeView) {
if (activeView && activeView.cls) { // this fires when the app's state has changed stateChange will
// tell each of the state managers that the state has changed, and
let routeConfig = activeView.cls.route; // each state manager will decide what to do with this info
if (routeConfig) { // (the url state manager updates the url bar if a route was setup)
let matchedRoute = this.match(routeConfig.path); if (activeView && activeView.component) {
if (matchedRoute) {
let componentRoute = activeView.component.route;
if (componentRoute) {
let path = componentRoute.generate(activeView.params);
if (path) {
for (let name in stateManagers) { for (let name in stateManagers) {
stateManagers[name].stateChange(matchedRoute.path, type, activeView); stateManagers[name].stateChange(path, type, activeView);
}
this.lastPath(matchedRoute.path);
}
} }
} }
} }
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 matchedRoute = null;
let route = null;
let routeMatch = null; let routeMatch = null;
let highestSpecifity = 0;
for (let routeName in this._routes) { for (let i = 0; i < this._routes.length; i++) {
routeMatch = this._routes[routeName].match(path); route = this._routes[i];
routeMatch = route.match(path);
if (routeMatch.match && (!matchedRoute || routeMatch.specificity > highestSpecifity)) { if (routeMatch && (!matchedRoute || route.specificity > matchedRoute.specificity)) {
matchedRoute = this._routes[routeName]; matchedRoute = route;
highestSpecifity = routeMatch.specificity;
} }
} }
return matchedRoute; 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) { otherwise(val) {
if (arguments.length) { if (arguments.length) {
this._otherwise = val; this._otherwise = val;
} else if (this._otherwise) {
return this._routes[this._otherwise];
} }
return this._otherwise
} }
addViewController(viewCtrl) { addViewController(viewCtrl) {
this._viewCtrls.push(viewCtrl); this._viewCtrls.push(viewCtrl);
} }
activeViewController() { viewController() {
if (this._viewCtrls.length) { if (this._viewCtrls.length) {
return this._viewCtrls[ this._viewCtrls.length - 1 ]; return this._viewCtrls[ this._viewCtrls.length - 1 ];
} }
@ -185,35 +209,21 @@ let stateManagerClasses = {};
let stateManagers = {}; let stateManagers = {};
export class Routable {
constructor(cls, routeConfig) {
cls.route = routeConfig;
}
}
class Route { class Route {
constructor(name, routeConfig) { constructor(routeConfig) {
this.name = name;
this.cls = null;
util.extend(this, routeConfig); util.extend(this, routeConfig);
this.recognizer = new PathRecognizer(this.path); this.recognizer = new PathRecognizer(this.path);
this.specificity = this.recognizer.specificity;
this.component.route = this;
} }
match(matchPath) { match(path) {
let routeMatch = new RouteMatch(this, matchPath); return RegExpWrapper.firstMatch(this.recognizer.regex, path);
if (routeMatch) {
return routeMatch;
} }
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
View 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);