├── README.md ├── bower.json ├── build ├── dist ├── satnav.js └── satnav.min.js ├── example ├── index.html └── scripts │ └── main.js ├── lib └── satnav.js └── package.json /README.md: -------------------------------------------------------------------------------- 1 | # Satnav 2 | 3 | ## A micro (~1.5kb gzipped) JS routing library. 4 | 5 | **Author:** *Joe Harlow* () 6 | 7 | --- 8 | `Satnav` provides functionality for Regex-like paths in JavaScript. It is library agnostic and has no dependencies. 9 | 10 | ### Browser Support 11 | --- 12 | 13 | `Satnav` is built on the `hashchange` event but also includes support for HTML5 `pushState`. 14 | 15 | `Satnav` provides a built-in `hashchange` polyfill for older versions of browsers, however it is currently untested. 16 | 17 | `Satnav` will work in the following browsers, plus most mobile browsers. 18 | 19 | - Microsoft Internet Explorer 8+ 20 | - Mozilla Firefox 3.6+ 21 | - Google Chrome 5.0+ 22 | - Apple Safari 5.0+ 23 | - Opera 10.6+ 24 | 25 | ### Installation 26 | --- 27 | 28 | `Satnav` can be installed with `bower`, by running: 29 | 30 | `bower install satnav` 31 | 32 | ### Usage 33 | --- 34 | 35 | `Satnav` can be accessed using either `Satnav` or `sN`. From here on out, we will refer to it as `Satnav`. 36 | 37 | #### `Satnav`(`config /* Object */`) 38 | 39 | `Satnav` itself is a function object. Calling `Satnav`(`config`) will set the global variables and return itself to enable chaining of functionality. 40 | 41 | The `config` object can have the following properties: 42 | 43 | - #####`html5` (`boolean`) 44 | Tells `Satnav` to use the HTML5 `pushState` method. Will only work if `pushState` is available. 45 | 46 | Default: `false`. 47 | - #####`force` (`boolean`) 48 | Should a `hashchange` be forced again if it is the same route. Useful if you have a Single Page Website where the user could scroll away from that routes content. 49 | 50 | Default: `false`. 51 | - #####`poll` (`int`) 52 | Interval in ms at which the `hashchange` polyfill should occur. 53 | 54 | Default: `25`. 55 | 56 | - #####`matchAll` (`boolean`) 57 | In integrations where a change event should fire every time even if no routes are defined, set this value to `true`. 58 | 59 | Default: `false`. 60 | 61 | ##### Example 62 | 63 | Satnav({ 64 | html5: true, // use HTML5 pushState 65 | force: true, // force change event on same route 66 | poll: 100 // poll hash every 100ms if polyfilled 67 | }); 68 | 69 | 70 | 71 | ### Methods (chainable) 72 | --- 73 | 74 | #### `navigate`(`route /* Object */`) 75 | 76 | Used to define a route that `Satnav` should respond to. The `route` object should contain a `path` and `directions` property. 77 | 78 | - #####`path` (`String`) 79 | 80 | A regex-like string containing the route. Parenthesised tokens in the string will become properties in the route's `params` object, and a value will be required for the route to be matched. Preceding a parenthesised token with a `?` will make the token optional. For eg. `'some/path/{required}/?{optional}'` will match `#some/path/value1` and `#some/path/value1/value2`, but not `#some/path`. 81 | 82 | - #####`directions` (`Function`) 83 | 84 | A function callback for when the route is achieved. A `params` object will be passed into the function containing the token/values from the route. 85 | 86 | - #####`title` (`String` *OR* `Function`) 87 | 88 | A value which can either be returned from a function or string which will then be set as the documents title. The function receives the routes params object. 89 | 90 | ##### Example 91 | 92 | Satnav.navigate({ 93 | path: 'some/path/{required}/?{optional}', 94 | directions: function(params) { 95 | console.log(params.required); // log value of 'required' token 96 | console.log(params.hasOwnProperty('optional')); // check if optional token exists 97 | }, 98 | title: function(params) { 99 | return 'My Awesome route: ' + params.required; 100 | } 101 | }); 102 | 103 | #### `otherwise`(`route /* Object OR String * /`) 104 | 105 | Used to define the default route that `Satnav` should target if no matching route is found. 106 | route can either be a string or an object with a property of `path`. 107 | 108 | ##### Example 109 | 110 | Satnav.otherwise('/'); // will route all unmatched paths to #/ 111 | 112 | or 113 | 114 | Satnav.otherwise({ 115 | path: '/some/path/value1' 116 | }); 117 | 118 | #### `change`(`fn /* Function */`) 119 | 120 | A function callback fired on `hashchange`. **This function will be fired before a routes `directions` are fired**. 121 | 122 | The function will receive the current `hash`, a `params` object containing the token/values of the new route and another object containing the previous `hashchange` token/values. 123 | 124 | ##### Example 125 | 126 | Satnav.change(function(hash,params,old) { 127 | console.log(hash); // log current hash 128 | console.log(params); // log new route values 129 | console.log(old); // log previous route values 130 | }); 131 | 132 | 133 | 134 | #### `go`() 135 | 136 | Called at the end of a chain, `go`() tells `Satnav` to resolve the current route. This is what makes deeplinking possible. 137 | 138 | ##### Example 139 | 140 | Satnav({ 141 | html5: false, // don't use pushState 142 | force: true, // force change event on same route 143 | poll: 100 // poll hash every 100ms if polyfilled 144 | }) 145 | .navigate({ 146 | path: 'some/path/{required}/?{optional}', 147 | directions: function(params) { 148 | console.log(params.required); // log value of 'required' token 149 | console.log(params.hasOwnProperty('optional')); // check if optional token exists 150 | } 151 | }) 152 | .navigate({ 153 | path: 'another/path/?{optional}', 154 | directions: function(params) { 155 | console.log(params.hasOwnProperty('optional')); // check if optional token exists 156 | } 157 | }) 158 | .otherwise('/') // will route all unmatched paths to #/ 159 | .change(function(hash,params,old) { 160 | console.log(hash); // log current hash 161 | console.log(params); // log new route values 162 | console.log(old); // log previous route values 163 | }) 164 | .go() 165 | 166 | 167 | ### Deferring Hash Changes 168 | --- 169 | 170 | `Satnav` includes the ability to defer a routes `directions`. This is extremely useful in builds where an animation off is required to happen before something else animates on for the new route. 171 | 172 | #### How-to 173 | 174 | By returning `Satnav.defer` or `this.defer` in the `change` callback, `Satnav` will wait until the promise is resolved until continuing to the `directions` of the new route. 175 | 176 | Satnav 177 | .navigate({ 178 | path: 'some/path/{required}/?{optional}', 179 | directions: function(params) { 180 | console.log('Fires 4 seconds after change callback'); 181 | } 182 | }) 183 | .change(function(hash,params,old) { 184 | console.log('Change callback'); 185 | setTimeout(function() { 186 | Satnav.resolve(); // we resolve Satnav 4 seconds after the change event is fired 187 | }, 4000) 188 | return this.defer; // return promise object 189 | }) 190 | .go(); 191 | 192 | ### License 193 | --- 194 | 195 | Copyright (C) 2013 Joe Harlow (Fourth of 5 Limited) 196 | 197 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 198 | 199 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 200 | 201 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 202 | 203 | 204 | 205 | 206 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "satnav", 3 | "version": "1.0.0", 4 | "main": [ 5 | "lib/satnav.js" 6 | ], 7 | "ignore": [ 8 | "**/.*", 9 | "node_modules", 10 | "components" 11 | ], 12 | "dependencies": {} 13 | } -------------------------------------------------------------------------------- /build: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cp -rf lib/satnav.js dist/satnav.js 4 | uglifyjs dist/satnav.js -o dist/satnav.min.js 5 | gzip -c dist/satnav.min.js | wc -c 6 | 7 | echo '[Satnav] Build complete.' -------------------------------------------------------------------------------- /dist/satnav.js: -------------------------------------------------------------------------------- 1 | (function(w, d) { 2 | 3 | /* 4 | * 5 | * Satnav.js Routing 6 | * @author: Joe Harlow 7 | * 8 | */ 9 | 10 | 'use strict'; 11 | 12 | var _sN, 13 | _otherwise, 14 | _promise, 15 | _current, 16 | _params = {}, 17 | _ = {}, 18 | _routes = [], 19 | _listeners = {}, 20 | _settings = { 21 | poll : 25, // hashchange Polyfill poll interval 22 | html5 : false, // use pushState if available 23 | force : false, // force hashchange events 24 | matchAll: false // match all routes 25 | }; 26 | 27 | _.setup = function(fn) { 28 | var ev = _settings.html5 && 'pushState' in w.history ? 'popstate' : 'hashchange'; 29 | if (_settings.force) _.force(); 30 | if (('on' + ev) in w) { 31 | _.listen(w, ev, fn); 32 | } else { 33 | var oH = w.location.href; 34 | setInterval(function() { 35 | var nH = w.location.href; 36 | if (oH !== nH) { 37 | oH = nH; 38 | fn.call(w); 39 | } 40 | }, _settings.poll); 41 | } 42 | }; 43 | 44 | _.force = function() { 45 | var a = d.getElementsByTagName('a'); 46 | var _click = function() { 47 | var href = this.getAttribute('href'); 48 | var s = _.sanitize(href.substr(href.indexOf('#') + 1)); 49 | if (s === _current) { 50 | _.change(); 51 | } 52 | }; 53 | 54 | for (var i = 0; i < a.length; i++) { 55 | var el = a[i]; 56 | if (el.hasAttribute('href') && el.getAttribute('href').indexOf('#') !== -1) { 57 | _.listen(el, 'click', _click); 58 | } 59 | } 60 | }; 61 | 62 | _.promise = function() { 63 | var _t = this; 64 | _t.stack = []; 65 | 66 | _t.then = function(fn) { 67 | _t.stack.push(fn); 68 | }; 69 | 70 | _t.resolve = function() { 71 | while (_t.stack.length) { 72 | return _t.stack.shift().call(_sN); 73 | } 74 | }; 75 | }; 76 | 77 | _.route = function(path, directions, title) { 78 | var params = []; 79 | path = '^' + path; 80 | path = path.replace(/(\?)?\{([^}]+)\}/g, function(match, optional, param) { 81 | params.push({ name : param, optional : (typeof optional !== 'undefined') ? true : false }); 82 | return '([\\w-.]+)?'; 83 | }).replace(/\//g, '\\/?'); 84 | path += '\\/?$'; 85 | _routes.push({ regex : new RegExp(path), params : params, directions : directions, title : title }); 86 | }; 87 | 88 | _.change = function() { 89 | _current = _.state(); 90 | var route = _.match(_current); 91 | if (route) { 92 | var old = _params; 93 | _params = route.params; 94 | var change = _.dispatch('change', _current, _params, old); 95 | var title = (typeof route.title === 'function') ? route.title(_params) : route.title; 96 | d.title = title; 97 | if (_settings.html5 && 'pushState' in w.history) { 98 | w.history.replaceState(_.extend(_params, { hash : _current }), title, '/' + _current); 99 | } 100 | var end = function() { 101 | route.directions.call(_sN, _params); 102 | }; 103 | if (change instanceof _.promise) { 104 | _promise.then(end); 105 | } else { 106 | end(); 107 | } 108 | } else if (_settings.matchAll) { 109 | _.dispatch('change', _current); 110 | if (_settings.html5 && 'pushState' in w.history) { 111 | w.history.replaceState({ hash : _current }, d.title, '/' + _current); 112 | } 113 | } else { 114 | w.location.hash = _otherwise || ''; 115 | } 116 | }; 117 | 118 | _.sanitize = function(path) { 119 | return path.replace(/^\//g, ''); 120 | }; 121 | 122 | _.match = function(h) { 123 | for (var r in _routes) { 124 | var route = _routes[r]; 125 | var matched = h.match(route.regex); 126 | if (matched) { 127 | var p = {}; 128 | for (var i = 1, len = matched.length; i < len; i++) { 129 | if (typeof matched[i] === 'undefined' && !route.params[i-1].optional) { 130 | throw new Error('[Satnav] A required route parameter was not defined'); 131 | } else if (typeof matched[i] !== 'undefined') { 132 | p[route.params[i-1].name] = matched[i]; 133 | } 134 | } 135 | return { directions : route.directions, params : p, title: route.title }; 136 | } 137 | } 138 | return undefined; 139 | }; 140 | 141 | _.dispatch = function(ev) 142 | { 143 | var args = Array.prototype.slice.call(arguments, 1); 144 | return ev in _listeners && _listeners[ev].apply(_sN, args); 145 | }; 146 | 147 | _.state = function() 148 | { 149 | return w.history.state && w.history.state.hash ? w.history.state.hash : _.sanitize(w.location.hash.substr(1)); 150 | }; 151 | 152 | _.extend = function(obj, ext) 153 | { 154 | for (var prop in ext) { 155 | obj[prop] = (obj.hasOwnProperty(prop)) ? obj[prop] : ext[prop]; 156 | } 157 | return obj; 158 | }; 159 | 160 | _.listen = (function() { 161 | if (w.addEventListener) { 162 | return function(el, ev, fn) { 163 | el.addEventListener(ev, fn, false); 164 | }; 165 | } else if (w.attachEvent) { 166 | return function(el, ev, fn) { 167 | el.attachEvent('on' + ev, function() { fn.call(el); }, false); 168 | }; 169 | } 170 | })(); 171 | 172 | _sN = function(config) { 173 | _settings = _.extend(config, _settings); 174 | if (_settings.matchAll) _.setup(_.change); 175 | return _sN; 176 | }; 177 | 178 | _sN.defer = (function() { 179 | _promise = new _.promise(); 180 | return _promise; 181 | })(); 182 | 183 | _sN.resolve = function() { 184 | setTimeout(_promise.resolve, 1); 185 | }; 186 | 187 | _sN.navigate = function(route) { 188 | if ('path' in route) { 189 | if (_routes.length === 0 && !_settings.matchAll) { 190 | _.setup(_.change); 191 | } 192 | _.route(_.sanitize(route.path), ('directions' in route) ? route.directions : function() {}, ('title' in route) ? route.title : d.title); 193 | } else { 194 | throw new Error('[Satnav] A required argument was not defined'); 195 | } 196 | return _sN; 197 | }; 198 | 199 | _sN.otherwise = function(route) { 200 | _otherwise = (typeof route === 'string') ? _.sanitize(route) : ('path' in route) ? _.sanitize(route.path) : ''; 201 | return _sN; 202 | }; 203 | 204 | _sN.change = function(fn) { 205 | _listeners.change = fn; 206 | return _sN; 207 | }; 208 | 209 | _sN.go = function() { 210 | _.change(); 211 | return _sN; 212 | }; 213 | 214 | if (typeof define === 'function' && define.amd) { 215 | define('Satnav', [], function() { 216 | return _sN; 217 | }); 218 | } else if (typeof module !== 'undefined' && module.exports) { 219 | module.exports = _sN; 220 | } else { 221 | w.sN = w.Satnav = _sN; 222 | } 223 | 224 | })(window, document); 225 | -------------------------------------------------------------------------------- /dist/satnav.min.js: -------------------------------------------------------------------------------- 1 | (function(w,d){"use strict";var _sN,_otherwise,_promise,_current,_params={},_={},_routes=[],_listeners={},_settings={poll:25,html5:false,force:false,matchAll:false};_.setup=function(fn){var ev=_settings.html5&&"pushState"in w.history?"popstate":"hashchange";if(_settings.force)_.force();if("on"+ev in w){_.listen(w,ev,fn)}else{var oH=w.location.href;setInterval(function(){var nH=w.location.href;if(oH!==nH){oH=nH;fn.call(w)}},_settings.poll)}};_.force=function(){var a=d.getElementsByTagName("a");var _click=function(){var href=this.getAttribute("href");var s=_.sanitize(href.substr(href.indexOf("#")+1));if(s===_current){_.change()}};for(var i=0;i 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Satnav.js 10 | 11 | 12 | 13 | 14 |
15 |
16 |

