ionic router update

This commit is contained in:
Adam Bradley
2015-08-21 23:04:23 -05:00
parent 5b5e32ded5
commit 26e0117a3f
18 changed files with 259 additions and 681 deletions

View File

@ -24,6 +24,7 @@ export * from 'ionic/components/modal/modal'
export * from 'ionic/components/nav/nav'
export * from 'ionic/components/nav/nav-controller'
export * from 'ionic/components/nav/nav-push'
export * from 'ionic/components/nav/nav-router'
export * from 'ionic/components/nav-bar/nav-bar'
export * from 'ionic/components/popup/popup'
export * from 'ionic/components/slides/slides'
@ -36,3 +37,4 @@ export * from 'ionic/components/switch/switch'
export * from 'ionic/components/tabs/tabs'
export * from 'ionic/components/tabs/tab'
export * from 'ionic/components/toolbar/toolbar'
export * from 'ionic/components/view/view-item'

View File

@ -1,6 +1,6 @@
import {Component, View, bootstrap, ElementRef, NgZone, bind, DynamicComponentLoader, Injector} from 'angular2/angular2';
import {routerInjectables, HashLocationStrategy, LocationStrategy, Router} from 'angular2/router';
import {IonicRouter} from '../../routing/router';
import {IonicConfig} from '../../config/config';
import {Platform} from '../../platform/platform';
import * as util from '../../util/util';
@ -19,8 +19,6 @@ export class IonicApp {
// Our component registry map
this.components = {};
this._activeViewId = null;
}
load(appRef) {
@ -54,17 +52,6 @@ export class IonicApp {
this._zone.run(fn);
}
stateChange(type, activeView) {
if (this._activeViewId !== activeView.id) {
this.router.stateChange(type, activeView);
this._activeViewId = activeView.id;
}
}
stateClear() {
this.router.stateClear();
}
/**
* Register a known component with a key, for easy lookups later.
*/
@ -175,7 +162,7 @@ class RootAnchor {
}
}
export function ionicBootstrap(rootComponentType, config, router) {
export function ionicBootstrap(rootComponentType, config) {
return new Promise(resolve => {
try {
// get the user config, or create one if wasn't passed in
@ -197,15 +184,6 @@ export function ionicBootstrap(rootComponentType, config, router) {
// prepare the ready promise to fire....when ready
Platform.prepareReady(config);
// setup router
if (typeof router !== IonicRouter) {
router = new IonicRouter(router);
}
router.app(app);
// TODO: don't wire these together
app.router = router;
// TODO: probs need a better way to inject global injectables
let actionMenu = new ActionMenu(app, config);
let modal = new Modal(app, config);
@ -215,10 +193,11 @@ export function ionicBootstrap(rootComponentType, config, router) {
let appBindings = Injector.resolve([
bind(IonicApp).toValue(app),
bind(IonicConfig).toValue(config),
bind(IonicRouter).toValue(router),
bind(ActionMenu).toValue(actionMenu),
bind(Modal).toValue(modal),
bind(Popup).toValue(popup)
bind(Popup).toValue(popup),
routerInjectables,
bind(LocationStrategy).toClass(HashLocationStrategy)
]);
bootstrap(rootComponentType, appBindings).then(appRef => {
@ -240,10 +219,7 @@ export function ionicBootstrap(rootComponentType, config, router) {
console.error(err)
});
router.load(window, app, config).then(() => {
// resolve that the app has loaded
resolve(app);
});
}).catch(err => {
console.error('ionicBootstrap', err);

View File

@ -1,10 +1,6 @@
it('should check apple via checkbox element click', function() {
it('should check apple, enable/check grape, submit form', function() {
element(by.css('#e2eAppleCheckbox')).click();
});
it('should enable/check grape via buttons and submit form', function() {
element(by.css('#e2eGrapeDisabled')).click();
element(by.css('#e2eGrapeChecked')).click();
element(by.css('#e2eSubmit')).click();

View File

@ -37,7 +37,10 @@ export class NavController {
}
export class NavParams {
constructor(params) {
extend(this, params);
constructor(data) {
this.data = data || {};
}
get(param) {
return this.data[param];
}
}

View File

@ -0,0 +1,81 @@
import {Directive, ElementRef, DynamicComponentLoader, Attribute} from 'angular2/angular2';
import {
RouterOutlet,
Router,
ComponentInstruction,
Instruction,
Location} from 'angular2/router';
import {Nav} from './nav';
@Directive({
selector: 'ion-nav'
})
export class NavRouter extends RouterOutlet {
constructor(_elementRef: ElementRef, _loader: DynamicComponentLoader,
_parentRouter: Router, @Attribute('name') nameAttr: string,
nav: Nav) {
super(_elementRef, _loader, _parentRouter, nameAttr);
// Nav is Ionic's ViewController, which we injected into this class
this.nav = nav;
// register this router with Ionic's ViewController
// Ionic's ViewController will call this NavRouter's "stateChange"
// method when the ViewController has...changed its state
nav.registerRouter(this);
}
_activate(instruction: ComponentInstruction): Promise<any> {
var previousInstruction = this._currentInstruction;
this._currentInstruction = instruction;
var componentType = instruction.componentType;
this.childRouter = this._parentRouter.childRouter(componentType);
// tell the ViewController which componentType, and it's params, to navigate to
this.nav.push(componentType, instruction.params);
}
stateChange(type, viewItem) {
// stateChange is called by Ionic's ViewController
// type could be "push" or "pop"
// viewItem is Ionic's ViewItem class, which has the properties "componentType" and "params"
// only do an update if there's an actual view change
if (!viewItem || this._activeViewId === viewItem.id) return;
this._activeViewId = viewItem.id;
// get the best PathRecognizer for this view's componentType
let pathRecognizer = this.getPathRecognizerByComponent(viewItem.componentType);
if (pathRecognizer) {
// generate a componentInstruction from the view's PathRecognizer and params
let componentInstruction = pathRecognizer.generate(viewItem.params.data);
// create an Instruction from the componentInstruction
let instruction = new Instruction(componentInstruction, null);
// update the browser's URL
this._parentRouter.navigateInstruction(instruction);
}
}
getPathRecognizerByComponent(componentType) {
// given a componentType, figure out the best PathRecognizer to use
let rules = this._parentRouter.registry._rules;
let pathRecognizer = null;
rules.forEach((rule) => {
pathRecognizer = rule.matchers.find((matcherPathRecognizer) => {
return (matcherPathRecognizer.handler.componentType === componentType);
});
});
return pathRecognizer;
}
}

View File

@ -1,25 +1,12 @@
import {App} from 'ionic/ionic';
import {App, NavController} from 'ionic/ionic';
import {FirstPage} from './pages/first-page';
import {SecondPage} from './pages/second-page';
import {ThirdPage} from './pages/third-page';
@App({
routes: [
{
path: '/firstpage',
component: FirstPage,
root: true
},
{
path: '/secondpage',
component: SecondPage,
},
{
path: '/thirdpage',
component: ThirdPage,
}
]
template: '<ion-nav [root]="root"></ion-nav>'
})
class MyApp {}
class E2EApp {
constructor() {
this.root = FirstPage;
}
}

View File

@ -0,0 +1,56 @@
import {RouteConfig, Location} from 'angular2/router';
import {App, IonicView, NavParams, ViewItem} from 'ionic/ionic';
@IonicView({templateUrl: 'view1.html'})
class View1Cmp {
constructor(location: Location, viewItem: ViewItem) {
this.path = location.path();
this.viewItem = viewItem;
console.log(`View1Cmp, path: ${this.path}`);
}
viewDidEnter() {
this.windowHash = window.location.hash;
}
}
@IonicView({templateUrl: 'view2.html'})
class View2Cmp {
constructor(location: Location, viewItem: ViewItem) {
this.path = location.path();
this.viewItem = viewItem;
console.log(`View2Cmp, path: ${this.path}`);
}
viewDidEnter() {
this.windowHash = window.location.hash;
}
}
@IonicView({templateUrl: 'view3.html'})
class View3Cmp {
constructor(params: NavParams, location: Location, viewItem: ViewItem) {
this.id = params.get('id');
this.path = location.path();
this.viewItem = viewItem;
console.log(`View3Cmp, path: ${this.path}, param id: ${this.id}`);
}
viewDidEnter() {
this.windowHash = window.location.hash;
}
}
@App()
@RouteConfig([
{ path: '/', component: View1Cmp, as: 'first' },
{ path: '/second', component: View2Cmp, as: 'second' },
{ path: '/third/:id', component: View3Cmp, as: 'third' }
])
class InboxApp {
constructor(location: Location) {
this.location = location;
}
}

View File

@ -0,0 +1,22 @@
<ion-navbar *navbar primary>
<ion-title>First View</ion-title>
</ion-navbar>
<ion-content class="padding">
<h4>
Window Hash: {{windowHash}}
</h4>
<h4>
Location Path: {{path}}
</h4>
<h4>
ViewItem Id: {{viewItem.id}}
</h4>
<p>
<a href="#/second">Second View via href</a>
</p>
<p>
<a href="#/third/3">Third View via href, 3 as id param</a>
</p>
</ion-content>

View File

@ -0,0 +1,22 @@
<ion-navbar *navbar primary>
<ion-title>Second View</ion-title>
</ion-navbar>
<ion-content class="padding">
<h4>
Window Hash: {{windowHash}}
</h4>
<h4>
Location Path: {{path}}
</h4>
<h4>
ViewItem Id: {{viewItem.id}}
</h4>
<p>
<a href="#/third/12">Third View via href, 12 as id param</a>
</p>
<p>
<a href="#/">First View via href</a>
</p>
</ion-content>

View File

@ -0,0 +1,25 @@
<ion-navbar *navbar primary>
<ion-title>Third View</ion-title>
</ion-navbar>
<ion-content class="padding">
<h4>
Window Hash: {{windowHash}}
</h4>
<h4>
Location Path: {{path}}
</h4>
<h4>
NavParams, id: {{id}}
</h4>
<h4>
ViewItem Id: {{viewItem.id}}
</h4>
<p>
<a href="#/second">Second View via href</a>
</p>
<p>
<a href="#/">First View via href</a>
</p>
</ion-content>

View File

@ -1,4 +1,4 @@
import {Component} from 'angular2/angular2';
import {RouteConfig, Location} from 'angular2/router';
import {App, IonicView, NavController} from 'ionic/ionic';
@ -24,10 +24,10 @@ class SignIn {
}
}
@IonicView({
templateUrl: './tabs.html'
})
class TabsPage {
constructor(nav: NavController) {
this.tab1Root = Tab1Page1
@ -184,13 +184,9 @@ class Tab2Page3 {
class Tab3Page1 {}
@App({
routes: [
{
path: '/signin',
component: SignIn,
root: true
}
]
})
@App()
@RouteConfig([
{ path: '/', component: SignIn, as: 'signin' },
{ path: '/tabs', component: TabsPage, as: 'tabs' },
])
class E2EApp {}

View File

@ -5,7 +5,6 @@ import {AppViewManager} from 'angular2/src/core/compiler/view_manager';
import {Ion} from '../ion';
import {IonicConfig} from '../../config/config';
import {IonicApp} from '../app/app';
import {IonicRouter} from '../../routing/router';
import {ViewItem} from './view-item';
import {NavController} from '../nav/nav-controller';
import {PaneController} from '../nav/pane';
@ -30,13 +29,10 @@ export class ViewController extends Ion {
this.compiler = injector.get(Compiler);
this.loader = injector.get(DynamicComponentLoader);
this.viewMngr = injector.get(AppViewManager);
this.router = injector.get(IonicRouter);
this.app = injector.get(IonicApp);
this.config = config;
this.zone = zone;
this.router.addViewController(this);
this.items = [];
this.panes = new PaneController(this);
@ -54,8 +50,8 @@ export class ViewController extends Ion {
]);
}
push(component, params = {}, opts = {}) {
if (!component || this.isTransitioning()) {
push(componentType, params = {}, opts = {}) {
if (!componentType || this.isTransitioning()) {
return Promise.reject();
}
@ -79,13 +75,15 @@ export class ViewController extends Ion {
}
// create a new ViewItem
let enteringItem = new ViewItem(this, component, params);
let enteringItem = new ViewItem(this, componentType, params);
// add the item to the stack
this.add(enteringItem);
// notify app of the state change
this.app.stateChange('push', enteringItem);
if (this.router) {
// notify router of the state change
this.router.stateChange('push', enteringItem, params);
}
// start the transition
this.transition(enteringItem, leavingItem, opts, () => {
@ -120,8 +118,10 @@ export class ViewController extends Ion {
// only item on the history stack.
let enteringItem = this.getPrevious(leavingItem);
if (enteringItem) {
// notify app of the state change
this.app.stateChange('pop', enteringItem);
if (this.router) {
// notify router of the state change
this.router.stateChange('pop', enteringItem);
}
// start the transition
this.transition(enteringItem, leavingItem, opts, () => {
@ -145,8 +145,6 @@ export class ViewController extends Ion {
return Promise.resolve();
}
this.app.stateClear();
// if animate has not been set then default to false
opts.animate = opts.animate || false;
@ -193,9 +191,9 @@ export class ViewController extends Ion {
return this.push((component && component.component) || component, (component && component.params), opts);
}
setRoot(component, params = {}, opts = {}) {
setRoot(componentType, params = {}, opts = {}) {
return this.setItems([{
component,
componentType,
params
}], opts);
}
@ -321,8 +319,10 @@ export class ViewController extends Ion {
enteringItem.didEnter();
leavingItem.didLeave();
// notify app of the state change
this.app.stateChange('pop', enteringItem);
if (this.router) {
// notify router of the state change
this.router.stateChange('pop', enteringItem);
}
} else {
// cancelled the swipe back, return items to original state
@ -550,6 +550,10 @@ export class ViewController extends Ion {
return (item && item.state === STAGED_ENTERING_STATE);
}
registerRouter(router) {
this.router = router;
}
}
const ACTIVE_STATE = 1;

View File

@ -2,7 +2,6 @@ import {CORE_DIRECTIVES, FORM_DIRECTIVES, Component, Directive, View, forwardRef
import * as util from 'ionic/util';
import {IonicConfig} from './config';
import {IonicRouter} from '../routing/router';
import {ionicBootstrap} from '../components/app/app';
import {
Aside, Button, Content, Scroll, Refresher,
@ -16,7 +15,7 @@ import {
TextInput, TextInputElement, Label,
Segment, SegmentButton, SegmentControlValueAccessor,
RadioGroup, RadioButton, SearchBar,
Nav, NavbarTemplate, Navbar, NavPush, NavPop,
Nav, NavbarTemplate, Navbar, NavPush, NavPop, NavRouter,
TapClick, TapDisabled,
Register,
ShowWhen, HideWhen,
@ -78,6 +77,7 @@ export const IonicDirectives = [
forwardRef(() => Navbar),
forwardRef(() => NavPush),
forwardRef(() => NavPop),
forwardRef(() => NavRouter),
forwardRef(() => Register),
forwardRef(() => ShowWhen),
@ -172,7 +172,7 @@ export function App(args={}) {
// redefine with added annotations
Reflect.defineMetadata('annotations', annotations, cls);
ionicBootstrap(cls, args.config, args.routes);
ionicBootstrap(cls, args.config);
return cls;
}

View File

@ -9,9 +9,6 @@ export * from './components'
export * from './platform/platform'
export * from './platform/registry'
export * from './routing/router'
export * from './routing/url-state'
export * from './util/click-block'
export * from './util/focus'

View File

@ -1,178 +0,0 @@
import {
RegExp,
RegExpWrapper,
RegExpMatcherWrapper,
StringWrapper,
isPresent,
isBlank,
BaseException,
normalizeBlank
} from 'angular2/src/facade/lang';
import {
Map,
MapWrapper,
StringMap,
StringMapWrapper,
List,
ListWrapper
} from 'angular2/src/facade/collection';
class ContinuationSegment {
generate(params) {
return '';
}
}
class StaticSegment {
constructor(string) {
this.name = '';
this.regex = escapeRegex(string);
}
generate() {
return this.regex;
}
}
class DynamicSegment {
constructor(name) {
this.regex = "([^/]+)";
}
generate(params) {
if (!StringMapWrapper.contains(params, this.name)) {
throw new BaseException(
`Route generator for '${this.name}' was not included in parameters passed.`)
}
return normalizeBlank(StringMapWrapper.get(params, this.name));
}
}
class StarSegment {
constructor(name) {
this.regex = "(.+)";
}
generate(params) {
return normalizeBlank(StringMapWrapper.get(params, this.name));
}
}
var paramMatcher = RegExpWrapper.create("^:([^\/]+)$");
var wildcardMatcher = RegExpWrapper.create("^\\*([^\/]+)$");
function parsePathString(route: string) {
// normalize route as not starting with a "/". Recognition will
// also normalize.
if (StringWrapper.startsWith(route, "/")) {
route = StringWrapper.substring(route, 1);
}
var segments = splitBySlash(route);
var results = [];
var specificity = 0;
// The "specificity" of a path is used to determine which route is used when multiple routes match
// a URL.
// Static segments (like "/foo") are the most specific, followed by dynamic segments (like
// "/:id"). Star segments
// add no specificity. Segments at the start of the path are more specific than proceeding ones.
// The code below uses place values to combine the different types of segments into a single
// integer that we can
// sort later. Each static segment is worth hundreds of points of specificity (10000, 9900, ...,
// 200), and each
// dynamic segment is worth single points of specificity (100, 99, ... 2).
if (segments.length > 98) {
throw new BaseException(`'${route}' has more than the maximum supported number of segments.`);
}
var limit = segments.length - 1;
for (var i = 0; i <= limit; i++) {
var segment = segments[i], match;
if (isPresent(match = RegExpWrapper.firstMatch(paramMatcher, segment))) {
results.push(new DynamicSegment(match[1]));
specificity += (100 - i);
} else if (isPresent(match = RegExpWrapper.firstMatch(wildcardMatcher, segment))) {
results.push(new StarSegment(match[1]));
} else if (segment == '...') {
if (i < limit) {
// TODO (matsko): setup a proper error here `
throw new BaseException(`Unexpected "..." before the end of the path for "${route}".`);
}
results.push(new ContinuationSegment());
} else if (segment.length > 0) {
results.push(new StaticSegment(segment));
specificity += 100 * (100 - i);
}
}
return {segments: results, specificity};
}
function splitBySlash(url: string): List<string> {
return url.split('/');
}
// represents something like '/foo/:bar'
export class PathRecognizer {
constructor(path) {
this.segments = [];
var parsed = parsePathString(path);
var specificity = parsed['specificity'];
var segments = parsed['segments'];
var regexString = '^';
ListWrapper.forEach(segments, (segment) => {
if (segment instanceof ContinuationSegment) {
this.terminal = false;
} else {
regexString += '/' + segment.regex;
}
});
if (this.terminal) {
regexString += '$';
}
this.regex = RegExpWrapper.create(regexString);
this.segments = segments;
this.specificity = specificity;
}
parseParams(url) {
var params = StringMapWrapper.create();
var urlPart = url;
for (var i = 0; i < this.segments.length; i++) {
var segment = this.segments[i];
if (segment instanceof ContinuationSegment) {
continue;
}
var match = RegExpWrapper.firstMatch(RegExpWrapper.create('/' + segment.regex), urlPart);
urlPart = StringWrapper.substring(urlPart, match[0].length);
if (segment.name.length > 0) {
StringMapWrapper.set(params, segment.name, match[1]);
}
}
return params;
}
generate(params) {
return ListWrapper.join(
ListWrapper.map(this.segments, (segment) => '/' + segment.generate(params)), '');
}
}
var specialCharacters = ['/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\'];
var escapeRe = RegExpWrapper.create('(\\' + specialCharacters.join('|\\') + ')', 'g');
function escapeRegex(string): string {
return StringWrapper.replaceAllMapped(string, escapeRe, (match) => { return "\\" + match; });
}

View File

@ -1,235 +0,0 @@
import {RegExpWrapper} from 'angular2/src/facade/lang';
import * as util from '../util/util';
import {PathRecognizer} from './path-recognizer';
export class IonicRouter {
constructor(config) {
this._routes = [];
this._viewCtrls = [];
this.config(config);
}
app(app) {
this.app = app;
}
config(config) {
if (config) {
for (let i = 0; i < config.length; i++) {
this.addRoute(config[i]);
}
}
}
addRoute(routeConfig) {
if (routeConfig && routeConfig.path && routeConfig.component) {
let route = new Route(routeConfig);
if (routeConfig.root) {
this.otherwise(route);
}
this._routes.push(route);
}
}
stateChange(type, activeView) {
// 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(path, type, activeView);
}
}
}
}
}
stateClear() {
for (let name in stateManagers) {
stateManagers[name].stateClear();
}
}
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;
for (let i = 0; i < this._routes.length; i++) {
route = this._routes[i];
routeMatch = route.match(path);
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;
}
return this._otherwise
}
addViewController(viewCtrl) {
this._viewCtrls.push(viewCtrl);
}
viewController() {
if (this._viewCtrls.length) {
return this._viewCtrls[ this._viewCtrls.length - 1 ];
}
}
static registerStateManager(name, StateManagerClass) {
stateManagerClasses[name] = StateManagerClass;
}
static deregisterStateManager(name) {
delete stateManagerClasses[name];
delete stateManagers[name];
}
}
let stateManagerClasses = {};
let stateManagers = {};
class Route {
constructor(routeConfig) {
util.extend(this, routeConfig);
this.recognizer = new PathRecognizer(this.path);
this.specificity = this.recognizer.specificity;
this.component.route = this;
}
match(path) {
return RegExpWrapper.firstMatch(this.recognizer.regex, path);
}
generate(params) {
return this.recognizer.generate(params);
}
}

View File

@ -1,177 +0,0 @@
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;
}
stateClear() {
this.paths([]);
}
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);

View File

@ -16,6 +16,7 @@ module.exports = {
'node_modules/systemjs/node_modules/es6-module-loader/dist/es6-module-loader.js',
'node_modules/systemjs/dist/system.js',
'node_modules/angular2/bundles/angular2.dev.js',
'node_modules/angular2/bundles/router.dev.js',
'dist/js/ionic.js',
'node_modules/web-animations-js/web-animations.min.js'
],