├── .gitignore ├── .jshintrc ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bower.json ├── dist ├── backbone.base-router.js ├── backbone.base-router.min.js └── backbone.base-router.min.js.map ├── examples ├── fetch-router │ ├── README.md │ ├── index.html │ └── script.js └── old-router │ ├── README.md │ ├── index.html │ └── script.js ├── gruntfile.js ├── package.json ├── src ├── backbone.base-router.js └── wrapper.js └── test ├── .jshintrc ├── setup ├── helpers.js └── node.js ├── spec-runner.html └── unit └── base-router.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Commenting this out is preferred by some people, see 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 25 | node_modules 26 | bower_components 27 | 28 | # Users Environment Variables 29 | .lock-wscript 30 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "globals": { 3 | "define" : true, 4 | "require" : true, 5 | "module" : true, 6 | "exports" : true, 7 | "$" : true, 8 | "jQuery" : true, 9 | "_" : true, 10 | "Backbone" : true 11 | }, 12 | 13 | "bitwise" : true, 14 | "camelcase" : true, 15 | "curly" : true, 16 | "eqeqeq" : true, 17 | "es3" : true, 18 | "forin" : true, 19 | "immed" : true, 20 | "indent" : 2, 21 | "latedef" : true, 22 | "newcap" : true, 23 | "noarg" : true, 24 | "noempty" : true, 25 | "nonbsp" : true, 26 | "nonew" : true, 27 | "plusplus" : true, 28 | "quotmark" : "single", 29 | "undef" : true, 30 | "unused" : "vars", 31 | "strict" : false, 32 | "trailing" : true, 33 | "maxparams" : 4, 34 | "maxdepth" : 2, 35 | "maxcomplexity" : 6, 36 | "maxlen" : 100, 37 | 38 | "asi" : false, 39 | "boss" : true, 40 | "debug" : false, 41 | "eqnull" : false, 42 | "esnext" : false, 43 | "evil" : false, 44 | "expr" : false, 45 | "funcscope" : false, 46 | "globalstrict" : true, 47 | "iterator" : false, 48 | "lastsemic" : false, 49 | "laxbreak" : false, 50 | "laxcomma" : false, 51 | "loopfunc" : false, 52 | "maxerr" : 50, 53 | "multistr" : false, 54 | "notypeof" : false, 55 | "proto" : false, 56 | "scripturl" : false, 57 | "smarttabs" : false, 58 | "shadow" : false, 59 | "sub" : false, 60 | "supernew" : false, 61 | "validthis" : false, 62 | "noyield" : false, 63 | 64 | "browser" : true, 65 | "couch" : false, 66 | "devel" : false, 67 | "dojo" : false, 68 | "jquery" : false, 69 | "mootools" : false, 70 | "node" : false, 71 | "nonstandard" : false, 72 | "prototypejs" : false, 73 | "rhino" : false, 74 | "worker" : false, 75 | "wsh" : false, 76 | "yui" : false 77 | } 78 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | matrix: 5 | include: 6 | - node_js: "0.10" 7 | env: UNDERSCORE=1.4.4 BACKBONE=1.1.1 MAINRUN=false 8 | - node_js: "0.10" 9 | env: UNDERSCORE=1.5 BACKBONE=1.1.2 MAINRUN=false 10 | - node_js: "0.10" 11 | env: UNDERSCORE=1.6 BACKBONE=1.1.1 MAINRUN=false 12 | - node_js: "0.10" 13 | env: UNDERSCORE=1.4.4 BACKBONE=1.1.1 MAINRUN=false 14 | - node_js: "0.10" 15 | env: UNDERSCORE=1.4.4 BACKBONE=1.1.2 MAINRUN=false 16 | env: MAINRUN=true 17 | before_install: 18 | - npm config set ca "" 19 | install: 20 | - npm install -g grunt-cli 21 | - npm install 22 | # Which matrix settings -- otherwise default 23 | - if [[ $MAINRUN == false ]]; then npm install backbone@$BACKBONE; fi 24 | - if [[ $MAINRUN == false ]]; then npm install underscore@$UNDERSCORE; fi 25 | sudo: false -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### [1.3.2](https://github.com/jmeas/backbone.base-router/releases/tag/1.3.2) 2 | 3 | - Update to support Lodash v4 4 | 5 | ### [1.3.1](https://github.com/jmeas/backbone.base-router/releases/tag/1.3.1) 6 | 7 | - Updated dependencies to include the latest Backbone versions. 8 | 9 | ### [1.3.0](https://github.com/jmeas/backbone.base-router/releases/tag/1.3.0) 10 | 11 | - Updated dependencies to include the latest Backbone versions. 12 | 13 | ### [1.2.0](https://github.com/jmeas/backbone.base-router/releases/tag/1.2.0) 14 | 15 | - **New feature:** The `routeData` Object now returns the original query string under `queryString` 16 | - **New feature:** Splat values are now included in `routeData.params` 17 | 18 | ### [1.1.0](https://github.com/jmeas/backbone.base-router/releases/tag/1.1.0) 19 | 20 | - Updated dependencies to include the latest Backbone and Underscore versions. 21 | 22 | ### [1.0.0](https://github.com/jmeas/backbone.base-router/releases/tag/v1.0.0) 23 | 24 | - **Enhancement**: Adds support for multiple query parameters of the same name. They 25 | are returned as an array. 26 | 27 | ### [0.5.0](https://github.com/jmeas/backbone.base-router/releases/tag/v0.5.0) 28 | 29 | - Updated Backbone dependency 30 | - Tests against multiple versions of Backbone/Underscore 31 | 32 | ### [0.4.1](https://github.com/jmeas/backbone.base-router/releases/tag/v0.4.1) 33 | 34 | - **Bug fix**: Uses the constructor on the Router's prototype 35 | 36 | ### [0.4.0](https://github.com/jmeas/backbone.base-router/releases/tag/v0.4.0) 37 | 38 | - **Refactor**: No longer override `_routeToRegExp` 39 | - **Refactor**: Simplified method to parse query parameters 40 | - **Bug fix**: `routeParams` was removed from the prototype 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 James Smith 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # backbone.base-router 2 | 3 | A better starting point for creating your own routing abstractions in Backbone. 4 | 5 | ### What problems does this library solve? 6 | 7 | Backbone's Router has two frustrating properties: it is too simple and too difficult to 8 | change. This library solves the second problem to make it easier for *you* to solve the 9 | first problem. 10 | 11 | Instead of requiring that you associate a callback with a route, this library 12 | lets you associate whatever you'd like with a route. It can be a callback if you want, 13 | but it can also be an object, or even a string. 14 | 15 | Whenever a Route is matched, a single method on the Router is called. This method is passed a single 16 | argument, `routeData`, that contains as much about the matched route as possible. Included this object are parsed 17 | query parameters, named fragment params, and the object you associated with the route, among other things. This 18 | single point-of-entry, combined with all of this data, makes it remarkably easy to add new abstractions to the Router. 19 | 20 | ### What problems *doesn't* this library solve? 21 | 22 | This library is not an effort to rewrite Backbone.history. As such, some of History's quirks are carried 23 | over, too. For instance, the order that you specify your callbacks in *still* matters, as this is how `Backbone.History` matches routes. 24 | 25 | - [Getting Started](#getting-started) 26 | - [The Single Point of Entry](#the-single-point-of-entry) 27 | - [Removed Features](#removed-features) 28 | - [Example Usage](#example-usage) 29 | - [API](#api) 30 | - [history](#history) 31 | - [onNavigate](#onnavigate-routedata-) 32 | 33 | 34 | ### Getting Started 35 | 36 | #### The Single Point of Entry 37 | 38 | The API for the Base Router is simple: there's a single callback that gets called when a Route is navigated 39 | to. This callback is a plethora of information you might need, such as parsed query 40 | parameters and whatever object was associated with the callback. This is the location 41 | where you build your abstractions from. 42 | 43 | #### Removed features 44 | 45 | Backbone.BaseRouter does more for you by doing less. The following features were removed from the router. 46 | 47 | - A callback, if specified, is not automatically executed 48 | - No routing-related events are fired 49 | 50 | The point of removing these features is that it gives you complete control over the Routing 51 | mechanism. It's simple to add them back in. Or you can change them to be exactly how you 52 | want. Or just leave them out. It's entirely up to you. 53 | 54 | #### Example Usage 55 | 56 | See the [`examples/`](https://github.com/jmeas/backbone.base-router/tree/master/examples) directory. There are READMEs for each example. 57 | 58 | ## API 59 | 60 | ### `onNavigate( routeData )` 61 | 62 | The single point of entry is the `onNavigate` method. This method is called each time the user navigates 63 | via Backbone.history. 64 | 65 | ```js 66 | // Create a new Base Router 67 | var router = new BaseRouter(); 68 | 69 | // Each time the user navigates to a matched route, a console message 70 | // logs all of the data passed to the callback. 71 | router.onNavigate = function(routeData) { 72 | console.log('The user has navigated!', routeData); 73 | }; 74 | ``` 75 | 76 | In addition to being called everytime that the user navigates to a matched route, which in itself is useful, the 77 | callback is passed a plethora of useful data related to the navigation action. This information is contained 78 | in the `routeData` argument. 79 | 80 | #### `routeData` 81 | 82 | ##### `linked` 83 | 84 | The object that was associated with this route. In a traditional Backbone router, this is always a callback that 85 | is executed. In the BaseRouter, this can be anything, and no assumptions are made about what you should do with it. 86 | 87 | ##### `route` 88 | 89 | The regular expression that matched the URI fragment. 90 | 91 | ##### `originalRoute` 92 | 93 | If the route was registered as a string, and not a regular expression, then this will 94 | be that original string. Otherwise, it is undefined. 95 | 96 | ##### `params` 97 | 98 | An object which has keys that are the named parameters from the Route, and corresponding values 99 | from the URL. 100 | 101 | ##### `query` 102 | 103 | An object representation of the query string in the URI fragment. 104 | 105 | ##### `queryString` 106 | 107 | The original query string in the URI fragment. `undefined` if no query string given 108 | 109 | ##### `router` 110 | 111 | The router instance that this route was registered on. 112 | 113 | ##### `uriFragment` 114 | 115 | The URI fragment that was matched. 116 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backbone.base-router", 3 | "version": "1.3.2", 4 | "homepage": "https://github.com/jmeas/backbone.base-router", 5 | "authors": [ 6 | "Jmeas " 7 | ], 8 | "description": "A better starting point for a new Backbone Router.", 9 | "main": "dist/backbone.base-router.js", 10 | "keywords": [ 11 | "backbone", 12 | "router", 13 | "routing", 14 | "url", 15 | "uri", 16 | "query", 17 | "params", 18 | "history" 19 | ], 20 | "license": "MIT", 21 | "ignore": [ 22 | "**/.*", 23 | "node_modules", 24 | "bower_components", 25 | "test", 26 | "tests", 27 | "examples" 28 | ], 29 | "dependencies": { 30 | "backbone": "1.1.1 - 1.3.3", 31 | "underscore": "1.4.4 - 1.8.3" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /dist/backbone.base-router.js: -------------------------------------------------------------------------------- 1 | // Backbone.BaseRouter v1.3.2 2 | (function(root, factory) { 3 | if (typeof define === 'function' && define.amd) { 4 | define(['backbone', 'underscore'], function(Backbone, _) { 5 | return factory(Backbone, _); 6 | }); 7 | } 8 | else if (typeof exports !== 'undefined') { 9 | var Backbone = require('backbone'); 10 | var _ = require('underscore'); 11 | module.exports = factory(Backbone, _); 12 | } 13 | else { 14 | factory(root.Backbone, root._); 15 | } 16 | }(this, function(Backbone, _) { 17 | 'use strict'; 18 | 19 | // 20 | // Backbone.BaseRouter 21 | // 22 | 23 | // This is copied over from Backbone, because it doesn't expose it 24 | var NAMED_PARAM = /(\(\?)?[:*]\w+/g; 25 | // Find plus symbols 26 | var PLUS_SYMBOL = /\+/g; 27 | 28 | Backbone.BaseRouter = Backbone.Router.extend({ 29 | constructor: function() { 30 | this.routeParams = {}; 31 | Backbone.Router.prototype.constructor.apply(this, arguments); 32 | }, 33 | 34 | // The single point of entry. This is called whenever a 35 | // route is matched. The routeData argument contains lots of 36 | // useful information. 37 | onNavigate: function(routeData) {}, 38 | 39 | route: function(origRoute, linked) { 40 | var route, routeStr; 41 | 42 | if (_.isRegExp(origRoute)) { 43 | route = origRoute; 44 | routeStr = '' + origRoute; 45 | } else { 46 | route = this._routeToRegExp(origRoute); 47 | routeStr = origRoute; 48 | } 49 | 50 | this.routeParams[origRoute] = _.map(routeStr.match(NAMED_PARAM), function (param) { 51 | return param.slice(1); 52 | }); 53 | 54 | // Begin setting up our routeData, 55 | // based on what we already know. 56 | var routeData = { 57 | route: route, 58 | router: this, 59 | linked: linked 60 | }; 61 | 62 | // Only attach the originalRoute to routeData if it isn't a RegExp. 63 | if (!_.isRegExp(origRoute)) { 64 | routeData.originalRoute = origRoute; 65 | } 66 | 67 | // Register a callback with history 68 | var router = this; 69 | Backbone.history.route(route, function(fragment, navOptions) { 70 | var routeParams = router._extractParameters(route, fragment); 71 | var queryString = routeParams.pop(); 72 | 73 | // If the user is using baseHistory, then we'll get the navOptions back from BB.History 74 | if (navOptions) { routeData.navOptions = navOptions; } 75 | routeData.queryString = queryString ? queryString : undefined; 76 | routeData.query = router._getQueryParameters(queryString); 77 | routeData.params = router._getNamedParams(routeStr, routeParams); 78 | routeData.uriFragment = fragment; 79 | 80 | router.onNavigate(routeData); 81 | }); 82 | 83 | return this; 84 | }, 85 | 86 | // Decodes the Url query string parameters & and returns them 87 | // as an object. Supports empty parameters, but not array-like 88 | // parameters (which aren't in the URI specification) 89 | _getQueryParameters: function(queryString) { 90 | if (!queryString) { return {}; } 91 | 92 | return _.reduce(queryString.split('&'), function(memo, param) { 93 | var parts = param.replace(PLUS_SYMBOL, ' ').split('='); 94 | var key = parts[0]; 95 | var val = parts[1]; 96 | 97 | key = decodeURIComponent(key); 98 | val = val === undefined ? null : decodeURIComponent(val); 99 | 100 | // If we don't have the value, then we set it. 101 | if (!memo[key]) { 102 | memo[key] = val; 103 | } 104 | 105 | // Otherwise, if we have the value, and it's an array, 106 | // then we push to it. 107 | else if (_.isArray(memo[key])) { 108 | memo[key].push(val); 109 | } 110 | 111 | // Otherwise, we have a value that is not yet an array, 112 | // so we convert it to an array, adding the newest value. 113 | else { 114 | memo[key] = [memo[key], val]; 115 | } 116 | 117 | return memo; 118 | }, {}); 119 | }, 120 | 121 | // Returns the named parameters of the route 122 | _getNamedParams: function(route, routeParams) { 123 | if (!routeParams.length) { return {}; } 124 | 125 | var routeKeys = this.routeParams[route]; 126 | var routeValues = routeParams.slice(0, routeKeys.length); 127 | return _.reduce(_.zip(routeKeys, routeValues), function (obj, opts) { 128 | obj[opts[0]] = opts[1]; 129 | return obj; 130 | }, {}); 131 | } 132 | }); 133 | 134 | 135 | return Backbone.BaseRouter; 136 | })); 137 | -------------------------------------------------------------------------------- /dist/backbone.base-router.min.js: -------------------------------------------------------------------------------- 1 | // Backbone.BaseRouter v1.3.2 2 | 3 | !function(a,b){if("function"==typeof define&&define.amd)define(["backbone","underscore"],function(a,c){return b(a,c)});else if("undefined"!=typeof exports){var c=require("backbone"),d=require("underscore");module.exports=b(c,d)}else b(a.Backbone,a._)}(this,function(a,b){"use strict";var c=/(\(\?)?[:*]\w+/g,d=/\+/g;return a.BaseRouter=a.Router.extend({constructor:function(){this.routeParams={},a.Router.prototype.constructor.apply(this,arguments)},onNavigate:function(a){},route:function(d,e){var f,g;b.isRegExp(d)?(f=d,g=""+d):(f=this._routeToRegExp(d),g=d),this.routeParams[d]=b.map(g.match(c),function(a){return a.slice(1)});var h={route:f,router:this,linked:e};b.isRegExp(d)||(h.originalRoute=d);var i=this;return a.history.route(f,function(a,b){var c=i._extractParameters(f,a),d=c.pop();b&&(h.navOptions=b),h.queryString=d?d:void 0,h.query=i._getQueryParameters(d),h.params=i._getNamedParams(g,c),h.uriFragment=a,i.onNavigate(h)}),this},_getQueryParameters:function(a){return a?b.reduce(a.split("&"),function(a,c){var e=c.replace(d," ").split("="),f=e[0],g=e[1];return f=decodeURIComponent(f),g=void 0===g?null:decodeURIComponent(g),a[f]?b.isArray(a[f])?a[f].push(g):a[f]=[a[f],g]:a[f]=g,a},{}):{}},_getNamedParams:function(a,c){if(!c.length)return{};var d=this.routeParams[a],e=c.slice(0,d.length);return b.reduce(b.zip(d,e),function(a,b){return a[b[0]]=b[1],a},{})}}),a.BaseRouter}); 4 | //# sourceMappingURL=backbone.base-router.min.js.map -------------------------------------------------------------------------------- /dist/backbone.base-router.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["backbone.base-router.js"],"names":["root","factory","define","amd","Backbone","_","exports","require","module","this","NAMED_PARAM","PLUS_SYMBOL","BaseRouter","Router","extend","constructor","routeParams","prototype","apply","arguments","onNavigate","routeData","route","origRoute","linked","routeStr","isRegExp","_routeToRegExp","map","match","param","slice","router","originalRoute","history","fragment","navOptions","_extractParameters","queryString","pop","undefined","query","_getQueryParameters","params","_getNamedParams","uriFragment","reduce","split","memo","parts","replace","key","val","decodeURIComponent","isArray","push","length","routeKeys","routeValues","zip","obj","opts"],"mappings":";;CACC,SAASA,EAAMC,GACd,GAAsB,kBAAXC,SAAyBA,OAAOC,IACzCD,QAAQ,WAAY,cAAe,SAASE,EAAUC,GACpD,MAAOJ,GAAQG,EAAUC,SAGxB,IAAuB,mBAAZC,SAAyB,CACvC,GAAIF,GAAWG,QAAQ,YACnBF,EAAIE,QAAQ,aAChBC,QAAOF,QAAUL,EAAQG,EAAUC,OAGnCJ,GAAQD,EAAKI,SAAUJ,EAAKK,IAE9BI,KAAM,SAASL,EAAUC,GACzB,YAOA,IAAIK,GAAc,kBAEdC,EAAc,KA6GlB,OA3GAP,GAASQ,WAAaR,EAASS,OAAOC,QACpCC,YAAa,WACXN,KAAKO,eACLZ,EAASS,OAAOI,UAAUF,YAAYG,MAAMT,KAAMU,YAMpDC,WAAY,SAASC,KAErBC,MAAO,SAASC,EAAWC,GACzB,GAAIF,GAAOG,CAEPpB,GAAEqB,SAASH,IACbD,EAAQC,EACRE,EAAW,GAAKF,IAEhBD,EAAQb,KAAKkB,eAAeJ,GAC5BE,EAAWF,GAGbd,KAAKO,YAAYO,GAAalB,EAAEuB,IAAIH,EAASI,MAAMnB,GAAc,SAAUoB,GACvE,MAAOA,GAAMC,MAAM,IAKvB,IAAIV,IACFC,MAAOA,EACPU,OAAQvB,KACRe,OAAQA,EAILnB,GAAEqB,SAASH,KACdF,EAAUY,cAAgBV,EAI5B,IAAIS,GAASvB,IAeb,OAdAL,GAAS8B,QAAQZ,MAAMA,EAAO,SAASa,EAAUC,GAC/C,GAAIpB,GAAcgB,EAAOK,mBAAmBf,EAAOa,GAC/CG,EAActB,EAAYuB,KAG1BH,KAAcf,EAAUe,WAAaA,GACzCf,EAAUiB,YAAcA,EAAcA,EAAcE,OACpDnB,EAAUoB,MAAQT,EAAOU,oBAAoBJ,GAC7CjB,EAAUsB,OAASX,EAAOY,gBAAgBnB,EAAUT,GACpDK,EAAUwB,YAAcV,EAExBH,EAAOZ,WAAWC,KAGbZ,MAMTiC,oBAAqB,SAASJ,GAC5B,MAAKA,GAEEjC,EAAEyC,OAAOR,EAAYS,MAAM,KAAM,SAASC,EAAMlB,GACrD,GAAImB,GAAQnB,EAAMoB,QAAQvC,EAAa,KAAKoC,MAAM,KAC9CI,EAAMF,EAAM,GACZG,EAAMH,EAAM,EAsBhB,OApBAE,GAAME,mBAAmBF,GACzBC,EAAcZ,SAARY,EAAoB,KAAOC,mBAAmBD,GAG/CJ,EAAKG,GAMD9C,EAAEiD,QAAQN,EAAKG,IACtBH,EAAKG,GAAKI,KAAKH,GAMfJ,EAAKG,IAAQH,EAAKG,GAAMC,GAZxBJ,EAAKG,GAAOC,EAePJ,WAKXJ,gBAAiB,SAAStB,EAAON,GAC/B,IAAKA,EAAYwC,OAAU,QAE3B,IAAIC,GAAYhD,KAAKO,YAAYM,GAC7BoC,EAAc1C,EAAYe,MAAM,EAAG0B,EAAUD,OACjD,OAAOnD,GAAEyC,OAAOzC,EAAEsD,IAAIF,EAAWC,GAAc,SAAUE,EAAKC,GAE1D,MADAD,GAAIC,EAAK,IAAMA,EAAK,GACbD,UAMRxD,EAASQ","file":"backbone.base-router.min.js"} -------------------------------------------------------------------------------- /examples/fetch-router/README.md: -------------------------------------------------------------------------------- 1 | # Fetch Router 2 | 3 | This Router lets you fetch data, then display a view, for each Route. 4 | 5 | ### Running the example 6 | 7 | This example depends on `node-static`. Install it via 8 | 9 | ```sh 10 | $ npm install -g node-static 11 | ``` 12 | 13 | Now make sure that you've installed the dependencies for the project. 14 | 15 | ```sh 16 | $ npm install 17 | ``` 18 | 19 | From the root directory **of this repository**, run `static`. Then navigate your browser to: 20 | 21 | `http://127.0.0.1:8080/examples/fetch-router/` 22 | -------------------------------------------------------------------------------- /examples/fetch-router/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Routing Test 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/fetch-router/script.js: -------------------------------------------------------------------------------- 1 | var Model = Backbone.Model.extend({ 2 | 3 | // Because I don't actually want to fetch any data for this example, 4 | // I'm overriding the method to return a Promise that resolves after 1s has passed. 5 | fetch: function() { 6 | return new Promise(function(resolve) { 7 | window.setTimeout(function() { 8 | resolve(true); 9 | }, 1000); 10 | }); 11 | } 12 | }); 13 | 14 | var Router = Backbone.BaseRouter.extend({ 15 | onNavigate: function(routeData) { 16 | var RouteObj = routeData.linked; 17 | RouteObj.fetch().then(RouteObj.show); 18 | }, 19 | 20 | routes: { 21 | 'examples/fetch-router(/)': { 22 | 23 | fetch: function() { 24 | console.log('Fetching data...'); 25 | this.model = new Model(); 26 | return this.model.fetch(); 27 | }, 28 | 29 | show: function() { 30 | console.log('Data fetched. Showing the view.'); 31 | } 32 | } 33 | } 34 | }); 35 | 36 | var router = new Router(); 37 | 38 | Backbone.history.start({ 39 | pushState: true 40 | }); 41 | -------------------------------------------------------------------------------- /examples/old-router/README.md: -------------------------------------------------------------------------------- 1 | # Old Router 2 | 3 | This re-implements something similar to a standard Backbone Router. But it's better, 4 | because it gives you your named params as an object and parses the query string for you. 5 | 6 | ### Running the example 7 | 8 | This example depends on `node-static`. Install it via 9 | 10 | ```sh 11 | $ npm install -g node-static 12 | ``` 13 | 14 | Now make sure that you've installed the dependencies for the project. 15 | 16 | ```sh 17 | $ npm install 18 | ``` 19 | 20 | From the root directory **of this repository**, run `static`. Then navigate your browser to: 21 | 22 | `http://127.0.0.1:8080/examples/old-router/` 23 | -------------------------------------------------------------------------------- /examples/old-router/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Routing Test 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/old-router/script.js: -------------------------------------------------------------------------------- 1 | var Router = Backbone.BaseRouter.extend({ 2 | onNavigate: function(routeData) { 3 | this.trigger('before:route', routeData); 4 | routeData.linked(routeData); 5 | this.trigger('route', routeData); 6 | }, 7 | 8 | routes: { 9 | 'examples/old-router(/)': function(routeData) { 10 | console.log('This route matched, and I got some cool data:', routeData); 11 | } 12 | } 13 | }); 14 | 15 | var router = new Router(); 16 | 17 | Backbone.history.start({ 18 | pushState: true 19 | }); 20 | -------------------------------------------------------------------------------- /gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | require('load-grunt-tasks')(grunt); 4 | 5 | // Project configuration. 6 | grunt.initConfig({ 7 | pkg: grunt.file.readJSON('package.json'), 8 | meta: { 9 | version: '<%= pkg.version %>', 10 | banner: '// Backbone.BaseRouter v<%= meta.version %>\n' 11 | }, 12 | 13 | preprocess: { 14 | baseRouter: { 15 | src: 'src/wrapper.js', 16 | dest: 'dist/backbone.base-router.js' 17 | } 18 | }, 19 | 20 | template: { 21 | options: { 22 | data: { 23 | version: '<%= meta.version %>' 24 | } 25 | }, 26 | baseRouter: { 27 | src: '<%= preprocess.baseRouter.dest %>', 28 | dest: '<%= preprocess.baseRouter.dest %>' 29 | } 30 | }, 31 | 32 | concat: { 33 | options: { 34 | banner: '<%= meta.banner %>' 35 | }, 36 | baseRouter: { 37 | src: '<%= preprocess.baseRouter.dest %>', 38 | dest: '<%= preprocess.baseRouter.dest %>' 39 | } 40 | }, 41 | 42 | uglify: { 43 | options: { 44 | banner: '<%= meta.banner %>' 45 | }, 46 | baseRouter: { 47 | src: '<%= preprocess.baseRouter.dest %>', 48 | dest: 'dist/backbone.base-router.min.js', 49 | options: { 50 | sourceMap: true 51 | } 52 | } 53 | }, 54 | 55 | jshint: { 56 | baseRouter: { 57 | options: { 58 | jshintrc: '.jshintrc' 59 | }, 60 | src: ['src/backbone.base-router.js'] 61 | }, 62 | tests: { 63 | options: { 64 | jshintrc: 'test/.jshintrc' 65 | }, 66 | src: ['test/unit/*.js'] 67 | } 68 | }, 69 | 70 | mochaTest: { 71 | spec: { 72 | options: { 73 | require: 'test/setup/node.js', 74 | reporter: 'dot', 75 | clearRequireCache: true, 76 | mocha: require('mocha') 77 | }, 78 | src: [ 79 | 'test/setup/helpers.js', 80 | 'test/unit/*.js' 81 | ] 82 | } 83 | } 84 | }); 85 | 86 | grunt.registerTask('test', 'Test the library', [ 87 | 'jshint', 88 | 'mochaTest' 89 | ]); 90 | 91 | grunt.registerTask('build', 'Build the library', [ 92 | 'test', 93 | 'preprocess', 94 | 'template', 95 | 'concat', 96 | 'uglify' 97 | ]); 98 | 99 | grunt.registerTask('default', 'An alias of test', [ 100 | 'test' 101 | ]); 102 | }; 103 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backbone.base-router", 3 | "version": "1.3.2", 4 | "description": "A better starting point for a new Backbone Router.", 5 | "main": "dist/backbone.base-router.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "grunt" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/jmeas/backbone.base-router.git" 15 | }, 16 | "keywords": [ 17 | "backbone", 18 | "router", 19 | "routing", 20 | "url", 21 | "uri", 22 | "query", 23 | "params", 24 | "history" 25 | ], 26 | "author": "Jmeas", 27 | "license": "MIT", 28 | "bugs": { 29 | "url": "https://github.com/jmeas/backbone.base-router/issues" 30 | }, 31 | "homepage": "https://github.com/jmeas/backbone.base-router", 32 | "devDependencies": { 33 | "chai": "^1.9.2", 34 | "grunt": "^0.4.5", 35 | "grunt-cli": "^0.1.13", 36 | "grunt-contrib-concat": "^0.5.0", 37 | "grunt-contrib-jshint": "^0.10.0", 38 | "grunt-contrib-uglify": "^0.6.0", 39 | "grunt-mocha-test": "^0.12.1", 40 | "grunt-preprocess": "^4.0.0", 41 | "grunt-template": "^0.2.3", 42 | "jquery": "^2.1.1", 43 | "jsdom": "^1.0.0", 44 | "load-grunt-tasks": "^0.6.0", 45 | "mocha": "^1.21.4", 46 | "sinon": "^1.10.3", 47 | "sinon-chai": "^2.5.0" 48 | }, 49 | "dependencies": { 50 | "backbone": "1.1.1 - 1.3.3", 51 | "underscore": "1.4.4 - 1.8.3" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/backbone.base-router.js: -------------------------------------------------------------------------------- 1 | // 2 | // Backbone.BaseRouter 3 | // 4 | 5 | // This is copied over from Backbone, because it doesn't expose it 6 | var NAMED_PARAM = /(\(\?)?[:*]\w+/g; 7 | // Find plus symbols 8 | var PLUS_SYMBOL = /\+/g; 9 | 10 | Backbone.BaseRouter = Backbone.Router.extend({ 11 | constructor: function() { 12 | this.routeParams = {}; 13 | Backbone.Router.prototype.constructor.apply(this, arguments); 14 | }, 15 | 16 | // The single point of entry. This is called whenever a 17 | // route is matched. The routeData argument contains lots of 18 | // useful information. 19 | onNavigate: function(routeData) {}, 20 | 21 | route: function(origRoute, linked) { 22 | var route, routeStr; 23 | 24 | if (_.isRegExp(origRoute)) { 25 | route = origRoute; 26 | routeStr = '' + origRoute; 27 | } else { 28 | route = this._routeToRegExp(origRoute); 29 | routeStr = origRoute; 30 | } 31 | 32 | this.routeParams[origRoute] = _.map(routeStr.match(NAMED_PARAM), function (param) { 33 | return param.slice(1); 34 | }); 35 | 36 | // Begin setting up our routeData, 37 | // based on what we already know. 38 | var routeData = { 39 | route: route, 40 | router: this, 41 | linked: linked 42 | }; 43 | 44 | // Only attach the originalRoute to routeData if it isn't a RegExp. 45 | if (!_.isRegExp(origRoute)) { 46 | routeData.originalRoute = origRoute; 47 | } 48 | 49 | // Register a callback with history 50 | var router = this; 51 | Backbone.history.route(route, function(fragment, navOptions) { 52 | var routeParams = router._extractParameters(route, fragment); 53 | var queryString = routeParams.pop(); 54 | 55 | // If the user is using baseHistory, then we'll get the navOptions back from BB.History 56 | if (navOptions) { routeData.navOptions = navOptions; } 57 | routeData.queryString = queryString ? queryString : undefined; 58 | routeData.query = router._getQueryParameters(queryString); 59 | routeData.params = router._getNamedParams(routeStr, routeParams); 60 | routeData.uriFragment = fragment; 61 | 62 | router.onNavigate(routeData); 63 | }); 64 | 65 | return this; 66 | }, 67 | 68 | // Decodes the Url query string parameters & and returns them 69 | // as an object. Supports empty parameters, but not array-like 70 | // parameters (which aren't in the URI specification) 71 | _getQueryParameters: function(queryString) { 72 | if (!queryString) { return {}; } 73 | 74 | return _.reduce(queryString.split('&'), function(memo, param) { 75 | var parts = param.replace(PLUS_SYMBOL, ' ').split('='); 76 | var key = parts[0]; 77 | var val = parts[1]; 78 | 79 | key = decodeURIComponent(key); 80 | val = val === undefined ? null : decodeURIComponent(val); 81 | 82 | // If we don't have the value, then we set it. 83 | if (!memo[key]) { 84 | memo[key] = val; 85 | } 86 | 87 | // Otherwise, if we have the value, and it's an array, 88 | // then we push to it. 89 | else if (_.isArray(memo[key])) { 90 | memo[key].push(val); 91 | } 92 | 93 | // Otherwise, we have a value that is not yet an array, 94 | // so we convert it to an array, adding the newest value. 95 | else { 96 | memo[key] = [memo[key], val]; 97 | } 98 | 99 | return memo; 100 | }, {}); 101 | }, 102 | 103 | // Returns the named parameters of the route 104 | _getNamedParams: function(route, routeParams) { 105 | if (!routeParams.length) { return {}; } 106 | 107 | var routeKeys = this.routeParams[route]; 108 | var routeValues = routeParams.slice(0, routeKeys.length); 109 | return _.reduce(_.zip(routeKeys, routeValues), function (obj, opts) { 110 | obj[opts[0]] = opts[1]; 111 | return obj; 112 | }, {}); 113 | } 114 | }); 115 | -------------------------------------------------------------------------------- /src/wrapper.js: -------------------------------------------------------------------------------- 1 | (function(root, factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | define(['backbone', 'underscore'], function(Backbone, _) { 4 | return factory(Backbone, _); 5 | }); 6 | } 7 | else if (typeof exports !== 'undefined') { 8 | var Backbone = require('backbone'); 9 | var _ = require('underscore'); 10 | module.exports = factory(Backbone, _); 11 | } 12 | else { 13 | factory(root.Backbone, root._); 14 | } 15 | }(this, function(Backbone, _) { 16 | 'use strict'; 17 | 18 | // @include backbone.base-router.js 19 | 20 | return Backbone.BaseRouter; 21 | })); 22 | -------------------------------------------------------------------------------- /test/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "globals": { 3 | "define" : true, 4 | "require" : true, 5 | "module" : true, 6 | "exports" : true, 7 | "$" : true, 8 | "jQuery" : true, 9 | "_" : true, 10 | "Backbone" : true, 11 | "describe" : true, 12 | "it" : true, 13 | "expect" : true, 14 | "beforeEach": true, 15 | "afterEach" : true 16 | }, 17 | 18 | "bitwise" : true, 19 | "camelcase" : true, 20 | "curly" : true, 21 | "eqeqeq" : true, 22 | "es3" : true, 23 | "forin" : true, 24 | "immed" : true, 25 | "indent" : 2, 26 | "latedef" : true, 27 | "newcap" : true, 28 | "noarg" : true, 29 | "noempty" : true, 30 | "nonbsp" : true, 31 | "nonew" : true, 32 | "plusplus" : true, 33 | "quotmark" : "single", 34 | "undef" : true, 35 | "unused" : "vars", 36 | "strict" : false, 37 | "trailing" : true, 38 | "maxparams" : 4, 39 | "maxdepth" : 10, 40 | "maxcomplexity" : 6, 41 | "maxlen" : 150, 42 | 43 | "asi" : false, 44 | "boss" : true, 45 | "debug" : false, 46 | "eqnull" : false, 47 | "esnext" : false, 48 | "evil" : false, 49 | "expr" : true, 50 | "funcscope" : false, 51 | "globalstrict" : false, 52 | "iterator" : false, 53 | "lastsemic" : false, 54 | "laxbreak" : false, 55 | "laxcomma" : false, 56 | "loopfunc" : false, 57 | "maxerr" : 50, 58 | "multistr" : false, 59 | "notypeof" : false, 60 | "proto" : false, 61 | "scripturl" : false, 62 | "smarttabs" : false, 63 | "shadow" : false, 64 | "sub" : false, 65 | "supernew" : false, 66 | "validthis" : false, 67 | "noyield" : false, 68 | 69 | "browser" : true, 70 | "couch" : false, 71 | "devel" : false, 72 | "dojo" : false, 73 | "jquery" : false, 74 | "mootools" : false, 75 | "node" : false, 76 | "nonstandard" : false, 77 | "prototypejs" : false, 78 | "rhino" : false, 79 | "worker" : false, 80 | "wsh" : false, 81 | "yui" : false 82 | } 83 | -------------------------------------------------------------------------------- /test/setup/helpers.js: -------------------------------------------------------------------------------- 1 | function setupTestHelpers() { 2 | beforeEach(function() { 3 | this.Location = function(href) { 4 | this.replace(href); 5 | }; 6 | 7 | _.extend(this.Location.prototype, { 8 | parser: document.createElement('a'), 9 | replace: function(href) { 10 | this.parser.href = href; 11 | _.extend(this, _.pick(this.parser, 12 | 'href', 13 | 'hash', 14 | 'host', 15 | 'search', 16 | 'fragment', 17 | 'pathname', 18 | 'protocol' 19 | )); 20 | // In IE, anchor.pathname does not contain a leading slash though 21 | // window.location.pathname does. 22 | if (!/^\//.test(this.pathname)) this.pathname = '/' + this.pathname; 23 | }, 24 | toString: function() { 25 | return this.href; 26 | } 27 | }); 28 | 29 | this.sinon = sinon.sandbox.create(); 30 | global.stub = _.bind(this.sinon.stub, this.sinon); 31 | global.spy = _.bind(this.sinon.spy, this.sinon); 32 | }); 33 | 34 | afterEach(function() { 35 | global.Backbone.history.stop(); 36 | this.sinon.restore(); 37 | delete global.stub; 38 | delete global.spy; 39 | }); 40 | } 41 | 42 | var node = typeof exports !== 'undefined'; 43 | var $ = node ? require('jquery') : $; 44 | 45 | if (node) { 46 | setupTestHelpers(); 47 | } 48 | 49 | // when running in a browser 50 | else { 51 | this.global = window; 52 | mocha.setup('bdd'); 53 | 54 | window.expect = chai.expect; 55 | window.sinon = sinon; 56 | 57 | onload = function() { 58 | mocha.checkLeaks(); 59 | mocha.globals(['stub', 'spy']); 60 | mocha.run(); 61 | setupTestHelpers(); 62 | }; 63 | } 64 | -------------------------------------------------------------------------------- /test/setup/node.js: -------------------------------------------------------------------------------- 1 | // Create our JSDom document 2 | global.jsdom = require('jsdom').jsdom; 3 | global.document = jsdom(''); 4 | global.window = global.document.parentWindow; 5 | global.navigator = window.navigator = { 6 | userAgent: 'NodeJS JSDom', 7 | appVersion: '' 8 | }; 9 | 10 | var sinon = require('sinon'); 11 | var chai = require('chai'); 12 | var sinonChai = require('sinon-chai'); 13 | 14 | global.$ = require('jquery'); 15 | global._ = require('underscore'); 16 | global.Backbone = require('backbone'); 17 | global.Backbone.$ = global.$; 18 | 19 | chai.use(sinonChai); 20 | 21 | global.expect = chai.expect; 22 | global.sinon = sinon; 23 | 24 | require('../../src/backbone.base-router'); 25 | -------------------------------------------------------------------------------- /test/spec-runner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Backbone.BaseRouter Tests 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 | 32 | 33 | -------------------------------------------------------------------------------- /test/unit/base-router.js: -------------------------------------------------------------------------------- 1 | describe('Base Router', function() { 2 | beforeEach(function() { 3 | this.router = new Backbone.BaseRouter(); 4 | var suite = this; 5 | this.router.onNavigate = function(routeData) { 6 | suite.routeData = routeData; 7 | }; 8 | this.sinon.spy(this.router, 'onNavigate'); 9 | }); 10 | 11 | describe('when routing to a matched route with pushState', function() { 12 | beforeEach(function() { 13 | Backbone.history.location = new this.Location('http://example.com/example'); 14 | this.router.route('example', {hungry: true}); 15 | Backbone.history.start({pushState: true}); 16 | this.regularKeys = ['linked', 'route', 'originalRoute', 'params', 'query', 'queryString', 'router', 'uriFragment']; 17 | }); 18 | 19 | it('should trigger onNavigate', function() { 20 | expect(this.router.onNavigate).to.have.been.calledOnce; 21 | }); 22 | 23 | it('should pass the correct arguments to onNavigate', function() { 24 | expect(this.routeData).to.have.keys(this.regularKeys); 25 | }); 26 | 27 | it('should pass the object that was linked with the route', function() { 28 | expect(this.routeData.linked).to.deep.equal({hungry: true}); 29 | }); 30 | 31 | it('should pass the Router instance', function() { 32 | expect(this.routeData.router).to.equal(this.router); 33 | }); 34 | 35 | it('should pass the original matched Route', function() { 36 | expect(this.routeData.originalRoute).to.equal('example'); 37 | }); 38 | 39 | it('should pass the current URI fragment', function() { 40 | expect(this.routeData.uriFragment).to.equal('example'); 41 | }); 42 | 43 | it('should pass a Regular expression as the route', function() { 44 | expect(this.routeData.route).to.be.instanceOf(RegExp); 45 | }); 46 | 47 | it('should pass an empty object for query params', function() { 48 | expect(this.routeData.query).to.deep.equal({}); 49 | }); 50 | 51 | it('should pass undefined for queryString', function() { 52 | expect(this.routeData.queryString).to.equal(undefined); 53 | }); 54 | 55 | it('should pass an empty object for named params', function() { 56 | expect(this.routeData.params).to.deep.equal({}); 57 | }); 58 | }); 59 | 60 | describe('when routing to a matched route without pushState', function() { 61 | beforeEach(function() { 62 | Backbone.history.location = new this.Location('http://example.com#example'); 63 | this.router.route('example', {hungry: true}); 64 | Backbone.history.start({pushState: false}); 65 | this.regularKeys = ['linked', 'route', 'originalRoute', 'params', 'query', 'queryString', 'router', 'uriFragment']; 66 | }); 67 | 68 | it('should trigger onNavigate', function() { 69 | expect(this.router.onNavigate).to.have.been.calledOnce; 70 | }); 71 | 72 | it('should pass the correct arguments to onNavigate', function() { 73 | expect(this.routeData).to.have.keys(this.regularKeys); 74 | }); 75 | 76 | it('should pass the object that was linked with the route', function() { 77 | expect(this.routeData.linked).to.deep.equal({hungry: true}); 78 | }); 79 | 80 | it('should pass the Router instance', function() { 81 | expect(this.routeData.router).to.equal(this.router); 82 | }); 83 | 84 | it('should pass the original matched Route', function() { 85 | expect(this.routeData.originalRoute).to.equal('example'); 86 | }); 87 | 88 | it('should pass the current URI fragment', function() { 89 | expect(this.routeData.uriFragment).to.equal('example'); 90 | }); 91 | 92 | it('should pass a Regular expression as the route', function() { 93 | expect(this.routeData.route).to.be.instanceOf(RegExp); 94 | }); 95 | 96 | it('should pass an empty object for query params', function() { 97 | expect(this.routeData.query).to.deep.equal({}); 98 | }); 99 | 100 | it('should pass undefined for queryString', function() { 101 | expect(this.routeData.queryString).to.equal(undefined); 102 | }); 103 | 104 | it('should pass an empty object for named params', function() { 105 | expect(this.routeData.params).to.deep.equal({}); 106 | }); 107 | }); 108 | 109 | describe('when routing to a matched route with pushState that has named params', function() { 110 | beforeEach(function() { 111 | Backbone.history.location = new this.Location('http://example.com/books/2/chapter/4'); 112 | this.callback = function() {}; 113 | this.router.route('books/:id/chapter/:chapter', this.callback); 114 | Backbone.history.start({pushState: true}); 115 | }); 116 | 117 | it('should trigger onNavigate', function() { 118 | expect(this.router.onNavigate).to.have.been.calledOnce; 119 | }); 120 | 121 | it('should pass the object that was linked with the route', function() { 122 | expect(this.routeData.linked).to.deep.equal(this.callback); 123 | }); 124 | 125 | it('should pass the Router instance', function() { 126 | expect(this.routeData.router).to.equal(this.router); 127 | }); 128 | 129 | it('should pass the original matched Route', function() { 130 | expect(this.routeData.originalRoute).to.equal('books/:id/chapter/:chapter'); 131 | }); 132 | 133 | it('should pass the current URI fragment', function() { 134 | expect(this.routeData.uriFragment).to.equal('books/2/chapter/4'); 135 | }); 136 | 137 | it('should pass a Regular expression as the route', function() { 138 | expect(this.routeData.route).to.be.instanceOf(RegExp); 139 | }); 140 | 141 | it('should pass an empty object for query params', function() { 142 | expect(this.routeData.query).to.deep.equal({}); 143 | }); 144 | 145 | it('should pass undefined for queryString', function() { 146 | expect(this.routeData.queryString).to.equal(undefined); 147 | }); 148 | 149 | it('should pass the parsed URI params', function() { 150 | expect(this.routeData.params).to.deep.equal({id: '2', chapter: '4'}); 151 | }); 152 | }); 153 | 154 | describe('when routing to a matched route without pushState that has named params', function() { 155 | beforeEach(function() { 156 | Backbone.history.location = new this.Location('http://example.com#books/2/chapter/4'); 157 | this.callback = function() {}; 158 | this.router.route('books/:id/chapter/:chapter', this.callback); 159 | Backbone.history.start({pushState: false}); 160 | }); 161 | 162 | it('should trigger onNavigate', function() { 163 | expect(this.router.onNavigate).to.have.been.calledOnce; 164 | }); 165 | 166 | it('should pass the object that was linked with the route', function() { 167 | expect(this.routeData.linked).to.deep.equal(this.callback); 168 | }); 169 | 170 | it('should pass the Router instance', function() { 171 | expect(this.routeData.router).to.equal(this.router); 172 | }); 173 | 174 | it('should pass the original matched Route', function() { 175 | expect(this.routeData.originalRoute).to.equal('books/:id/chapter/:chapter'); 176 | }); 177 | 178 | it('should pass the current URI fragment', function() { 179 | expect(this.routeData.uriFragment).to.equal('books/2/chapter/4'); 180 | }); 181 | 182 | it('should pass a Regular expression as the route', function() { 183 | expect(this.routeData.route).to.be.instanceOf(RegExp); 184 | }); 185 | 186 | it('should pass an empty object for query params', function() { 187 | expect(this.routeData.query).to.deep.equal({}); 188 | }); 189 | 190 | it('should pass undefined for queryString', function() { 191 | expect(this.routeData.queryString).to.equal(undefined); 192 | }); 193 | 194 | it('should pass the parsed URI params', function() { 195 | expect(this.routeData.params).to.deep.equal({id: '2', chapter: '4'}); 196 | }); 197 | }); 198 | 199 | describe('when routing to a matched route with pushState and query params', function() { 200 | beforeEach(function() { 201 | Backbone.history.location = new this.Location('http://example.com/example?james=true&hungry=3'); 202 | this.callback = function() {}; 203 | this.router.route('example', this.callback); 204 | Backbone.history.start({pushState: true}); 205 | }); 206 | 207 | it('should trigger onNavigate', function() { 208 | expect(this.router.onNavigate).to.have.been.calledOnce; 209 | }); 210 | 211 | it('should pass the object that was linked with the route', function() { 212 | expect(this.routeData.linked).to.deep.equal(this.callback); 213 | }); 214 | 215 | it('should pass the Router instance', function() { 216 | expect(this.routeData.router).to.equal(this.router); 217 | }); 218 | 219 | it('should pass the original matched Route', function() { 220 | expect(this.routeData.originalRoute).to.equal('example'); 221 | }); 222 | 223 | it('should pass the current URI fragment', function() { 224 | expect(this.routeData.uriFragment).to.equal('example?james=true&hungry=3'); 225 | }); 226 | 227 | it('should pass a Regular expression as the route', function() { 228 | expect(this.routeData.route).to.be.instanceOf(RegExp); 229 | }); 230 | 231 | it('should pass the parsed query params', function() { 232 | expect(this.routeData.query).to.deep.equal({james: 'true', hungry: '3'}); 233 | }); 234 | 235 | it('should pass the original query string', function() { 236 | expect(this.routeData.queryString).to.equal('james=true&hungry=3'); 237 | }); 238 | 239 | it('should pass an empty object for named params', function() { 240 | expect(this.routeData.params).to.deep.equal({}); 241 | }); 242 | }); 243 | 244 | describe('when routing to a matched route without pushState and query params', function() { 245 | beforeEach(function() { 246 | Backbone.history.location = new this.Location('http://example.com#example?james=true&hungry=3'); 247 | this.callback = function() {}; 248 | this.router.route('example', this.callback); 249 | Backbone.history.start({pushState: false}); 250 | }); 251 | 252 | it('should trigger onNavigate', function() { 253 | expect(this.router.onNavigate).to.have.been.calledOnce; 254 | }); 255 | 256 | it('should pass the object that was linked with the route', function() { 257 | expect(this.routeData.linked).to.deep.equal(this.callback); 258 | }); 259 | 260 | it('should pass the Router instance', function() { 261 | expect(this.routeData.router).to.equal(this.router); 262 | }); 263 | 264 | it('should pass the original matched Route', function() { 265 | expect(this.routeData.originalRoute).to.equal('example'); 266 | }); 267 | 268 | it('should pass the current URI fragment', function() { 269 | expect(this.routeData.uriFragment).to.equal('example?james=true&hungry=3'); 270 | }); 271 | 272 | it('should pass a Regular expression as the route', function() { 273 | expect(this.routeData.route).to.be.instanceOf(RegExp); 274 | }); 275 | 276 | it('should pass the parsed query params', function() { 277 | expect(this.routeData.query).to.deep.equal({james: 'true', hungry: '3'}); 278 | }); 279 | 280 | it('should pass the original query string', function() { 281 | expect(this.routeData.queryString).to.equal('james=true&hungry=3'); 282 | }); 283 | 284 | it('should pass an empty object for named params', function() { 285 | expect(this.routeData.params).to.deep.equal({}); 286 | }); 287 | }); 288 | 289 | describe('when routing to a matched route with pushState and query params with an array-like syntax', function() { 290 | beforeEach(function() { 291 | Backbone.history.location = new this.Location('http://example.com/example?letters=a&letters=b&letters=c'); 292 | this.callback = function() {}; 293 | this.router.route('example', this.callback); 294 | Backbone.history.start({pushState: true}); 295 | }); 296 | 297 | it('should trigger onNavigate', function() { 298 | expect(this.router.onNavigate).to.have.been.calledOnce; 299 | }); 300 | 301 | it('should pass the object that was linked with the route', function() { 302 | expect(this.routeData.linked).to.deep.equal(this.callback); 303 | }); 304 | 305 | it('should pass the Router instance', function() { 306 | expect(this.routeData.router).to.equal(this.router); 307 | }); 308 | 309 | it('should pass the original matched Route', function() { 310 | expect(this.routeData.originalRoute).to.equal('example'); 311 | }); 312 | 313 | it('should pass the current URI fragment', function() { 314 | expect(this.routeData.uriFragment).to.equal('example?letters=a&letters=b&letters=c'); 315 | }); 316 | 317 | it('should pass a Regular expression as the route', function() { 318 | expect(this.routeData.route).to.be.instanceOf(RegExp); 319 | }); 320 | 321 | it('should pass the parsed query params, not parsing the array-like syntax', function() { 322 | expect(this.routeData.query).to.deep.equal({letters: ['a', 'b', 'c']}); 323 | }); 324 | 325 | it('should pass the original query string', function() { 326 | expect(this.routeData.queryString).to.equal('letters=a&letters=b&letters=c'); 327 | }); 328 | 329 | it('should pass an empty object for named params', function() { 330 | expect(this.routeData.params).to.deep.equal({}); 331 | }); 332 | }); 333 | 334 | describe('when routing to a matched route without pushState and query params with an array-like syntax', function() { 335 | beforeEach(function() { 336 | Backbone.history.location = new this.Location('http://example.com#example?letters=a&letters=b&letters=c'); 337 | this.callback = function() {}; 338 | this.router.route('example', this.callback); 339 | Backbone.history.start({pushState: false}); 340 | }); 341 | 342 | it('should trigger onNavigate', function() { 343 | expect(this.router.onNavigate).to.have.been.calledOnce; 344 | }); 345 | 346 | it('should pass the object that was linked with the route', function() { 347 | expect(this.routeData.linked).to.deep.equal(this.callback); 348 | }); 349 | 350 | it('should pass the Router instance', function() { 351 | expect(this.routeData.router).to.equal(this.router); 352 | }); 353 | 354 | it('should pass the original matched Route', function() { 355 | expect(this.routeData.originalRoute).to.equal('example'); 356 | }); 357 | 358 | it('should pass the current URI fragment', function() { 359 | expect(this.routeData.uriFragment).to.equal('example?letters=a&letters=b&letters=c'); 360 | }); 361 | 362 | it('should pass a Regular expression as the route', function() { 363 | expect(this.routeData.route).to.be.instanceOf(RegExp); 364 | }); 365 | 366 | it('should pass the parsed query params, not parsing the array-like syntax', function() { 367 | expect(this.routeData.query).to.deep.equal({letters: ['a', 'b', 'c']}); 368 | }); 369 | 370 | it('should pass the original query string', function() { 371 | expect(this.routeData.queryString).to.equal('letters=a&letters=b&letters=c'); 372 | }); 373 | 374 | it('should pass an empty object for named params', function() { 375 | expect(this.routeData.params).to.deep.equal({}); 376 | }); 377 | }); 378 | 379 | describe('when routing to a matched route with pushState and named params with a splat', function() { 380 | beforeEach(function() { 381 | Backbone.history.location = new this.Location('http://example.com/3/deeply/nested/splat'); 382 | this.routeObj = {}; 383 | this.router.route(':id/*splat', this.routeObj); 384 | Backbone.history.start({pushState: true}); 385 | }); 386 | 387 | it('should trigger onNavigate', function() { 388 | expect(this.router.onNavigate).to.have.been.calledOnce; 389 | }); 390 | 391 | it('should pass the object that was linked with the route', function() { 392 | expect(this.routeData.linked).to.deep.equal(this.routeObj); 393 | }); 394 | 395 | it('should pass the Router instance', function() { 396 | expect(this.routeData.router).to.equal(this.router); 397 | }); 398 | 399 | it('should pass the original matched Route', function() { 400 | expect(this.routeData.originalRoute).to.equal(':id/*splat'); 401 | }); 402 | 403 | it('should pass the current URI fragment', function() { 404 | expect(this.routeData.uriFragment).to.equal('3/deeply/nested/splat'); 405 | }); 406 | 407 | it('should pass a Regular expression as the route', function() { 408 | expect(this.routeData.route).to.be.instanceOf(RegExp); 409 | }); 410 | 411 | it('should pass an empty object for query params', function() { 412 | expect(this.routeData.query).to.deep.equal({}); 413 | }); 414 | 415 | it('should pass undefined for queryString', function() { 416 | expect(this.routeData.queryString).to.equal(undefined); 417 | }); 418 | 419 | it('should pass the parsed named param', function() { 420 | expect(this.routeData.params).to.deep.equal({id: '3', splat: 'deeply/nested/splat'}); 421 | }); 422 | }); 423 | 424 | describe('when routing to a matched route without pushState and named params with a splat', function() { 425 | beforeEach(function() { 426 | Backbone.history.location = new this.Location('http://example.com#3/deeply/nested/splat'); 427 | this.routeObj = {}; 428 | this.router.route(':id/*splat', this.routeObj); 429 | Backbone.history.start({pushState: false}); 430 | }); 431 | 432 | it('should trigger onNavigate', function() { 433 | expect(this.router.onNavigate).to.have.been.calledOnce; 434 | }); 435 | 436 | it('should pass the object that was linked with the route', function() { 437 | expect(this.routeData.linked).to.deep.equal(this.routeObj); 438 | }); 439 | 440 | it('should pass the Router instance', function() { 441 | expect(this.routeData.router).to.equal(this.router); 442 | }); 443 | 444 | it('should pass the original matched Route', function() { 445 | expect(this.routeData.originalRoute).to.equal(':id/*splat'); 446 | }); 447 | 448 | it('should pass the current URI fragment', function() { 449 | expect(this.routeData.uriFragment).to.equal('3/deeply/nested/splat'); 450 | }); 451 | 452 | it('should pass a Regular expression as the route', function() { 453 | expect(this.routeData.route).to.be.instanceOf(RegExp); 454 | }); 455 | 456 | it('should pass an empty object for query params', function() { 457 | expect(this.routeData.query).to.deep.equal({}); 458 | }); 459 | 460 | it('should pass undefined for queryString', function() { 461 | expect(this.routeData.queryString).to.equal(undefined); 462 | }); 463 | 464 | it('should pass the parsed named param', function() { 465 | expect(this.routeData.params).to.deep.equal({id: '3', splat: 'deeply/nested/splat'}); 466 | }); 467 | }); 468 | 469 | describe('when routing to a matched route with pushState and a splat with query params', function() { 470 | beforeEach(function() { 471 | Backbone.history.location = new this.Location('http://example.com/deeply/nested/splat?name=james'); 472 | this.router.route('*splat', false); 473 | Backbone.history.start({pushState: true}); 474 | }); 475 | 476 | it('should trigger onNavigate', function() { 477 | expect(this.router.onNavigate).to.have.been.calledOnce; 478 | }); 479 | 480 | it('should pass the object that was linked with the route', function() { 481 | expect(this.routeData.linked).to.deep.equal(false); 482 | }); 483 | 484 | it('should pass the Router instance', function() { 485 | expect(this.routeData.router).to.equal(this.router); 486 | }); 487 | 488 | it('should pass the original matched Route', function() { 489 | expect(this.routeData.originalRoute).to.equal('*splat'); 490 | }); 491 | 492 | it('should pass the current URI fragment', function() { 493 | expect(this.routeData.uriFragment).to.equal('deeply/nested/splat?name=james'); 494 | }); 495 | 496 | it('should pass a Regular expression as the route', function() { 497 | expect(this.routeData.route).to.be.instanceOf(RegExp); 498 | }); 499 | 500 | it('should pass the parsed query params', function() { 501 | expect(this.routeData.query).to.deep.equal({name: 'james'}); 502 | }); 503 | 504 | it('should pass the original query string', function() { 505 | expect(this.routeData.queryString).to.equal('name=james'); 506 | }); 507 | 508 | it('should pass an empty object for the named params', function() { 509 | expect(this.routeData.params).to.deep.equal({splat: 'deeply/nested/splat'}); 510 | }); 511 | }); 512 | 513 | describe('when routing to a matched route without pushState and a splat with query params', function() { 514 | beforeEach(function() { 515 | Backbone.history.location = new this.Location('http://example.com#deeply/nested/splat?name=james'); 516 | this.router.route('*splat', false); 517 | Backbone.history.start({pushState: false}); 518 | }); 519 | 520 | it('should trigger onNavigate', function() { 521 | expect(this.router.onNavigate).to.have.been.calledOnce; 522 | }); 523 | 524 | it('should pass the object that was linked with the route', function() { 525 | expect(this.routeData.linked).to.deep.equal(false); 526 | }); 527 | 528 | it('should pass the Router instance', function() { 529 | expect(this.routeData.router).to.equal(this.router); 530 | }); 531 | 532 | it('should pass the original matched Route', function() { 533 | expect(this.routeData.originalRoute).to.equal('*splat'); 534 | }); 535 | 536 | it('should pass the current URI fragment', function() { 537 | expect(this.routeData.uriFragment).to.equal('deeply/nested/splat?name=james'); 538 | }); 539 | 540 | it('should pass a Regular expression as the route', function() { 541 | expect(this.routeData.route).to.be.instanceOf(RegExp); 542 | }); 543 | 544 | it('should pass the parsed query params', function() { 545 | expect(this.routeData.query).to.deep.equal({name: 'james'}); 546 | }); 547 | 548 | it('should pass the original query string', function() { 549 | expect(this.routeData.queryString).to.equal('name=james'); 550 | }); 551 | 552 | it('should pass an empty object for the named params', function() { 553 | expect(this.routeData.params).to.deep.equal({splat: 'deeply/nested/splat'}); 554 | }); 555 | }); 556 | 557 | describe('when routing to a matched Regex route with pushState', function() { 558 | beforeEach(function() { 559 | Backbone.history.location = new this.Location('http://example.com/example'); 560 | this.routeObj = {}; 561 | this.router.route(/^example$/, this.routeObj); 562 | Backbone.history.start({pushState: true}); 563 | }); 564 | 565 | it('should not include originalRoute in the options', function() { 566 | expect(this.routeData).to.not.include.keys('originalRoute'); 567 | }); 568 | 569 | it('should trigger onNavigate', function() { 570 | expect(this.router.onNavigate).to.have.been.calledOnce; 571 | }); 572 | 573 | it('should pass the object that was linked with the route', function() { 574 | expect(this.routeData.linked).to.deep.equal(this.routeObj); 575 | }); 576 | 577 | it('should pass the Router instance', function() { 578 | expect(this.routeData.router).to.equal(this.router); 579 | }); 580 | 581 | it('should pass the current URI fragment', function() { 582 | expect(this.routeData.uriFragment).to.equal('example'); 583 | }); 584 | 585 | it('should pass a Regular expression as the route', function() { 586 | expect(this.routeData.route).to.be.instanceOf(RegExp); 587 | }); 588 | 589 | it('should pass an empty object for query params', function() { 590 | expect(this.routeData.query).to.deep.equal({}); 591 | }); 592 | 593 | it('should pass undefined for queryString', function() { 594 | expect(this.routeData.queryString).to.equal(undefined); 595 | }); 596 | 597 | it('should pass empty named params', function() { 598 | expect(this.routeData.params).to.deep.equal({}); 599 | }); 600 | }); 601 | 602 | describe('when routing to a matched Regex route without pushState', function() { 603 | beforeEach(function() { 604 | Backbone.history.location = new this.Location('http://example.com#example'); 605 | this.routeObj = {}; 606 | this.router.route(/^example$/, this.routeObj); 607 | Backbone.history.start({pushState: false}); 608 | }); 609 | 610 | it('should not include originalRoute in the options', function() { 611 | expect(this.routeData).to.not.include.keys('originalRoute'); 612 | }); 613 | 614 | it('should trigger onNavigate', function() { 615 | expect(this.router.onNavigate).to.have.been.calledOnce; 616 | }); 617 | 618 | it('should pass the object that was linked with the route', function() { 619 | expect(this.routeData.linked).to.deep.equal(this.routeObj); 620 | }); 621 | 622 | it('should pass the Router instance', function() { 623 | expect(this.routeData.router).to.equal(this.router); 624 | }); 625 | 626 | it('should pass the current URI fragment', function() { 627 | expect(this.routeData.uriFragment).to.equal('example'); 628 | }); 629 | 630 | it('should pass a Regular expression as the route', function() { 631 | expect(this.routeData.route).to.be.instanceOf(RegExp); 632 | }); 633 | 634 | it('should pass an empty object for query params', function() { 635 | expect(this.routeData.query).to.deep.equal({}); 636 | }); 637 | 638 | it('should pass undefined for queryString', function() { 639 | expect(this.routeData.queryString).to.equal(undefined); 640 | }); 641 | 642 | it('should pass empty named params', function() { 643 | expect(this.routeData.params).to.deep.equal({}); 644 | }); 645 | }); 646 | }); 647 | --------------------------------------------------------------------------------