diff --git a/ionic/components/app/app.ts b/ionic/components/app/app.ts index 7d779ab322..7d921eeab1 100644 --- a/ionic/components/app/app.ts +++ b/ionic/components/app/app.ts @@ -48,9 +48,9 @@ export class IonicApp { return this._zone; } - stateChange(activeView, viewCtrl) { + stateChange(type, activeView) { if (this._activeViewId !== activeView.id) { - this.router.stateChange(activeView, viewCtrl); + this.router.stateChange(type, activeView); this._activeViewId = activeView.id; } } diff --git a/ionic/components/view/view-controller.ts b/ionic/components/view/view-controller.ts index 459821d1f4..c10935af22 100644 --- a/ionic/components/view/view-controller.ts +++ b/ionic/components/view/view-controller.ts @@ -79,7 +79,7 @@ export class ViewController extends Ion { this.add(enteringItem); // notify app of the state change - this.app.stateChange(enteringItem, this); + this.app.stateChange('push', enteringItem); // start the transition this.transition(enteringItem, leavingItem, opts, () => { @@ -115,7 +115,7 @@ export class ViewController extends Ion { let enteringItem = this.getPrevious(leavingItem); if (enteringItem) { // notify app of the state change - this.app.stateChange(enteringItem, this); + this.app.stateChange('pop', enteringItem); // start the transition this.transition(enteringItem, leavingItem, opts, () => { @@ -299,7 +299,7 @@ export class ViewController extends Ion { leavingItem.didLeave(); // notify app of the state change - this.app.stateChange(enteringItem, this); + this.app.stateChange('pop', enteringItem); } else { // cancelled the swipe back, return items to original state diff --git a/ionic/routing/router.ts b/ionic/routing/router.ts index 3dd93fbd85..174ab4d2dd 100644 --- a/ionic/routing/router.ts +++ b/ionic/routing/router.ts @@ -38,41 +38,129 @@ export class IonicRouter { init(window) { this.initHistory(window); - - let rootViewCtrl = this.activeViewController(); - if (rootViewCtrl) { - let matchedRoute = this.match( this.getCurrentPath() ) || this.otherwise(); - this.push(rootViewCtrl, matchedRoute); - } + this.loadByPath(this.getCurrentPath(), this.otherwise()); } initHistory(window) { this.location = window.location; this.history = window.history; - window.addEventListener('popstate', (ev) => { + window.addEventListener('popstate', ev => { this.onPopState(ev); }); } - onPopState(ev) { - let routeName = (ev.state && ev.state.name); + loadByPath(path, fallbackRoute) { + let self = this; + let activeViewCtrl = self.activeViewController(); + let matchedRoute = self.match(path) || fallbackRoute; - console.log('onPopState', routeName); + function zoneLoad() { + self._app.zone().run(() => { + activeViewCtrl.push(matchedRoute.cls); + self._lastPath = matchedRoute.path; + }); + } + + 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(); + } + }); + } + } + } + + onPopState(ev) { + let newState = ev.state || {}; + let newStatePath = newState.path; + let newStateBackPath = newState.backPath; + let newStateForwardPath = newState.forwardPath; + let lastLoadedStatePath = this._lastPath; + + if (newStatePath === lastLoadedStatePath) { + // do nothing if the last path is the same + // as the "new" current state + return; + } let activeViewCtrl = this.activeViewController(); if (activeViewCtrl) { - activeViewCtrl.pop(); + + 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); + } + + } + } + + stateChange(type, activeView) { + if (activeView && activeView.ComponentType) { + + let routeConfig = activeView.ComponentType.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); + } + + this._lastPath = matchedRoute.path; + } + } } } pushState(route) { - let newPath = route.path; - if (this.location.hash !== '#' + newPath) { - let state = { - name: route.name - }; - this.history.pushState(state, '', '#' + newPath); + 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; } } @@ -101,44 +189,6 @@ export class IonicRouter { } } - push(viewCtrl, route) { - let self = this; - - function run() { - self._app.zone().run(() => { - viewCtrl.push(route.cls); - }); - } - - if (viewCtrl && route) { - if (route.cls) { - run(); - - } else if (route.module) { - System.import(route.module).then(m => { - if (m) { - route.cls = m[route.name]; - run(); - } - }); - } - } - } - - stateChange(activeView) { - if (activeView && activeView.ComponentType) { - - let routeConfig = activeView.ComponentType.route; - if (routeConfig) { - let matchedRoute = this.match(routeConfig.path); - - if (matchedRoute) { - this.pushState(matchedRoute); - } - } - } - } - addViewController(viewCtrl) { this._viewCtrls.push(viewCtrl); } @@ -147,7 +197,6 @@ export class IonicRouter { if (this._viewCtrls.length) { return this._viewCtrls[ this._viewCtrls.length - 1 ]; } - return null; } getCurrentPath() { @@ -156,6 +205,10 @@ export class IonicRouter { return hash.slice(1); } + isNewPath(path) { + return (this.location.hash !== ('#' + path)); + } + }