├── .nvmrc ├── .idea ├── scopes │ ├── index.xml │ └── source.xml ├── encodings.xml ├── codeStyles │ └── codeStyleConfig.xml ├── misc.xml ├── vcs.xml ├── jsLibraryMappings.xml ├── modules.xml ├── codeStyleSettings.xml ├── mobx-router5.iml ├── watcherTasks.xml └── inspectionProfiles │ └── Project_Default.xml ├── src ├── index.js ├── modules │ ├── mobxPlugin.js │ └── RouterStore.js └── index.d.ts ├── .gitignore ├── tools ├── .eslintrc └── build.js ├── __tests__ ├── .eslintrc └── main.js ├── .travis.yml ├── .editorconfig ├── .gitattributes ├── LICENSE.txt ├── .eslintrc ├── package.json ├── CONTRIBUTING.md └── README.md /.nvmrc: -------------------------------------------------------------------------------- 1 | v8.11.1 2 | -------------------------------------------------------------------------------- /.idea/scopes/index.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/scopes/source.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import mobxPlugin from './modules/mobxPlugin'; 2 | import RouterStore from './modules/RouterStore'; 3 | 4 | export { 5 | mobxPlugin, 6 | RouterStore, 7 | }; 8 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Include your project-specific ignores in this file 2 | # Read about how to use .gitignore: https://help.github.com/articles/ignoring-files 3 | 4 | coverage 5 | dist 6 | docs/dist 7 | node_modules 8 | npm-debug.log 9 | -------------------------------------------------------------------------------- /.idea/jsLibraryMappings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/codeStyleSettings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 9 | -------------------------------------------------------------------------------- /tools/.eslintrc: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "env": { 4 | "node": true, 5 | "browser": false 6 | }, 7 | "globals": {"Promise": true}, 8 | "plugins": ["import"], 9 | "rules": { 10 | "import/no-extraneous-dependencies": [ 11 | "error", 12 | { 13 | "optionalDependencies": false, 14 | "peerDependencies": false 15 | } 16 | ], 17 | "strict": "off" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /__tests__/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest": true 4 | }, 5 | "plugins": ["import"], 6 | "rules": { 7 | "import/no-extraneous-dependencies": [ 8 | "error", 9 | { 10 | "optionalDependencies": false, 11 | "peerDependencies": true, 12 | "devDependencies": true 13 | } 14 | ], 15 | "no-unused-expressions": "off", 16 | "padded-blocks": "off" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | cache: 4 | directories: 5 | - node_modules 6 | branches: 7 | only: 8 | - master 9 | notifications: 10 | email: false 11 | node_js: 12 | - '8' 13 | before_script: 14 | - npm prune 15 | script: 16 | - npm run lint 17 | - npm run test:cover 18 | - npm run build 19 | after_success: 20 | - npm run coveralls 21 | - npx travis-deploy-once "npx semantic-release" 22 | 23 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # http://editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | 9 | # Change these settings to your own preference 10 | indent_style = space 11 | indent_size = 2 12 | 13 | # We recommend you to keep these unchanged 14 | charset = utf-8 15 | end_of_line = lf 16 | insert_final_newline = true 17 | trim_trailing_whitespace = true 18 | 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Automatically normalize line endings for all text-based files 2 | # http://git-scm.com/docs/gitattributes#_end_of_line_conversion 3 | * text=auto 4 | 5 | # For the following file types, normalize line endings to LF on 6 | # checkin and prevent conversion to CRLF when they are checked out 7 | # (this is required in order to prevent newline related issues like, 8 | # for example, after the build script is run) 9 | .* text eol=lf 10 | *.css text eol=lf 11 | *.ejs text eol=lf 12 | *.js text eol=lf 13 | *.md text eol=lf 14 | *.txt text eol=lf 15 | *.json text eol=lf 16 | -------------------------------------------------------------------------------- /.idea/mobx-router5.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /.idea/watcherTasks.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 16 | 23 | 24 | -------------------------------------------------------------------------------- /src/modules/mobxPlugin.js: -------------------------------------------------------------------------------- 1 | 2 | // TODO 3 | const defaultOptions = {}; 4 | 5 | function mobxPluginFactory(routerStore, options = defaultOptions) { 6 | function mobxPlugin(router, dependencies) { 7 | // NOTE: cross-referencing objects 8 | router.setDependency('routerStore', routerStore); 9 | routerStore.setRouter(router); 10 | 11 | // Implemented methods 12 | return { 13 | onTransitionStart(toState, fromState) { 14 | routerStore.onTransitionStart(toState, fromState); 15 | }, 16 | onTransitionSuccess(toState, fromState, opts) { 17 | routerStore.onTransitionSuccess(toState, fromState, opts); 18 | }, 19 | onTransitionCancel(toState, fromState) { 20 | routerStore.onTransitionCancel(toState, fromState); 21 | }, 22 | onTransitionError(toState, fromState, err) { 23 | routerStore.onTransitionError(toState, fromState, err); 24 | }, 25 | }; 26 | } 27 | 28 | mobxPlugin.pluginName = 'MOBX_PLUGIN'; 29 | 30 | return mobxPlugin; 31 | } 32 | 33 | export default mobxPluginFactory; 34 | 35 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2017] [Leonardo Gentile] 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 | -------------------------------------------------------------------------------- /src/index.d.ts: -------------------------------------------------------------------------------- 1 | import {State} from "router5"; 2 | import {IComputedValue} from "mobx/lib/core/computedvalue"; 3 | import {Route, Router} from "router5/create-router"; 4 | import {Options} from "router5/core/navigation"; 5 | import {PluginFactory} from "router5/core/plugins"; 6 | import {Params} from "router5"; 7 | 8 | declare module "mobx-router5" { 9 | export class RouterStore { 10 | public router: Router; 11 | public route: Route; 12 | public previousRoute: Route; 13 | public transitionRoute: Route; 14 | public transitionError: any; 15 | public intersectionNode: string; 16 | // public toActivate: IObservableArray; 17 | // public toDeactivate: IObservableArray; 18 | 19 | public setRouter: (router: Router) => void; 20 | public updateRoute: (routeType: string, route: Route) => void; 21 | public resetRoute: (routeType: string) => void; 22 | public onTransitionStart: (route: Route, previousRoute: Route) => void; 23 | public onTransitionSuccess: (route: Route, previousRoute: Route, opts: Options) => void; 24 | public onTransitionCancel: (route: Route, previousRoute: Route) => void; 25 | public onTransitionError: (route: Route, previousRoute: Route, transitionError: any) => void; 26 | public clearErrors: () => void; 27 | public navigate: (toRoute: string, params?: Params) => void; 28 | public shouldUpdateNodeFactory: (nodeName: string) => IComputedValue<(toState: State, fromState?: State) => Boolean>; 29 | } 30 | 31 | export function mobxPlugin(routerStore: RouterStore): PluginFactory; 32 | } 33 | 34 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true, 4 | "browser": true 5 | }, 6 | // Parser 7 | "parser": "babel-eslint", 8 | "parserOptions": { 9 | // ECMA Features 10 | "ecmaFeatures": { 11 | "arrowFunctions": true, 12 | "blockBindings": true, 13 | "classes": true, 14 | "defaultParams": true, 15 | "destructuring": true, 16 | "modules": true, 17 | "objectLiteralComputedProperties": true, 18 | "templateStrings": true, 19 | "jsx": true 20 | } 21 | }, 22 | "extends": [ 23 | "eslint:recommended", 24 | "plugin:import/errors", 25 | "plugin:import/warnings" 26 | ], 27 | "rules": { 28 | // Possible Errors 29 | "no-dupe-args": 2, 30 | "no-dupe-keys": 2, 31 | "no-empty": 2, 32 | "no-func-assign": 2, 33 | "no-inner-declarations": 2, 34 | "no-unreachable": 2, 35 | "no-unexpected-multiline": 2, 36 | // Best practices 37 | "consistent-return": 0, 38 | "curly": [2, "multi-line"], 39 | "eqeqeq": 2, 40 | "no-else-return": 2, 41 | "no-multi-spaces": 0, 42 | // Strict mode 43 | "strict": 0, 44 | // Variables 45 | "no-shadow": 0, 46 | "no-unused-vars": [2, { "args": "none" }], 47 | "no-use-before-define": 0, 48 | // Style 49 | "brace-style": [1, "stroustrup"], 50 | "comma-spacing": [2, {"before": false, "after": true}], 51 | "comma-style": [2, "last"], 52 | "consistent-this": [2, "that"], 53 | "lines-around-comment": [2, {"allowBlockStart": true}], 54 | "key-spacing": 0, 55 | "new-parens": 0, 56 | "quotes": [2, "single", "avoid-escape"], 57 | "no-underscore-dangle": 0, 58 | "no-unneeded-ternary": 2, 59 | "semi": 2, 60 | // ES6 61 | "no-var": 2, 62 | "no-this-before-super": 2, 63 | "object-shorthand": 2 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tools/build.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Babel Starter Kit (https://www.kriasoft.com/babel-starter-kit) 3 | * 4 | * Copyright © 2015-2016 Kriasoft, LLC. All rights reserved. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE.txt file in the root directory of this source tree. 8 | */ 9 | 10 | 'use strict'; 11 | 12 | const fs = require('fs'); 13 | const path = require('path'); 14 | const del = require('del'); 15 | const rollup = require('rollup'); 16 | const babel = require('rollup-plugin-babel'); 17 | const pkg = require('../package.json'); 18 | 19 | let promise = Promise.resolve(); 20 | 21 | 22 | // Clean up the output directory 23 | promise = promise.then(() => del(['dist/*'])); 24 | 25 | // Compile source code into a distributable format with Babel 26 | ['es', 'cjs', 'umd'].forEach((format) => { 27 | promise = promise.then(() => rollup.rollup({ 28 | entry: 'src/index.js', 29 | external: Object.keys(pkg.dependencies).concat(Object.keys(pkg.peerDependencies)), 30 | plugins: [babel(Object.assign(pkg.babel, { 31 | babelrc: false, 32 | exclude: 'node_modules/**', 33 | runtimeHelpers: true, // because we use transform-runtime plugin (avoid repetition) 34 | presets: pkg.babel.presets.map(x => (x === 'latest' ? ['latest', { es2015: { modules: false } }] : x)), 35 | }))], 36 | }) 37 | .then(bundle => bundle.write({ 38 | dest: `dist/${format === 'cjs' ? 'index' : `index.${format}`}.js`, 39 | format, 40 | sourceMap: true, 41 | moduleName: format === 'umd' ? pkg.name : undefined, 42 | }))); 43 | }); 44 | 45 | // Copy typings file to dist. 46 | promise = promise.then(() => { 47 | const typingsFile = fs.readFileSync(path.resolve(__dirname, '..', 'src', 'index.d.ts'), { encoding: 'utf-8' }); 48 | fs.writeFileSync(path.resolve(__dirname, '..', 'dist', 'index.d.ts'), typingsFile, 'utf-8'); 49 | }); 50 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 34 | -------------------------------------------------------------------------------- /src/modules/RouterStore.js: -------------------------------------------------------------------------------- 1 | import {observable, action, computed} from 'mobx'; 2 | import transitionPath, {shouldUpdateNode} from 'router5-transition-path'; 3 | 4 | class RouterStore { 5 | 6 | @observable.ref route = null; 7 | @observable.ref previousRoute = null; 8 | @observable.ref transitionRoute = null; 9 | @observable.ref transitionError = null; 10 | @observable.ref intersectionNode = ''; 11 | @observable.ref toActivate = []; 12 | @observable.ref toDeactivate = []; 13 | // @observable currentView; 14 | 15 | router = null; 16 | 17 | constructor() { 18 | this.navigate = this.navigate.bind(this); 19 | this.shouldUpdateNodeFactory = this.shouldUpdateNodeFactory.bind(this); 20 | } 21 | 22 | setRouter(router) { 23 | this.router = router; 24 | } 25 | 26 | updateRoute(routeType, route) { 27 | this[routeType] = route; 28 | } 29 | 30 | resetRoute(routeType) { 31 | this[routeType] = null; 32 | } 33 | 34 | // =========== 35 | // = ACTIONS = 36 | // =========== 37 | // These are called by the plugin 38 | @action onTransitionStart = (route, previousRoute) => { 39 | this.updateRoute('transitionRoute', route); 40 | this.transitionError = null; 41 | }; 42 | 43 | @action onTransitionSuccess = (route, previousRoute, opts) => { 44 | this.updateRoute('route', route); 45 | this.updateRoute('previousRoute', previousRoute); 46 | if (route) { 47 | const {intersection, toActivate, toDeactivate} = transitionPath(route, previousRoute); 48 | this.intersectionNode = opts.reload ? '' : intersection; 49 | this.toActivate = toActivate; 50 | this.toDeactivate = toDeactivate; 51 | } 52 | this.clearErrors(); 53 | }; 54 | 55 | @action onTransitionCancel = (route, previousRoute) => { 56 | this.resetRoute('transitionRoute'); 57 | }; 58 | 59 | @action onTransitionError = (route, previousRoute, transitionError) => { 60 | this.updateRoute('transitionRoute', route); 61 | this.updateRoute('previousRoute', previousRoute); 62 | this.transitionError = transitionError; 63 | }; 64 | 65 | // These can be called manually 66 | @action clearErrors = () => { 67 | this.resetRoute('transitionRoute'); 68 | this.transitionError = null; 69 | }; 70 | 71 | 72 | // Public API, we can manually call these router methods 73 | // Note: These are not actions because they don't directly modify the state 74 | 75 | // Just an alias 76 | navigate = (name, params, opts) => { 77 | this.router.navigate(name, params, opts); 78 | }; 79 | 80 | // Utility to calculate which react routeNode should update 81 | shouldUpdateNodeFactory = (nodeName) => { 82 | return computed(() => { 83 | return shouldUpdateNode(nodeName)(this.route, this.previousRoute); 84 | }); 85 | }; 86 | 87 | 88 | } 89 | 90 | export default RouterStore; 91 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mobx-router5", 3 | "version": "0.0.0-development", 4 | "description": "Router5 integration with mobX", 5 | "homepage": "https://github.com/LeonardoGentile/mobx-router5", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/LeonardoGentile/mobx-router5.git" 9 | }, 10 | "author": "Leonardo Gentile", 11 | "contributors": [], 12 | "license": "MIT", 13 | "keywords": [ 14 | "router", 15 | "mobx", 16 | "router5", 17 | "functional", 18 | "routing", 19 | "observable", 20 | "reactive", 21 | "observer" 22 | ], 23 | "files": [ 24 | "dist", 25 | "src", 26 | "README.md", 27 | "CHANGELOG.md", 28 | "CONTRIBUTING.md", 29 | "LICENSE.txt" 30 | ], 31 | "bugs": { 32 | "url": "https://github.com/LeonardoGentile/mobx-router5/issues" 33 | }, 34 | "main": "dist/index.js", 35 | "module": "dist/index.es.js", 36 | "babel": { 37 | "comments": false, 38 | "presets": [ 39 | "latest", 40 | "stage-0" 41 | ], 42 | "plugins": [ 43 | "transform-runtime", 44 | "transform-decorators-legacy" 45 | ] 46 | }, 47 | "dependencies": { 48 | "babel-runtime": "^6.11.6", 49 | "router5-transition-path": "5.3.0" 50 | }, 51 | "devDependencies": { 52 | "babel-cli": "^6.16.0", 53 | "babel-core": "^6.17.0", 54 | "babel-eslint": "^7.0.0", 55 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 56 | "babel-plugin-transform-runtime": "^6.15.0", 57 | "babel-preset-latest": "^6.16.0", 58 | "babel-preset-stage-0": "^6.16.0", 59 | "babel-register": "^6.16.3", 60 | "commitizen": "2.9.6", 61 | "coveralls": "^2.11.14", 62 | "cz-conventional-changelog": "2.1.0", 63 | "del": "^2.2.2", 64 | "eslint": "^3.19.0", 65 | "eslint-plugin-import": "^2.2.0", 66 | "jest": "23.6.0", 67 | "mobx": "^4.2.0", 68 | "rollup": "0.41.6", 69 | "rollup-plugin-babel": "^2.7.1", 70 | "router5": "^6.1.2", 71 | "semantic-release": "^15.1.7", 72 | "semantic-release-conventional-commits": "^2.0.0", 73 | "travis-deploy-once": "4.4.1" 74 | }, 75 | "peerDependencies": { 76 | "router5": "^6.1.2", 77 | "mobx": "^5.0.0" 78 | }, 79 | "scripts": { 80 | "commit": "git-cz", 81 | "lint": "eslint src test tools", 82 | "test": "jest", 83 | "test:watch": "jest --watch", 84 | "test:cover": "jest --coverage", 85 | "coveralls": "cat ./coverage/lcov.info | coveralls", 86 | "build": "node tools/build", 87 | "publish:docs": "easystatic deploy docs --repo LeonardoGentile/mobx-router5", 88 | "start": "easystatic start docs", 89 | "semantic-release": "semantic-release", 90 | "travis-deploy-once": "travis-deploy-once" 91 | }, 92 | "config": { 93 | "commitizen": { 94 | "path": "./node_modules/cz-conventional-changelog" 95 | } 96 | }, 97 | "release": { 98 | "analyzeCommits": { 99 | "path": "semantic-release-conventional-commits", 100 | "minorTypes": [ 101 | "feat", 102 | "minor" 103 | ], 104 | "patchTypes": [ 105 | "fix", 106 | "patch", 107 | "docs", 108 | "refactor", 109 | "style", 110 | "perf" 111 | ] 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /__tests__/main.js: -------------------------------------------------------------------------------- 1 | import { createRouter } from 'router5'; 2 | import { autorun } from 'mobx'; 3 | import RouterStore from '../src/modules/RouterStore'; 4 | import mobxPlugin from '../src/modules/mobxPlugin'; 5 | 6 | 7 | 8 | const routes = [ 9 | { name: 'a', path: '/a' }, 10 | { name: 'b', path: '/b?param1¶m2' }, 11 | { name: 'c', path: '/c', children: [ 12 | { name: 'd', path: '/d?param1¶m2', }, 13 | { name: 'e', path: '/e', }, 14 | { name: 'f', path: '/f', }, 15 | { name: 'g', path: '/g', children: [ 16 | { name: 'h', path: '/h' }, 17 | { name: 'i', path: '/i' }, 18 | ]} 19 | ]} 20 | ]; 21 | 22 | 23 | function createTestRouter(options) { 24 | const router = createRouter(routes, {...options}); 25 | return router; 26 | } 27 | 28 | 29 | describe('mobxPlugin', () => { 30 | let router; 31 | let routerStore; 32 | 33 | function navigateTo(previousRoute, nextRouteName, routeParams, routeOptions, done) { 34 | router.navigate(nextRouteName, routeParams, routeOptions, function () { 35 | const nextRoute = router.getState(); 36 | done(previousRoute, nextRoute); 37 | }); 38 | } 39 | 40 | beforeEach(() => { 41 | routerStore = new RouterStore(); 42 | router = createTestRouter({defaultRoute: 'a'}); 43 | router.usePlugin(mobxPlugin(routerStore)); 44 | }); 45 | 46 | afterEach(() => { 47 | router.stop(); 48 | }); 49 | 50 | describe('router', () => { 51 | test('should be registered', () => { 52 | expect(router.hasPlugin('MOBX_PLUGIN')).toBe(true); 53 | }); 54 | }); 55 | 56 | describe('routerStore', () => { 57 | 58 | test('should add the router instance into the routerStore ', () => { 59 | expect(routerStore.router).toBe(router); 60 | }); 61 | 62 | test( 63 | 'should have observable properties `route` and `previousRoute` reflecting the navigation', 64 | () => { 65 | 66 | router.start('a', function () { 67 | const previousRoute = router.getState(); 68 | const nextRouteName = 'c.g.i'; 69 | navigateTo(previousRoute, nextRouteName, {}, {}, assertFn); 70 | }); 71 | 72 | function assertFn(previousRoute, nextRoute) { 73 | expect(routerStore.previousRoute.name).toBe('a'); 74 | expect(routerStore.previousRoute.name).toBe(previousRoute.name); 75 | expect(routerStore.previousRoute.path).toBe('/a'); 76 | expect(routerStore.previousRoute.path).toBe(previousRoute.path); 77 | 78 | expect(routerStore.route.name).toBe('c.g.i'); 79 | expect(routerStore.route.name).toBe(nextRoute.name); 80 | expect(routerStore.route.path).toBe('/c/g/i'); 81 | expect(routerStore.route.path).toBe(nextRoute.path); 82 | } 83 | 84 | } 85 | ); 86 | 87 | 88 | test( 89 | 'should have observable properties `params` reflecting the navigation', 90 | (done) => { 91 | 92 | let count = 0; 93 | // assert inside autorun 94 | let disposer = autorun(function () { 95 | let route = routerStore.route; 96 | if (route) { 97 | let params = routerStore.route.params; 98 | if (count === 0) { 99 | count++; 100 | } 101 | else if (count === 1) { 102 | expect(params.param1).toBe('hello'); 103 | expect(params.param2).toBe('there'); 104 | count++; 105 | } 106 | else if (count === 2) { 107 | expect(params.param1).toBe('ok'); 108 | expect(params.param2).toBe('yeah'); 109 | count++; 110 | } 111 | else { 112 | expect(params.param1).toBe('good'); 113 | expect(params.param2).toBe('bye'); 114 | 115 | disposer(); 116 | 117 | done(); // Tell Jest my test is done 118 | } 119 | } 120 | }); 121 | 122 | router.start('a', function () { 123 | const previousRoute = router.getState(); 124 | const nextRoute = 'b'; 125 | navigateTo(previousRoute, nextRoute, {param1: 'hello', param2: 'there'}, {}, gotoC); 126 | }); 127 | 128 | function gotoC(previousRoute, nextRoute) { 129 | const oldRoute = router.getState(); 130 | navigateTo(oldRoute, 'c.d', {param1: 'ok', param2: 'yeah'}, {}, gotoCWithNewParams); 131 | } 132 | 133 | function gotoCWithNewParams(previousRoute, nextRoute) { 134 | const oldRoute = router.getState(); 135 | navigateTo(oldRoute, 'c.d', {param1: 'good', param2: 'bye'}, {}, assertFn); 136 | } 137 | 138 | function assertFn(previousRoute, nextRoute) { 139 | expect(routerStore.previousRoute.params.param1).toBe('ok'); 140 | expect(routerStore.previousRoute.params.param2).toBe('yeah'); 141 | 142 | expect(routerStore.route.params.param1).toBe('good'); 143 | expect(routerStore.route.params.param2).toBe('bye'); 144 | 145 | } 146 | } 147 | ); 148 | 149 | 150 | test( 151 | 'should have the correct intersection node for navigation: c.f -> c.g', 152 | () => { 153 | routerStore = new RouterStore(); 154 | router = createTestRouter({defaultRoute: 'c.f'}); 155 | router.usePlugin(mobxPlugin(routerStore)); 156 | 157 | router.start('c.f', function () { 158 | const previousRoute = router.getState(); 159 | const nextRouteName = 'c.g'; 160 | navigateTo(previousRoute, nextRouteName, {}, {}, assertFn); 161 | }); 162 | 163 | function assertFn(previousRoute, nextRoute) { 164 | expect(routerStore.intersectionNode).toBe('c'); 165 | } 166 | 167 | } 168 | ); 169 | 170 | test( 171 | 'should have the correct intersection node for navigation: b -> c.g.h', 172 | () => { 173 | routerStore = new RouterStore(); 174 | router = createTestRouter({defaultRoute: 'b'}); 175 | router.usePlugin(mobxPlugin(routerStore)); 176 | 177 | router.start('b', function () { 178 | const previousRoute = router.getState(); 179 | const nextRouteName = 'c.g.h'; 180 | navigateTo(previousRoute, nextRouteName, {}, {}, assertFn); 181 | }); 182 | 183 | function assertFn(previousRoute, nextRoute) { 184 | expect(routerStore.intersectionNode).toBe(''); 185 | } 186 | } 187 | ); 188 | 189 | }); 190 | 191 | }); 192 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing to mobx-router5 2 | 3 | Please take a moment to review this document in order to make the contribution process easy and 4 | effective for everyone involved. 5 | 6 | Following these guidelines helps to communicate that you respect the time of the developers 7 | managing and developing this open source project. In return, they should reciprocate that respect in 8 | addressing your issue or assessing patches and features. 9 | 10 | 11 | ### Using the issue tracker 12 | 13 | The [issue tracker](https://github.com/LeonardoGentile/mobx-router5/issues) is the preferred channel 14 | for [bug reports](#bugs), [features requests](#features) and [submitting pull requests](#pull-requests), 15 | but please respect the following restrictions: 16 | 17 | * Please **do not** use the issue tracker for personal support requests. 18 | [Stack Overflow](https://stackoverflow.com/) is a better places to get help. 19 | 20 | * Please **do not** derail or troll issues. Keep the discussion on topic and respect the opinions of 21 | others. 22 | 23 | 24 | 25 | ### Bug reports 26 | 27 | A bug is a _demonstrable problem_ that is caused by the code in the repository. Good bug reports are 28 | extremely helpful - thank you! 29 | 30 | Guidelines for bug reports: 31 | 32 | 1. **Use the GitHub issue search** — check if the issue has already been reported. 33 | 34 | 2. **Check if the issue has been fixed** — try to reproduce it using the latest `master` or 35 | development branch in the repository. 36 | 37 | 3. **Isolate the problem** — ideally create a reduced test case and a live example. 38 | 39 | A good bug report shouldn't leave others needing to chase you up for more information. Please try to 40 | be as detailed as possible in your report. What is your environment? What steps will reproduce the 41 | issue? What browser(s) and OS experience the problem? What would you expect to be the outcome? All 42 | these details will help people to fix any potential bugs. 43 | 44 | Example: 45 | 46 | > Short and descriptive example bug report title 47 | > 48 | > A summary of the issue and the environment (browser/OS, npm, node, rollup, webpack...with relative versions) in which it occurs. If suitable, include the 49 | > steps required to reproduce the bug. 50 | > 51 | > 1. This is the first step 52 | > 2. This is the second step 53 | > 3. Further steps, etc. 54 | > 55 | > `` - a link to the reduced test case 56 | > 57 | > Any other information you want to share that is relevant to the issue being reported. This might 58 | > include the lines of code that you have identified as causing the bug, and potential solutions 59 | > (and your opinions on their merits). 60 | 61 | 62 | 63 | ## Feature requests 64 | 65 | Feature requests are welcome. But take a moment to find out whether your idea fits with the scope 66 | and aims of the project. It's up to *you* to make a strong case to convince the project's developers 67 | of the merits of this feature. Please provide as much detail and context as possible. 68 | 69 | 70 | 71 | ## Pull requests 72 | 73 | Good pull requests - patches, improvements, new features - are a fantastic help. They should remain 74 | focused in scope and avoid containing unrelated commits. 75 | 76 | **Please ask first** before embarking on any significant pull request (e.g. implementing features, 77 | refactoring code, porting to a different language), otherwise you risk spending a lot of time 78 | working on something that the project's developers might not want to merge into the project. 79 | 80 | Please adhere to the coding conventions used throughout a project (indentation, accurate comments, 81 | etc.) and any other requirements (such as test coverage). 82 | 83 | Adhering to the following process is the best way to get your work included in the project: 84 | 85 | 1. [Fork](https://help.github.com/articles/fork-a-repo/) the project, clone your fork, and configure 86 | the remotes: 87 | 88 | ```bash 89 | # Clone your fork of the repo into the current directory 90 | git clone https://github.com//mobx-router5.git 91 | # Navigate to the newly cloned directory 92 | cd mobx-router5 93 | # Assign the original repo to a remote called "upstream" 94 | git remote add upstream https://github.com/LeonardoGentile/mobx-router5.git 95 | # install deps 96 | npm install 97 | ``` 98 | 99 | 2. If you cloned a while ago, get the latest changes from upstream: 100 | 101 | ```bash 102 | git checkout master 103 | git pull upstream master 104 | ``` 105 | 106 | 3. Create a new topic branch (off the main project development branch) to contain your feature, 107 | change, or fix: 108 | 109 | ```bash 110 | git checkout -b 111 | ``` 112 | 113 | 4. Commit your changes in logical chunks. Please adhere to these [git commit message guidelines](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#-git-commit-guidelines) 114 | or your code is unlikely be merged into the main project. 115 | 116 | This repo uses [semantic-release](https://github.com/semantic-release/semantic-release) to automatically 117 | bump npm version based on standard commit messages, so it is very important to follow the guidelines. 118 | To help writing standard commit messages this repo uses [commitizen](http://commitizen.github.io/cz-cli/). 119 | In order to make this work please __do not use__ `git commit` for committing your changes but: 120 | 121 | ```bash 122 | git add . 123 | npm run commit 124 | ``` 125 | This will run the npm script `commit` from `packages.json` and will prompt you questions about your 126 | changes, please follow the prompted instructions. 127 | 128 | If you don' want to run the npm script you can install _commitizen_ globally with `npm install -g commitizen` 129 | and the equivalent command `git cz`. 130 | 131 | Use Git's [interactive rebase](https://help.github.com/articles/about-git-rebase/) 132 | feature to tidy up your commits before making them public. 133 | 134 | 5. Locally merge (or rebase) the upstream development branch into your topic branch: 135 | 136 | ```bash 137 | git pull [--rebase] upstream master 138 | ``` 139 | 140 | 6. Push your topic branch up to your fork: 141 | 142 | ```bash 143 | git push origin 144 | ``` 145 | 146 | 7. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/) with a clear title 147 | and description. 148 | 149 | **IMPORTANT**: By submitting a patch, you agree to allow the project owners to license your work 150 | under the terms of the [MIT License](LICENSE.txt). 151 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/LeonardoGentile/mobx-router5.svg?branch=master)](https://travis-ci.org/LeonardoGentile/mobx-router5) 2 | [![Coverage Status](https://coveralls.io/repos/github/LeonardoGentile/mobx-router5/badge.svg?branch=master)](https://coveralls.io/github/LeonardoGentile/mobx-router5?branch=master) 3 | [![license](https://img.shields.io/github/license/LeonardoGentile/mobx-router5.svg)](https://github.com/LeonardoGentile/mobx-router5/blob/master/LICENSE.txt) 4 | [![npm](https://img.shields.io/npm/v/mobx-router5.svg)](https://www.npmjs.com/package/mobx-router5) 5 | 6 | 7 | # mobx-router5 8 | 9 | > [Router5](https://router5.js.org/) integration with [mobx](https://mobx.js.org/). If you develop with React, use this package with __[react-mobx-router5](https://github.com/LeonardoGentile/react-mobx-router5)__. This plugin represents the source of truth for the @observer components exposed by react-mobx-router5. 10 | This plugin can also be used standalone together with mobx. 11 | 12 | ## Requirements 13 | 14 | - __router5 >= 6.1.2__ 15 | - __mobx >= 5.0.0__ If you use Mobx < 5 install "^4.0.0" version 16 | 17 | 18 | Notice Mobx@5 introduced breaking changes, please follow the [migration guide](https://github.com/mobxjs/mobx/blob/e17c47833d1812eee6d77914be890aa41e4b7908/CHANGELOG.md#500) 19 | 20 | 21 | These are considered `peerDependencies` that means they should exist in your installation, you should install them yourself to make this plugin work. The package won't install them as dependencies. 22 | 23 | ## Install 24 | 25 | ```bash 26 | npm install mobx-router5 27 | ``` 28 | 29 | 30 | ## How it works 31 | Before using this plugin it is necessary that you understand how [router5 works](http://router5.github.io/docs/understanding-router5.html). 32 | 33 | Whenever you performs a router5's transition from one state to another and that transition is *started*, *canceled*, it's *successful* or it has a transition *error* 34 | this plugin exposes all this info as [mobx observables references](https://mobx.js.org/refguide/observable.html) properties of the `RouterStore` class. 35 | You can then use the mobx API to **observe** and react to these **observables**: 36 | 37 | ```ecmascript 6 38 | @observable.ref route; // Current Route - Object 39 | @observable.ref previousRoute; // Object 40 | @observable.ref transitionRoute; // Object 41 | @observable.ref transitionError; // Object 42 | @observable.ref intersectionNode; // String 43 | @observable.ref canActivate; // Array 44 | @observable.ref canDeactivate; // Array 45 | 46 | ``` 47 | 48 | 49 | ## How to use 50 | 51 | - Create a router **store** instance from the `RouterStore` class 52 | - Create and configure a **router** instance 53 | - Add the **plugin** to the router instance, **passing the store to the plugin** 54 | - Use the store methods to perform routing or use your router instance directly 55 | - The only (non-action) method provided is `navigate` that is just an alias for router5 `navigate` 56 | - **Observe** the observable properties exposed by the store 57 | 58 | ```javascript 59 | import {createRouter} from 'router5'; 60 | import loggerPlugin from 'router5/plugins/logger'; 61 | import browserPlugin from 'router5/plugins/browser'; 62 | import routes from './routes'; 63 | import {mobxPlugin, RouterStore} from 'mobx-router5'; 64 | 65 | // Instantiate it directly or extend the class as you wish before invoking new 66 | const routerStore = new RouterStore(); 67 | 68 | export default function configureRouter(useLoggerPlugin = false) { 69 | const router = createRouter(routes, {defaultRoute: 'home'}) 70 | .usePlugin(mobxPlugin(routerStore)) // Important: pass the store to the plugin! 71 | .usePlugin(browserPlugin({useHash: true})); 72 | 73 | if (useLoggerPlugin) { 74 | router.usePlugin(loggerPlugin) ; 75 | } 76 | 77 | return router; 78 | } 79 | ``` 80 | 81 | ## RouterStore 82 | 83 | The core idea of this little plugin is the router store. 84 | The plugin will automatically call the actions (in fact mobx `@action`s) exposed by the store. 85 | By default you can just import the class `RouterStore`, create a new instance, pass it to the plugin and just use it. 86 | 87 | 88 | ### Actions 89 | On route transition Start/Success/Cancel/Error the *mobxPlugin* invokes automatically these mobx actions exposed by the `RouterStore`: 90 | 91 | - `onTransitionStart(toState, fromState)` 92 | - set the `transitionRoute` 93 | - clear the `transitionError` 94 | - `onTransitionSuccess(toState, fromState, opts)` 95 | - set the `route`, `previousRoute`, `canActivate`, `canDeactivate` and `interserctionNode` 96 | - also calls the `clearErrors()` action 97 | - `onTransitionCancel(toState, fromState)` 98 | - reset the `transitionRoute` 99 | - `onTransitionError(toState, fromState, err)` 100 | - set the `transitionRoute`, `previousRoute` and `transitionError` 101 | 102 | 103 | Normally it's **not necessary** to *manually* call these actions, the plugin will do it for us. 104 | 105 | The only one probably worth calling manually (only when necessary) is `clearErrors()`: it resets the `transitionRoute` and `transitionError`. 106 | 107 | ### Router instance reference inside the store 108 | 109 | When you add the plugin to the router with 110 | 111 | ``` 112 | router.usePlugin(mobxPlugin(routerStore)) 113 | ``` 114 | 115 | the router reference is added to the store, so that you can call all the router's methods from the store itself, for example: 116 | 117 | ``` 118 | routerStore.router.navigate('home', {}, {}) 119 | ``` 120 | and all other router5's API [listed here](http://router5.github.io/docs/api-reference.html). 121 | 122 | ### Store reference inside the router's dependencies 123 | The plugin also adds the `routerStore` (the instance) to the router dependencies with 124 | 125 | ``` 126 | router.setDependency('routerStore', routerStore); 127 | ``` 128 | That means that you can access your store from router5's lifecycle methods (canActivate, canDeactivate), middleware or plugins, see [router5 docs](http://router5.github.io/docs/injectables.html) on this topic. 129 | 130 | This creates indeed a cross referece, that is, the store has a reference of the router and the router has a reference of the store. In most cases this should not create any trouble but be aware of this for cases I haven't foreseen. 131 | 132 | ### Your own store 133 | If you know what you are doing you can subclass or create yor own store, making sure you implement at least the actions listed above and a `setRouter(router)` method. 134 | 135 | ### Contributing 136 | Please refer to the CONTRIBUTING.md file. 137 | Please notice that this would require node >=8 as some dev packages require it (for example semantic-release) 138 | 139 | ## Acknowledgments 140 | 141 | - The structure and build process of this repo are based on [babel-starter-kit](https://github.com/kriasoft/babel-starter-kit) 142 | - I've taken the [redux-router5](https://github.com/router5/redux-router5) plugin as example for developing this one 143 | - Thanks to egghead.io for the nice tips about open source development on their [free course](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github) 144 | - Special thanks to [Thomas Roch](https://github.com/troch) for the awesome [router5](https://github.com/router5/router5) ecosystem 145 | 146 | 147 | --------------------------------------------------------------------------------