Satnav.js

17 | 18 | [Click me] Route: { filter : 'example', page : 'pageid' }
19 | [Click me] Route: { filter : 'another', page : 'pageid'} 20 |
21 |
22 | Satnav({ html5 : false })                          // Set usage of html5 history api to false
23 |     .navigate({
24 |         path : '/?{filter}/?{page}',               
25 |         directions : function(params) {            // Function called when path is resolved
26 |             console.log('directions received');    
27 |             console.log(params);
28 |         }
29 |     })
30 |     .change(function(params, old) {                // Fired on hash change event
31 |         console.log('change event heard');
32 |         setTimeout(function() {
33 |             Satnav.resolve();                      // Use Satnav to resolve to the directions for the current route after deferring
34 |         }, 2000);
35 |         return this.defer;                         // Return this.defer/Satnav.defer to defer the resolution of the directions
36 |     })
37 |     .otherwise('/')
38 |     .go();                                         // Initiate Satnav, will handle deeplinking ie. determine initial route
39 |                 
40 |
41 |
42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /example/scripts/main.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | window.onload = function() { 4 | // Satnav({ 5 | // html5 : false, 6 | // force : true 7 | // }) 8 | // .navigate({ 9 | // path : '/?{filter}/?{page}', 10 | // directions : function(params) { 11 | // console.log('directions received'); 12 | // console.log(params); 13 | // } 14 | // }) 15 | // .change(function(params, old) { 16 | // console.log('change event heard'); 17 | // setTimeout(function() { 18 | // Satnav.resolve(); 19 | // }, 2000); 20 | // return this.defer; 21 | // }) 22 | // .otherwise('/') 23 | // .go(); 24 | Satnav({ 25 | html5: true, 26 | matchAll: true 27 | }) 28 | .change(function(hash) { 29 | console.log(hash); 30 | }) 31 | .go(); 32 | }; 33 | 34 | })(); -------------------------------------------------------------------------------- /lib/satnav.js: -------------------------------------------------------------------------------- 1 | (function(w, d) { 2 | 3 | /* 4 | * 5 | * Satnav.js Routing 6 | * @author: Joe Harlow 7 | * 8 | */ 9 | 10 | 'use strict'; 11 | 12 | var _sN, 13 | _otherwise, 14 | _promise, 15 | _current, 16 | _params = {}, 17 | _ = {}, 18 | _routes = [], 19 | _listeners = {}, 20 | _settings = { 21 | poll : 25, // hashchange Polyfill poll interval 22 | html5 : false, // use pushState if available 23 | force : false, // force hashchange events 24 | matchAll: false // match all routes 25 | }; 26 | 27 | _.setup = function(fn) { 28 | var ev = _settings.html5 && 'pushState' in w.history ? 'popstate' : 'hashchange'; 29 | if (_settings.force) _.force(); 30 | if (('on' + ev) in w) { 31 | _.listen(w, ev, fn); 32 | } else { 33 | var oH = w.location.href; 34 | setInterval(function() { 35 | var nH = w.location.href; 36 | if (oH !== nH) { 37 | oH = nH; 38 | fn.call(w); 39 | } 40 | }, _settings.poll); 41 | } 42 | }; 43 | 44 | _.force = function() { 45 | var a = d.getElementsByTagName('a'); 46 | var _click = function() { 47 | var href = this.getAttribute('href'); 48 | var s = _.sanitize(href.substr(href.indexOf('#') + 1)); 49 | if (s === _current) { 50 | _.change(); 51 | } 52 | }; 53 | 54 | for (var i = 0; i < a.length; i++) { 55 | var el = a[i]; 56 | if (el.hasAttribute('href') && el.getAttribute('href').indexOf('#') !== -1) { 57 | _.listen(el, 'click', _click); 58 | } 59 | } 60 | }; 61 | 62 | _.promise = function() { 63 | var _t = this; 64 | _t.stack = []; 65 | 66 | _t.then = function(fn) { 67 | _t.stack.push(fn); 68 | }; 69 | 70 | _t.resolve = function() { 71 | while (_t.stack.length) { 72 | return _t.stack.shift().call(_sN); 73 | } 74 | }; 75 | }; 76 | 77 | _.route = function(path, directions, title) { 78 | var params = []; 79 | path = '^' + path; 80 | path = path.replace(/(\?)?\{([^}]+)\}/g, function(match, optional, param) { 81 | params.push({ name : param, optional : (typeof optional !== 'undefined') ? true : false }); 82 | return '([\\w-.]+)?'; 83 | }).replace(/\//g, '\\/?'); 84 | path += '\\/?$'; 85 | _routes.push({ regex : new RegExp(path), params : params, directions : directions, title : title }); 86 | }; 87 | 88 | _.change = function() { 89 | _current = _.state(); 90 | var route = _.match(_current); 91 | if (route) { 92 | var old = _params; 93 | _params = route.params; 94 | var change = _.dispatch('change', _current, _params, old); 95 | var title = (typeof route.title === 'function') ? route.title(_params) : route.title; 96 | d.title = title; 97 | if (_settings.html5 && 'pushState' in w.history) { 98 | w.history.replaceState(_.extend(_params, { hash : _current }), title, '/' + _current); 99 | } 100 | var end = function() { 101 | route.directions.call(_sN, _params); 102 | }; 103 | if (change instanceof _.promise) { 104 | _promise.then(end); 105 | } else { 106 | end(); 107 | } 108 | } else if (_settings.matchAll) { 109 | _.dispatch('change', _current); 110 | if (_settings.html5 && 'pushState' in w.history) { 111 | w.history.replaceState({ hash : _current }, d.title, '/' + _current); 112 | } 113 | } else { 114 | w.location.hash = _otherwise || ''; 115 | } 116 | }; 117 | 118 | _.sanitize = function(path) { 119 | return path.replace(/^\//g, ''); 120 | }; 121 | 122 | _.match = function(h) { 123 | for (var r in _routes) { 124 | var route = _routes[r]; 125 | var matched = h.match(route.regex); 126 | if (matched) { 127 | var p = {}; 128 | for (var i = 1, len = matched.length; i < len; i++) { 129 | if (typeof matched[i] === 'undefined' && !route.params[i-1].optional) { 130 | throw new Error('[Satnav] A required route parameter was not defined'); 131 | } else if (typeof matched[i] !== 'undefined') { 132 | p[route.params[i-1].name] = matched[i]; 133 | } 134 | } 135 | return { directions : route.directions, params : p, title: route.title }; 136 | } 137 | } 138 | return undefined; 139 | }; 140 | 141 | _.dispatch = function(ev) 142 | { 143 | var args = Array.prototype.slice.call(arguments, 1); 144 | return ev in _listeners && _listeners[ev].apply(_sN, args); 145 | }; 146 | 147 | _.state = function() 148 | { 149 | return w.history.state && w.history.state.hash ? w.history.state.hash : _.sanitize(w.location.hash.substr(1)); 150 | }; 151 | 152 | _.extend = function(obj, ext) 153 | { 154 | for (var prop in ext) { 155 | obj[prop] = (obj.hasOwnProperty(prop)) ? obj[prop] : ext[prop]; 156 | } 157 | return obj; 158 | }; 159 | 160 | _.listen = (function() { 161 | if (w.addEventListener) { 162 | return function(el, ev, fn) { 163 | el.addEventListener(ev, fn, false); 164 | }; 165 | } else if (w.attachEvent) { 166 | return function(el, ev, fn) { 167 | el.attachEvent('on' + ev, function() { fn.call(el); }, false); 168 | }; 169 | } 170 | })(); 171 | 172 | _sN = function(config) { 173 | _settings = _.extend(config, _settings); 174 | if (_settings.matchAll) _.setup(_.change); 175 | return _sN; 176 | }; 177 | 178 | _sN.defer = (function() { 179 | _promise = new _.promise(); 180 | return _promise; 181 | })(); 182 | 183 | _sN.resolve = function() { 184 | setTimeout(_promise.resolve, 1); 185 | }; 186 | 187 | _sN.navigate = function(route) { 188 | if ('path' in route) { 189 | if (_routes.length === 0 && !_settings.matchAll) { 190 | _.setup(_.change); 191 | } 192 | _.route(_.sanitize(route.path), ('directions' in route) ? route.directions : function() {}, ('title' in route) ? route.title : d.title); 193 | } else { 194 | throw new Error('[Satnav] A required argument was not defined'); 195 | } 196 | return _sN; 197 | }; 198 | 199 | _sN.otherwise = function(route) { 200 | _otherwise = (typeof route === 'string') ? _.sanitize(route) : ('path' in route) ? _.sanitize(route.path) : ''; 201 | return _sN; 202 | }; 203 | 204 | _sN.change = function(fn) { 205 | _listeners.change = fn; 206 | return _sN; 207 | }; 208 | 209 | _sN.go = function() { 210 | _.change(); 211 | return _sN; 212 | }; 213 | 214 | if (typeof define === 'function' && define.amd) { 215 | define('Satnav', [], function() { 216 | return _sN; 217 | }); 218 | } else if (typeof module !== 'undefined' && module.exports) { 219 | module.exports = _sN; 220 | } else { 221 | w.sN = w.Satnav = _sN; 222 | } 223 | 224 | })(window, document); 225 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "satnav", 3 | "version": "1.0.0", 4 | "description": "A micro front-end router", 5 | "main": "dist/satnav.js", 6 | "directories": { 7 | "example": "example" 8 | }, 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/f5io/satnav-js.git" 15 | }, 16 | "keywords": [ 17 | "routing", 18 | "front-end" 19 | ], 20 | "author": "Joe Harlow", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/f5io/satnav-js/issues" 24 | }, 25 | "homepage": "https://github.com/f5io/satnav-js#readme" 26 | } 27 | --------------------------------------------------------------------------------