-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Shane Gibbons
committed
Dec 30, 2017
0 parents
commit 7a54344
Showing
5 changed files
with
250 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
*.swp | ||
*.swo | ||
*.swn | ||
node_modules | ||
build/* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
# Air Traffic Control | ||
|
||
Dead simple redux routing, the way it should work. | ||
|
||
## How it works | ||
|
||
Air Traffic Control maps route changes in your app to action creators in redux. | ||
|
||
## Installation | ||
|
||
``` | ||
$ npm install --save air-traffic-control | ||
``` | ||
|
||
## Usage | ||
|
||
``` | ||
const Router = require('air-traffic-control'); | ||
// import your action creators | ||
const ACTION_CREATORS = require('./actions').ACTION_CREATORS; | ||
// import your redux store | ||
const store require('./store'); | ||
// initialize the router | ||
// if you want local links to be automatically fed into the router, | ||
// pass the interceptLinks: true option. | ||
// This means you can right local links with <a href="/foo"> and they'll just work. | ||
const router = new Router(store, { interceptLinks: true }); // TODO: interceptLinks true as default? | ||
// register any routes you want | ||
router.route('/home', () => ACTION_CREATORS.goHome()); | ||
// route params are provided as arguments to your handler, they go in {these} | ||
router.route('/search/{query}', (query) => ACTION_CREATORS.search(query)'); | ||
// if you want more control, you can use regexes in your routes | ||
// matched groups are passed to your handler in order | ||
// this one matches: /trip/chicago-to-vegas/username/2017/09/01/{id} | ||
router.route(/^\/trip\/(?:[-a-zA-Z0-9()']+\/){5}([-a-zA-Z0-9_]+)$/, (id) => ACTION_CREATORS.trip(id)); | ||
// you'll probably want a default / 404 handler | ||
router.route(':rest:*', () => ACTION_CREATORS.notFound()); | ||
// once you're all set and you want to start routing, call: | ||
router.start(); | ||
``` | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
import $ from 'jquery'; | ||
|
||
const crossroads = require('crossroads'); | ||
|
||
const DEFAULT_CONFIG = { | ||
interceptLinks: false, // by default don't interfere with clicks on links on the page | ||
}; | ||
|
||
export default class { | ||
// create a new router, given a reference to the redux store for action dispatch | ||
constructor(store, config = {}) { | ||
// keep bound reference to dispatch so we can dispatch actions in route handlers | ||
this.dispatch = store.dispatch.bind(store); | ||
|
||
// handler to run additional post-match logic, useful internally in some cases | ||
this.onMatch = () => { }; | ||
|
||
// keep our config options around | ||
this.config = { ...DEFAULT_CONFIG, ...config }; | ||
} | ||
|
||
// register a new route | ||
// | ||
// takes a path and a handler | ||
// | ||
// path can be of the form /foo/{bar}/{baz} to match the bar param and baz param | ||
// | ||
// handler is of the form (bar, baz) => { } and must return return an action | ||
// | ||
route(pathOrPaths, handler) { | ||
const router = this; | ||
|
||
const addRoute = (path) => { | ||
crossroads.addRoute(path, (...args) => { | ||
console.log(`Route matched ${path}`); | ||
|
||
const decodedArgs = args.map(window.decodeURIComponent); | ||
router.dispatch(handler(...decodedArgs)); | ||
if (router.onMatch) { | ||
router.onMatch(); | ||
} | ||
}); | ||
}; | ||
|
||
if (Array.isArray(pathOrPaths)) { | ||
pathOrPaths.forEach(addRoute); | ||
} else { | ||
addRoute(pathOrPaths); | ||
} | ||
} | ||
|
||
// match a path and run associated handlers | ||
// | ||
// optional: handler to run in the case of a match after the standard handler runs | ||
// | ||
match(path, handler = () => {}) { | ||
console.log(`Checking for route matches for ${path}`); | ||
|
||
const oldOnMatch = this.onMatch; | ||
this.onMatch = handler; | ||
crossroads.parse(path); | ||
this.onMatch = oldOnMatch; | ||
} | ||
|
||
// make a purely aesthetic update to the location - this won't effect navigation / history | ||
// | ||
// optionally update the title as well | ||
|
||
// disabled eslint rule that tries to force this to static because the router should be singleton | ||
prettify(path, title) { // eslint-disable-line class-methods-use-this | ||
console.log(`Aesthetic path update: ${path}`); | ||
window.history.replaceState( | ||
{ | ||
pretty: true, | ||
orginalPath: window.location.pathname, | ||
}, | ||
title, | ||
path, | ||
); | ||
if (title) { | ||
document.title = title; | ||
} | ||
} | ||
|
||
// navigate to a new path, running any associated handlers | ||
navigate(path) { | ||
console.log(`Navigating to ${path}`); | ||
this.match(path); | ||
} | ||
|
||
// start listening for and handling route changes | ||
// | ||
start() { | ||
const router = this; | ||
|
||
// listen for history state change | ||
window.onpopstate = (evt) => { | ||
let targetPath; | ||
if (evt.state && evt.state.pretty) { | ||
targetPath = evt.originalPath; | ||
} else { | ||
targetPath = document.location.pathname; | ||
} | ||
crossroads.parse(targetPath || ''); | ||
}; | ||
|
||
// intercept link clinks and run through router when appropriate | ||
if (router.config.interceptLinks) { | ||
document.body.addEventListener('click', (evt) => { | ||
// only process clicks on elements with hrefs set for the current host | ||
const $parentLink = $(evt.target).closest('a'); | ||
const parseLink = document.createElement('a'); | ||
let href; | ||
if (!evt.target.href && $parentLink && $parentLink.length > 0) { | ||
parseLink.href = $parentLink.attr('href'); | ||
href = parseLink.href; // eslint-disable-line prefer-destructuring | ||
} else { | ||
href = evt.target.href; // eslint-disable-line prefer-destructuring | ||
} | ||
console.log(evt, $parentLink, href); | ||
const currentHost = ( | ||
href && | ||
evt.target.hostname && | ||
evt.target.hostname === window.location.hostname | ||
); | ||
const hashChange = ( | ||
href && | ||
href.split('#')[0] === window.location.href.split('#')[0] && | ||
href.indexOf('#') !== -1 | ||
); | ||
if (hashChange) { | ||
router.prettify(href); | ||
evt.preventDefault(); | ||
} | ||
if (currentHost && !hashChange) { | ||
// try to match the route, | ||
// prevent link click default behavior (navigate away) in the case of a match | ||
router.match(evt.target.pathname, () => { | ||
evt.preventDefault(); | ||
window.history.pushState({}, '' /* TODO: consistent title for history */, evt.target.pathname); | ||
}); | ||
} | ||
}); | ||
} | ||
|
||
crossroads.ignoreState = true; | ||
|
||
// actually parse our current route | ||
router.match(document.location.pathname); | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
{ | ||
"name": "air-traffic-control", | ||
"version": "0.1.0", | ||
"description": "Dead simple redux routing, the way it should work.", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "echo \"Error: no test specified\" && exit 1" | ||
}, | ||
"keywords": [ | ||
"redux", | ||
"router" | ||
], | ||
"author": "Airplane Mode Team", | ||
"license": "MIT", | ||
"dependencies": { | ||
"crossroads": "^0.12.2", | ||
"jquery": "^3.2.1" | ||
} | ||
} |