├── .travis.yml ├── .babelrc ├── tests ├── .eslintrc ├── unit │ └── history.spec.js ├── integration │ └── index.html └── feature │ └── router.spec.js ├── webpack.dev.js ├── .gitignore ├── webpack.common.js ├── webpack.prod.js ├── jest.config.js ├── src ├── index.js ├── writeHistory.js └── history.js ├── LICENSE ├── package.json ├── dist └── index.js └── readme.md /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env"] 3 | } 4 | -------------------------------------------------------------------------------- /tests/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest": true 4 | }, 5 | "rules": { 6 | "import/no-extraneous-dependencies": "off" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /webpack.dev.js: -------------------------------------------------------------------------------- 1 | const merge = require('webpack-merge') 2 | const common = require('./webpack.common.js') 3 | 4 | module.exports = merge(common, { 5 | mode: 'development', 6 | devtool: 'inline-source-map' 7 | }) 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | coverage/ 3 | node_modules/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | test/unit/coverage 8 | test/e2e/reports 9 | selenium-debug.log 10 | 11 | # Editor directories and files 12 | .idea 13 | *.suo 14 | *.ntvs* 15 | *.njsproj 16 | *.sln 17 | -------------------------------------------------------------------------------- /webpack.common.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | entry: './src/index.js', 5 | output: { 6 | filename: 'index.js', 7 | library: 'VueRouterBackButton', 8 | libraryTarget: 'umd', 9 | path: path.resolve(__dirname, 'dist') 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /webpack.prod.js: -------------------------------------------------------------------------------- 1 | const merge = require('webpack-merge') 2 | const UglifyJSPlugin = require('uglifyjs-webpack-plugin') 3 | const common = require('./webpack.common.js') 4 | 5 | module.exports = merge(common, { 6 | mode: 'production', 7 | module: { 8 | rules: [ 9 | { test: /\.js$/, exclude: /node_modules/, loader: "babel-loader" } 10 | ], 11 | }, 12 | plugins: [ 13 | new UglifyJSPlugin() 14 | ] 15 | }) 16 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testURL: 'http://localhost/', 3 | moduleFileExtensions: [ 4 | 'js', 5 | 'jsx', 6 | 'json', 7 | 'vue' 8 | ], 9 | transform: { 10 | '^.+\\.vue$': 'vue-jest', 11 | '^.+\\.jsx?$': 'babel-jest' 12 | }, 13 | moduleNameMapper: { 14 | '^@/(.*)$': '/src/$1' 15 | }, 16 | snapshotSerializers: [ 17 | 'jest-serializer-vue' 18 | ], 19 | } 20 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import routerHistory from './history' 2 | import writeHistory from './writeHistory' 3 | 4 | export { 5 | routerHistory, 6 | writeHistory 7 | } 8 | 9 | export default { 10 | install (Vue, { router, ignoreRoutesWithSameName } = {}) { 11 | if (!router) { 12 | console.error('VueRouterBackButton: router is required on install') 13 | return 14 | } 15 | 16 | Vue.use(routerHistory, { router, ignoreRoutesWithSameName }) 17 | router.afterEach(writeHistory) 18 | }, 19 | } 20 | -------------------------------------------------------------------------------- /src/writeHistory.js: -------------------------------------------------------------------------------- 1 | import History from './history' 2 | 3 | export default (to, from) => { 4 | if (History.visitedRecently(to.fullPath)) { 5 | const amount = History.indexOfRecentHistory(to.fullPath) 6 | 7 | History.back(amount) 8 | } else { 9 | /** 10 | * Ignore navigation to a route with the same name 11 | */ 12 | if (History.ignoreRoutesWithSameName && to.name && from.name && to.name === from.name) { 13 | return 14 | } 15 | 16 | /** 17 | * Save the new route 18 | */ 19 | History.push(to.fullPath) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Maxim Vanhove 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-router-back-button", 3 | "version": "1.3.2", 4 | "description": "Add a back button to your application", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "build": "webpack --config webpack.prod.js", 8 | "dev": "webpack --config webpack.dev.js", 9 | "watch": "webpack --config webpack.dev.js --watch", 10 | "test": "jest", 11 | "test:watch": "jest --watchAll", 12 | "clean": "rm -rf dist/*.js*" 13 | }, 14 | "dependencies": { 15 | "vue": "^2.3.3", 16 | "vue-router": "^2.6.0" 17 | }, 18 | "homepage": "https://github.com/MaximVanhove/vue-router-back-button", 19 | "author": "Maxim Vanhove", 20 | "license": "MIT", 21 | "devDependencies": { 22 | "babel-core": "^6.26.3", 23 | "babel-jest": "^22.4.4", 24 | "babel-loader": "^7.1.5", 25 | "babel-preset-env": "^1.7.0", 26 | "jest": "^22.4.4", 27 | "jest-serializer-vue": "^1.0.0", 28 | "mock-vue-router": "^0.1.3", 29 | "regenerator-runtime": "^0.11.1", 30 | "vue-jest": "^2.6.0", 31 | "webpack": "^4.3.0", 32 | "webpack-cli": "^2.1.5", 33 | "webpack-merge": "^4.1.5" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/unit/history.spec.js: -------------------------------------------------------------------------------- 1 | import { routerHistory } from '@/index' 2 | 3 | let push = (path) => { 4 | window.history.pushState({}, '', path) 5 | routerHistory.push(path) 6 | } 7 | 8 | describe('empty history', () => { 9 | beforeEach(() => { 10 | routerHistory.reset() 11 | }) 12 | 13 | test('it returns an empty history stack', () => { 14 | expect(routerHistory.getHistory()).toEqual([]) 15 | }) 16 | 17 | test('it can write history', () => { 18 | push('/home') 19 | push('/contact') 20 | 21 | expect(routerHistory.getHistory()).toContain('/home') 22 | expect(routerHistory.getHistory()).toContain('/contact') 23 | }) 24 | 25 | test('it can reset the history', () => { 26 | push('/about') 27 | 28 | routerHistory.reset() 29 | 30 | expect(routerHistory.getHistory()).toEqual([]) 31 | }) 32 | }) 33 | 34 | describe('filled history stack', () => { 35 | beforeEach(() => { 36 | routerHistory.reset() 37 | push('/step-1') 38 | push('/step-2') 39 | push('/step-3') 40 | }) 41 | 42 | test('it has history', () => { 43 | expect(routerHistory.hasHistory()).toBeTruthy() 44 | }) 45 | 46 | test('it can check if it can go back', () => { 47 | expect(routerHistory.hasPrevious()).toBeTruthy() 48 | }) 49 | 50 | test('it can go back', () => { 51 | push('/step-4') 52 | push('/step-5') 53 | 54 | routerHistory.back(1) 55 | 56 | expect(routerHistory.current().path).toEqual('/step-4') 57 | }) 58 | 59 | test('it can check if it can go forward', () => { 60 | routerHistory.back(1) 61 | 62 | expect(routerHistory.hasForward()).toBeTruthy() 63 | }) 64 | 65 | test('it can go forward', () => { 66 | routerHistory.back(1) 67 | 68 | routerHistory.forward(1) 69 | 70 | expect(routerHistory.current().path).toEqual('/step-3') 71 | }) 72 | }) 73 | -------------------------------------------------------------------------------- /tests/integration/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Integration test 8 | 9 | 10 | 11 | 12 | 13 |
14 |

Integration test

15 |

16 | Go to index 17 | Go to show 18 | Go to edit 19 | 20 | 24 | Go back to {{ $routerHistory.previous().path }} 25 | 26 | 30 | Go next to {{ $routerHistory.next().path }} 31 | 32 |

33 | 34 |
35 | 36 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /tests/feature/router.spec.js: -------------------------------------------------------------------------------- 1 | import mock from 'mock-vue-router' 2 | import { routerHistory, writeHistory } from '@/index' 3 | 4 | const routes = [ 5 | { 6 | path : '/home', 7 | name: 'home', 8 | }, 9 | { 10 | path : '/dashboard', 11 | name: 'dashboard', 12 | children : [ 13 | { 14 | path : 'stats', 15 | name: 'dashboard', 16 | }, 17 | { 18 | path : 'map', 19 | name: 'dashboard', 20 | }, 21 | ], 22 | }, 23 | { 24 | path : '/index', 25 | name: 'index', 26 | }, 27 | { 28 | path : '/show', 29 | name: 'show', 30 | }, 31 | { 32 | path : '/edit', 33 | name: 'edit', 34 | }, 35 | ] 36 | 37 | describe('vue-router', () => { 38 | let $router 39 | 40 | let push = (path) => { 41 | $router.push(path) 42 | window.history.pushState({}, '', path) 43 | } 44 | 45 | let replace = (path) => { 46 | $router.replace(path) 47 | window.history.replaceState({}, '', path) 48 | } 49 | 50 | beforeEach(() => { 51 | $router = mock(routes).$router 52 | $router.afterEach(writeHistory) 53 | routerHistory.router = $router 54 | routerHistory.registerHistoryEvents() 55 | routerHistory.reset() 56 | }) 57 | 58 | test('it can write history', () => { 59 | push('/index') 60 | 61 | expect(routerHistory.getHistory()).toContain('/index') 62 | }) 63 | 64 | test('it goes back where you actually came from', () => { 65 | push('/index') 66 | push('/show') 67 | push('/edit') 68 | push('/show') 69 | 70 | expect(routerHistory.previous().path).toEqual('/index') 71 | }) 72 | 73 | test('it replaces the route when it was replaced', () => { 74 | push('/index') 75 | push('/show') 76 | replace('/edit') 77 | 78 | expect(routerHistory.previous().path).toEqual('/index') 79 | expect(routerHistory.current().path).toEqual('/edit') 80 | expect(routerHistory.getHistory()).toEqual([ '/index', '/edit' ]) 81 | }) 82 | 83 | test('it can replace the route when user went backward', () => { 84 | push('/dashboard') 85 | push('/index') 86 | push('/show') 87 | push('/index') 88 | replace('/edit') 89 | 90 | expect(routerHistory.previous().path).toEqual('/dashboard') 91 | expect(routerHistory.current().path).toEqual('/edit') 92 | expect(routerHistory.getHistory()).toEqual([ '/dashboard', '/edit' ]) 93 | }) 94 | 95 | test('it can go back to routes with the same name', () => { 96 | routerHistory.ignoreRoutesWithSameName = false 97 | 98 | push('/index') 99 | push('/home') 100 | push('/dashboard') 101 | push('/dashboard/stats') 102 | push('/dashboard/map') 103 | 104 | expect(routerHistory.previous().path).toEqual('/dashboard/stats') 105 | }) 106 | 107 | test('it can ignore routes with the same name', () => { 108 | routerHistory.ignoreRoutesWithSameName = true 109 | 110 | push('/index') 111 | push('/home') 112 | push('/dashboard') 113 | push('/dashboard/stats') 114 | push('/dashboard/map') 115 | 116 | expect(routerHistory.previous().path).toEqual('/home') 117 | }) 118 | 119 | test('it takes you forward', () => { 120 | push('/index') 121 | push('/show') 122 | push('/edit') 123 | push('/show') 124 | 125 | expect(routerHistory.next().path).toEqual('/edit') 126 | }) 127 | 128 | test('it rewrites history when you go forward', () => { 129 | push('/1') 130 | push('/1.1') 131 | push('/2') 132 | 133 | push('/1.1') 134 | push('/1') 135 | push('/2') 136 | 137 | expect(routerHistory.previous().path).toEqual('/1') 138 | expect(routerHistory.next().path).toEqual(undefined) 139 | }) 140 | }) 141 | -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.VueRouterBackButton=e():t.VueRouterBackButton=e()}(window,function(){return function(t){var e={};function r(i){if(e[i])return e[i].exports;var n=e[i]={i:i,l:!1,exports:{}};return t[i].call(n.exports,n,n.exports,r),n.l=!0,n.exports}return r.m=t,r.c=e,r.d=function(t,e,i){r.o(t,e)||Object.defineProperty(t,e,{configurable:!1,enumerable:!0,get:i})},r.r=function(t){Object.defineProperty(t,"__esModule",{value:!0})},r.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return r.d(e,"a",e),e},r.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},r.p="",r(r.s=2)}([function(t,e,r){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var i={_storageKey:"vue.router.back.button.history",_history:[],_current:-1,useSession:function(){try{return!!window.sessionStorage}catch(t){return!1}}(),router:null,ignoreRoutesWithSameName:!1,install:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=e.router,n=e.ignoreRoutesWithSameName;i.router=r,i.ignoreRoutesWithSameName=n||!1,i.registerHistoryEvents(),Object.defineProperty(t.prototype,"$routerHistory",{get:function(){return i}})},registerHistoryEvents:function(){var t,e;window.history.replaceState=(t=window.history.replaceState,e=this.onReplaceState,function(){return e.apply(i,arguments),t.apply(this,arguments)}),i.registerHistoryEvents=function(){}},onReplaceState:function(){this.router&&this.router.currentRoute.fullPath===this.current().path&&this.replaceLastPush()},reset:function(){this._history=[],this._current=-1,this.save()},getHistory:function(){if(!this.useSession)return this._history;var t=sessionStorage.getItem(this._storageKey);return this._history=t?window.JSON.parse(t).history:[],this._history},getCurrent:function(){if(!this.useSession)return this._current;var t=sessionStorage.getItem(this._storageKey);return this._current=t?window.JSON.parse(t).current:-1,this._current},save:function(){if(this.useSession){var t=window.JSON.stringify({history:this._history,current:this._current});return sessionStorage.setItem(this._storageKey,t),this}},previous:function(){var t=this.getHistory();return t.length>1?{path:t[this._current-1]}:{path:null}},current:function(){var t=this.getHistory();return t.length>1?{path:t[this._current]}:{path:null}},next:function(){var t=this.getHistory();return t.length+1>this._current?{path:t[this._current+1]}:{path:null}},hasHistory:function(){return this.getHistory().length>1},hasPrevious:function(){return this.getCurrent()>0},hasForward:function(){return this.getCurrent()+11&&void 0!==arguments[1]?arguments[1]:{},r=e.router,s=e.ignoreRoutesWithSameName;r?(t.use(i.default,{router:r,ignoreRoutesWithSameName:s}),r.afterEach(n.default)):console.error("VueRouterBackButton: router is required on install")}}}])}); -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Vue router back button 2 | 3 | [![npm](https://img.shields.io/npm/dt/vue-router-back-button.svg)](https://www.npmjs.com/package/vue-router-back-button) [![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/MaximVanhove/vue-router-back-button.svg)](https://github.com/MaximVanhove/vue-router-back-button) [![GitHub stars](https://img.shields.io/github/stars/MaximVanhove/vue-router-back-button.svg?style=social&logo=github&label=Stars)](https://github.com/MaximVanhove/vue-router-back-button) 4 | 5 | 6 | A simple VueJS package to extend vue-router with a back button. 7 | 8 | ## Why? I could just add "history.back()" 9 | Sure thing, but the in browser history does not account for programatic navigation. This is where vue-router-back-button comes in. This package does not just sends you to the previous route. Instead it tracks your navigation and sends you back to where you came from. 10 | 11 | ### Regular 12 | Overview page > show page > edit page 13 | After save, a programatic navigation to show page > go back will take you to **edit page** 14 | 15 | ### Vue-router-back-button 16 | Overview page > show page > edit page 17 | After save, a programatic navigation to show page > go back will take you to **overview page** as intented 18 | 19 | ## Dependencies 20 | - vue >2.3.3 21 | - vue-router >2.6.0 22 | 23 | ## Setup 24 | ``` 25 | npm install vue-router-back-button --save 26 | ``` 27 | 28 | Tell Vue to use routerHistory and add writeHistory as Global After Hook. 29 | 30 | ``` 31 | import Vue from 'vue' 32 | import Router from 'vue-router' 33 | import VueRouterBackButton from 'vue-router-back-button' 34 | 35 | Vue.use(Router) 36 | 37 | const router = new Router({ 38 | routes: [] 39 | }) 40 | 41 | Vue.use(VueRouterBackButton, { router }) 42 | ``` 43 | 44 | ## Usage 45 | 46 | ### Going back 47 | 48 | You can add this anywhere in your app as $routerHistory is installed globally. If you can't go back, $routerHistory.previous().path will return null. 49 | 50 | ``` 51 | 54 | GO BACK 55 | 56 | ``` 57 | 58 | ### Going forward 59 | 60 | If you went back, you might want to undo that action right? Well now you can go forward as well! 61 | 62 | ``` 63 | 66 | GO FORWARD 67 | 68 | ``` 69 | 70 | ### Navigate without saving it to the history 71 | 72 | Sometimes you don't want to store a programmatic navigation in the history because users should not get back to it. 73 | 74 | Use `router.replace('/path')` instead of `router.push('/path')` 75 | 76 | ### Ignoring routes with the same name 77 | 78 | If you want to ignore routes with the same name, just set the `ignoreRoutesWithSameName` option to `true` 79 | 80 | ``` 81 | Vue.use(VueRouterBackButton, { 82 | router, 83 | ignoreRoutesWithSameName: true, 84 | }) 85 | ``` 86 | 87 | ## Documentation 88 | 89 | | Function | Description | 90 | | -------- |-------------| 91 | | previous () | Returns the previous visited path in an object | 92 | | hasPrevious() | Returns true if the user can go back | 93 | | next () | Returns the next visited path in an object, this happens when a user went back | 94 | | hasForward () | Returns true if the user can go forward | 95 | | | | 96 | 97 | Feel free to send PR's or request new features (I'll might need to rename this to vue-router-history if you do though) 98 | 99 | ## Nuxt support 100 | 101 | Add a new plugin file: `~/plugins/vue-router-back-button.js` 102 | 103 | ``` 104 | import Vue from 'vue'; 105 | import VueRouterBackButton from 'vue-router-back-button' 106 | 107 | export default ({ app }) => { 108 | Vue.use(VueRouterBackButton, { router: app.router }); 109 | }; 110 | ``` 111 | 112 | Add the reference to the plugins section of `nuxt.config.js` 113 | 114 | ``` 115 | ... 116 | plugins: [ 117 | ... 118 | { mode: 'client', src: '~plugins/vue-router-back-button.js' }, 119 | ... 120 | ], 121 | ... 122 | ``` 123 | 124 | Now you just need to use nuxt-link instead of router-link 125 | 126 | ``` 127 | 132 | 133 | 147 | ``` 148 | 149 | ## Author 150 | 151 | Maxim Vanhove 152 | Web developer at [Starring Jane](https://starringjane.com) 153 | 154 | [![Twitter Follow](https://img.shields.io/twitter/follow/MrMaximVanhove.svg?style=social&logo=twitter&label=Follow)](https://twitter.com/MrMaximVanhove) 155 | -------------------------------------------------------------------------------- /src/history.js: -------------------------------------------------------------------------------- 1 | const History = { 2 | /** 3 | * Key to set in sessionStorage 4 | * @type {String} 5 | */ 6 | _storageKey: 'vue.router.back.button.history', 7 | 8 | /** 9 | * Fallback if sessionStorage is not available 10 | * @type {Array} 11 | */ 12 | _history: [], 13 | 14 | /** 15 | * Current path 16 | * @type {Integer} 17 | */ 18 | _current: -1, 19 | 20 | /** 21 | * Check if sessionStorage is available 22 | */ 23 | useSession: (() => { 24 | try { 25 | return !!window.sessionStorage 26 | } catch(e) { 27 | return false 28 | } 29 | })(), 30 | 31 | /** 32 | * Vue router 33 | */ 34 | router: null, 35 | 36 | /** 37 | * Ignore navigation to a route with the same name 38 | */ 39 | ignoreRoutesWithSameName: false, 40 | 41 | /** 42 | * Install global property $routerHistory 43 | */ 44 | install (Vue, { router, ignoreRoutesWithSameName } = {}) { 45 | History.router = router; 46 | History.ignoreRoutesWithSameName = ignoreRoutesWithSameName || false 47 | History.registerHistoryEvents() 48 | 49 | Object.defineProperty(Vue.prototype, '$routerHistory', { 50 | get () { return History } 51 | }) 52 | }, 53 | 54 | registerHistoryEvents () { 55 | /** 56 | * Track replaceState 57 | */ 58 | var spy = function(original, callback) { 59 | return function () { 60 | callback.apply(History, arguments) 61 | return original.apply(this, arguments) 62 | } 63 | } 64 | 65 | window.history.replaceState = spy(window.history.replaceState, this.onReplaceState) 66 | 67 | /** 68 | * Disable function once registered 69 | */ 70 | History.registerHistoryEvents = function () {} 71 | }, 72 | 73 | onReplaceState () { 74 | if (this.router && this.router.currentRoute.fullPath === this.current().path) { 75 | this.replaceLastPush() 76 | } 77 | }, 78 | 79 | /** 80 | * Reset the history, mainly for testing 81 | */ 82 | reset () { 83 | this._history = [] 84 | this._current = -1 85 | 86 | this.save() 87 | }, 88 | 89 | /** 90 | * Get full history list 91 | * @method 92 | * @return {Array} 93 | */ 94 | getHistory () { 95 | if (!this.useSession) { 96 | return this._history 97 | } 98 | 99 | const session = sessionStorage.getItem(this._storageKey) 100 | 101 | if (!session) { 102 | this._history = [] 103 | } else { 104 | this._history = window.JSON.parse(session).history 105 | } 106 | 107 | return this._history 108 | }, 109 | 110 | /** 111 | * Get current index 112 | * @method 113 | * @return {Array} 114 | */ 115 | getCurrent () { 116 | if (!this.useSession) { 117 | return this._current 118 | } 119 | 120 | const session = sessionStorage.getItem(this._storageKey) 121 | 122 | if (!session) { 123 | this._current = -1 124 | } else { 125 | this._current = window.JSON.parse(session).current 126 | } 127 | 128 | return this._current 129 | }, 130 | 131 | /** 132 | * Save to session 133 | */ 134 | save () { 135 | if (this.useSession) { 136 | const session = window.JSON.stringify({ 137 | history: this._history, 138 | current: this._current 139 | }) 140 | 141 | sessionStorage.setItem(this._storageKey, session) 142 | 143 | return this 144 | } 145 | }, 146 | 147 | /** 148 | * Get the previous path 149 | */ 150 | previous () { 151 | const history = this.getHistory() 152 | 153 | if (history.length > 1) { 154 | return { path: history[this._current - 1] } 155 | } 156 | 157 | return { path: null } 158 | }, 159 | 160 | /** 161 | * Get the current path 162 | */ 163 | current () { 164 | const history = this.getHistory() 165 | 166 | if (history.length > 1) { 167 | return { path: history[this._current] } 168 | } 169 | 170 | return { path: null } 171 | }, 172 | 173 | /** 174 | * Get the next path 175 | */ 176 | next () { 177 | const history = this.getHistory() 178 | 179 | if (history.length + 1 > this._current) { 180 | return { path: history[this._current + 1] } 181 | } 182 | 183 | return { path: null } 184 | }, 185 | 186 | /** 187 | * Do we have any items in the history 188 | */ 189 | hasHistory () { 190 | const history = this.getHistory() 191 | 192 | return history.length > 1 193 | }, 194 | 195 | /** 196 | * Can we go back in history? 197 | */ 198 | hasPrevious () { 199 | const current = this.getCurrent() 200 | 201 | return current > 0 202 | }, 203 | 204 | /** 205 | * Can we go forward into the future? 206 | */ 207 | hasForward () { 208 | const current = this.getCurrent() 209 | const history = this.getHistory() 210 | 211 | return current + 1 < history.length 212 | }, 213 | 214 | /** 215 | * Add new route to the history 216 | */ 217 | push (path) { 218 | this._history = this.getHistory() 219 | this._current = this.getCurrent() 220 | 221 | this._history.splice(this._current + 1, this._history.length) 222 | 223 | const currentPath = this._history[this._history.length - 1] 224 | 225 | if (currentPath !== path) { 226 | this._history.push(path) 227 | this._current = this._current + 1 228 | } 229 | 230 | this.save() 231 | }, 232 | 233 | /** 234 | * Replace the last push 235 | * Used for navigations with replaceState 236 | */ 237 | replaceLastPush () { 238 | this._history = this.getHistory() 239 | this._current = this.getCurrent() 240 | 241 | this._history.splice(this._current - 1, 1) 242 | this._current = this._current - 1 243 | 244 | this.save() 245 | }, 246 | 247 | /** 248 | * User went back in history 249 | */ 250 | back (amount) { 251 | if (amount < 0) { 252 | return 253 | } 254 | 255 | this._current = this.getCurrent() 256 | this._current = this._current - amount 257 | 258 | this.save() 259 | }, 260 | 261 | /** 262 | * User went forward to the future 263 | */ 264 | forward (amount) { 265 | if (amount < 0) { 266 | return 267 | } 268 | 269 | this._current = this.getCurrent() 270 | this._current = this._current + amount 271 | 272 | this.save() 273 | }, 274 | 275 | /** 276 | * Get the recent future from history, uuh what?? =P 277 | */ 278 | getTheRecentFuture () { 279 | const history = this.getHistory() 280 | const current = this.getCurrent() 281 | 282 | return history.slice(current + 1, current + 4) 283 | }, 284 | 285 | /** 286 | * Get the index of recently visited route 287 | */ 288 | indexOfRecentHistory (path) { 289 | const history = this.getHistory() 290 | const current = this.getCurrent() 291 | 292 | const recentHistory = history.slice(0, current + 1).reverse() 293 | 294 | return recentHistory.indexOf(path) 295 | }, 296 | 297 | /** 298 | * Check if user visited a path recently 299 | * If so, the user must go back 300 | */ 301 | visitedRecently (path) { 302 | const index = this.indexOfRecentHistory(path) 303 | 304 | return index !== -1 305 | } 306 | } 307 | 308 | export default History 309 | --------------------------------------------------------------------------------