diff --git a/ionic/components/app/app.ts b/ionic/components/app/app.ts index 4b9a4120b4..c3d088b6ba 100644 --- a/ionic/components/app/app.ts +++ b/ionic/components/app/app.ts @@ -1,6 +1,5 @@ import {bootstrap, Compiler, ElementRef, NgZone, bind, ViewRef} from 'angular2/angular2'; import {AppViewManager} from 'angular2/src/core/compiler/view_manager'; -import {ViewContainerRef} from 'angular2/src/core/compiler/view_container_ref'; import {NgZone} from 'angular2/src/core/zone/ng_zone'; import {IonicRouter} from '../../routing/router'; @@ -75,10 +74,10 @@ export class IonicApp { * Create and append the given component into the root * element of the app. * - * @param Component the ComponentType to create and insert + * @param Component the cls to create and insert * @return Promise that resolves with the ContainerRef created */ - appendComponent(ComponentType: Type, context=null) { + appendComponent(cls: Type, context=null) { return new Promise((resolve, reject) => { let injector = this.injector(); let compiler = injector.get(Compiler); @@ -86,7 +85,7 @@ export class IonicApp { let rootComponentRef = this._ref._hostComponent; let viewContainerLocation = rootComponentRef.location; - compiler.compileInHost(ComponentType).then(protoViewRef => { + compiler.compileInHost(cls).then(protoViewRef => { let atIndex = 0; let hostViewRef = viewMngr.createViewInContainer( @@ -159,7 +158,7 @@ function initApp(window, document, config) { return app; } -export function ionicBootstrap(ComponentType, config, router) { +export function ionicBootstrap(cls, config, router) { return new Promise((resolve, reject) => { try { // get the user config, or create one if wasn't passed in @@ -203,10 +202,10 @@ export function ionicBootstrap(ComponentType, config, router) { bind(Modal).toValue(modal) ]; - bootstrap(ComponentType, injectableBindings).then(appRef => { + bootstrap(cls, injectableBindings).then(appRef => { app.load(appRef); - router.init(window); + router.load(app, config, window); // resolve that the app has loaded resolve(app); @@ -222,15 +221,3 @@ export function ionicBootstrap(ComponentType, config, router) { } }); } - -export function load(app) { - if (!app) { - console.error('Invalid app module'); - - } else if (!app.main) { - console.error('App module missing main()'); - - } else { - app.main(ionicBootstrap); - } -} diff --git a/ionic/ionic.ts b/ionic/ionic.ts index b165d2f742..b771d66d28 100644 --- a/ionic/ionic.ts +++ b/ionic/ionic.ts @@ -12,6 +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/util/click-block' export * from 'ionic/util/focus' diff --git a/ionic/routing/hash-url-state.ts b/ionic/routing/hash-url-state.ts new file mode 100644 index 0000000000..fb80e3687c --- /dev/null +++ b/ionic/routing/hash-url-state.ts @@ -0,0 +1,105 @@ +import {IonicRouter} from './router'; +import * as util from '../util/util'; + + +class HashUrlStateManager { + + constructor(router, ionicApp, ionicConfig, window) { + this.router = router; + this.ionicApp = ionicApp; + this.ionicConfig = ionicConfig; + this.location = window.location; + this.history = window.history; + + 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.loadByPath(newStatePath); + } + + } + } + + getCurrentPath() { + // Grab the path without the leading hash + return { + 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); diff --git a/ionic/routing/router.ts b/ionic/routing/router.ts index 5b1e027388..eb38e24fd4 100644 --- a/ionic/routing/router.ts +++ b/ionic/routing/router.ts @@ -32,20 +32,16 @@ export class IonicRouter { } } - init(window) { - this.initHistory(window); + load(ionicApp, ionicConfig, window) { + // create each of the state manager classes + for (let name in stateManagerClasses) { + stateManagers[name] = new stateManagerClasses[name](this, ionicApp, ionicConfig, window); + } + stateManagerClasses = {}; + this.loadByPath(this.getCurrentPath(), this.otherwise()); } - initHistory(window) { - this.location = window.location; - this.history = window.history; - - window.addEventListener('popstate', ev => { - this.onPopState(ev); - }); - } - loadByPath(path, fallbackRoute) { let self = this; let activeViewCtrl = self.activeViewController(); @@ -54,7 +50,7 @@ export class IonicRouter { function zoneLoad() { self._app.zone().run(() => { activeViewCtrl.push(matchedRoute.cls); - self._lastPath = matchedRoute.path; + self.lastPath(matchedRoute.path); }); } @@ -74,90 +70,47 @@ export class IonicRouter { } } - onPopState(ev) { - let newState = ev.state || {}; - let newStatePath = newState.path; - let newStateBackPath = newState.backPath; - let newStateForwardPath = newState.forwardPath; - let lastLoadedStatePath = this._lastPath; + getCurrentPath() { + // check each of the state managers and the one with the + // highest priority wins of knowing what path we are currently at + let rtnPath = null; + let highestPriority = -1; + let currentState = null; - if (newStatePath === lastLoadedStatePath) { - // do nothing if the last path is the same - // as the "new" current state - return; - } - - let activeViewCtrl = this.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.loadByPath(newStatePath); + for (let name in stateManagers) { + currentState = stateManagers[name].getCurrentPath(); + if (currentState.path && currentState.priority > highestPriority) { + rtnPath = currentState.path; } - } + + return rtnPath; } stateChange(type, activeView) { - if (activeView && activeView.ComponentType) { + if (activeView && activeView.cls) { - let routeConfig = activeView.ComponentType.route; + let routeConfig = activeView.cls.route; if (routeConfig) { let matchedRoute = this.match(routeConfig.path); if (matchedRoute) { - 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.isNewPath(matchedRoute.path)) { - this.history.back(); - } - - } else { - this.pushState(matchedRoute); + for (let name in stateManagers) { + stateManagers[name].stateChange(matchedRoute.path, type, activeView); } - this._lastPath = matchedRoute.path; + this.lastPath(matchedRoute.path); } } } } - pushState(route) { - let enteringState = { - path: route.path, - backPath: this._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.isNewPath(route.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; + lastPath(val) { + if (arguments.length) { + this._lastPath = val; } + return this._lastPath; } match(path) { @@ -195,18 +148,20 @@ export class IonicRouter { } } - getCurrentPath() { - let hash = this.location.hash; - // Grab the path without the leading hash - return hash.slice(1); + static registerStateManager(name, StateManagerClass) { + stateManagerClasses[name] = StateManagerClass; } - isNewPath(path) { - return (this.location.hash !== ('#' + path)); + static deregisterStateManager(name) { + delete stateManagerClasses[name]; + delete stateManagers[name]; } } +let stateManagerClasses = {}; +let stateManagers = {}; + export class Routable { constructor(cls, routeConfig) {