├── .gitignore ├── .npmignore ├── History.md ├── Makefile ├── Readme.md ├── index.js ├── lib ├── history.js └── popstate.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | support 2 | test 3 | examples 4 | *.sock 5 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 1.0.3 / 2016-03-08 3 | ================== 4 | 5 | * fix for safari's popstate jankiness 6 | 7 | 1.0.2 / 2016-03-05 8 | ================== 9 | 10 | * fix initial state change 11 | 12 | 1.0.1 / 2016-03-04 13 | ================== 14 | 15 | * go to the top of the page after we update the url 16 | 17 | 1.0.0 / 2010-01-03 18 | ================== 19 | 20 | * Initial release 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | test: 3 | @./node_modules/.bin/mocha \ 4 | --require should \ 5 | --reporter spec 6 | 7 | .PHONY: test -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | 2 | # redux-routes 3 | 4 | Simple redux history middleware. 5 | 6 | Doesn't provide a reducer because reducers are application-specific. You'll want to save the payload (the `action.payload.url`) somewhere in your store so you can use it for routing. 7 | 8 | There's nothing new here, [@callum](https://github.com/callum) did all the work in [redux-routing](https://github.com/callum/redux-routing). This module just pulls out the History API and the middleware from that module. 9 | 10 | ## Install 11 | 12 | ```bash 13 | npm install redux-routes 14 | ``` 15 | 16 | ## Example 17 | 18 | ```js 19 | var History = require('redux-routes') 20 | var Socrates = require('socrates') 21 | var navigate = History.navigate 22 | 23 | var store = Socrates([ 24 | History() 25 | ]) 26 | 27 | store.dispatch(navigate('/blog')) 28 | ``` 29 | 30 | ## License 31 | 32 | MIT 33 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tiny history middleware 3 | */ 4 | 5 | var querystring = require('querystring') 6 | var History = require('./lib/history') 7 | var parse = require('url').parse 8 | 9 | /** 10 | * Export `middleware` 11 | */ 12 | 13 | module.exports = middleware['default'] = middleware 14 | middleware.navigate = navigate 15 | 16 | /** 17 | * middleware middleware 18 | * 19 | * @param {Object} options 20 | * @return {Function} 21 | */ 22 | 23 | function middleware (options) { 24 | return function (store) { 25 | var history = History(store).listen() 26 | 27 | return function (next) { 28 | return function (action) { 29 | if (action.type !== '@@redux-routes/navigate') return next(action) 30 | var url = parse(action.payload.url) 31 | 32 | var location = { 33 | hash: url.hash || undefined, 34 | pathname: url.pathname, 35 | search: url.search || undefined 36 | } 37 | 38 | var query = url.query ? qs.parse(url.query) : null 39 | 40 | action.payload.url = url.format(location) 41 | var result = next(action) 42 | 43 | history.update(result) 44 | 45 | return result 46 | } 47 | } 48 | } 49 | } 50 | 51 | /** 52 | * Navigate action creator 53 | * 54 | * @param {String} url 55 | * @return {Object} action 56 | */ 57 | 58 | function navigate (url) { 59 | return { 60 | type: '@@redux-routes/navigate', 61 | payload: { url: url } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/history.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module Dependencies 3 | */ 4 | 5 | var popstate = require('./popstate') 6 | 7 | /** 8 | * Export `History` 9 | */ 10 | 11 | module.exports = History 12 | 13 | /** 14 | * History class 15 | */ 16 | 17 | function History (store) { 18 | if (!(this instanceof History)) return new History(store) 19 | this.store = store 20 | } 21 | 22 | /** 23 | * listen 24 | */ 25 | 26 | History.prototype.listen = function (action) { 27 | var self = this 28 | window.addEventListener('popstate', popstate(function (event) { 29 | self.onpop(event.state) 30 | }), false) 31 | 32 | return this 33 | } 34 | 35 | /** 36 | * update 37 | */ 38 | 39 | History.prototype.update = function (action) { 40 | var url = window.history.state 41 | 42 | if (url && action.payload.url === url) { 43 | this.replace(action.payload.url) 44 | } else { 45 | this.push(action.payload.url) 46 | 47 | // Scroll to the top after we update the page 48 | // 49 | // TODO: this may not always be the case, 50 | // like perhaps with link targets. For now, 51 | // I think it's a good default though. 52 | window.scroll(0, 0) 53 | } 54 | 55 | return this 56 | } 57 | 58 | /** 59 | * push 60 | */ 61 | 62 | History.prototype.push = function (url) { 63 | window.history.pushState(url, null, url) 64 | } 65 | 66 | /** 67 | * Replace 68 | */ 69 | 70 | History.prototype.replace = function (url) { 71 | window.history.replaceState(url, null, url) 72 | } 73 | 74 | /** 75 | * onpop 76 | */ 77 | 78 | History.prototype.onpop = function(url) { 79 | this.store.dispatch({ type: 'navigate', payload: { url: url } }) 80 | }; 81 | 82 | -------------------------------------------------------------------------------- /lib/popstate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module Dependencies 3 | */ 4 | 5 | module.exports = popstate 6 | 7 | /** 8 | * Fix popstate for safari 9 | * 10 | * Yanked from: https://github.com/visionmedia/page.js/blob/4895e378a7080a519390e286a22b02a87a055b10/index.js#L509-L536 11 | */ 12 | 13 | function popstate(fn) { 14 | var loaded = false; 15 | if ('undefined' === typeof window) { 16 | return; 17 | } 18 | if (document.readyState === 'complete') { 19 | loaded = true; 20 | } else { 21 | window.addEventListener('load', function() { 22 | setTimeout(function() { 23 | loaded = true; 24 | }, 0); 25 | }); 26 | } 27 | 28 | return function onpopstate(e) { 29 | if (!loaded) return 30 | fn(e) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redux-routes", 3 | "version": "1.0.3", 4 | "description": "Redux routing middleware", 5 | "keywords": [ 6 | "redux", 7 | "routing", 8 | "history" 9 | ], 10 | "author": "Matthew Mueller ", 11 | "repository": { 12 | "type": "git", 13 | "url": "git://github.com/MatthewMueller/redux-routes.git" 14 | }, 15 | "dependencies": {}, 16 | "devDependencies": { 17 | "mocha": "*", 18 | "should": "*" 19 | }, 20 | "main": "index" 21 | } --------------------------------------------------------------------------------