├── .jscsrc ├── .travis.yml ├── test ├── setup │ ├── browserify.js │ ├── setup.js │ └── node.js ├── runner.html ├── .jshintrc └── unit │ └── routing.js ├── .gitignore ├── LICENSE.md ├── .jshintrc ├── package.json ├── dist ├── backbone-routing.min.js ├── backbone-routing.js ├── backbone-routing.js.map └── backbone-routing.min.js.map ├── README.md ├── gulpfile.js └── src └── backbone-routing.js /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "google", 3 | "maximumLineLength": null, 4 | "esnext": true, 5 | "disallowSpacesInsideObjectBrackets": null 6 | } 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | script: "gulp coverage" 5 | after_success: 6 | - npm install -g codeclimate-test-reporter 7 | - codeclimate < coverage/lcov.info 8 | -------------------------------------------------------------------------------- /test/setup/browserify.js: -------------------------------------------------------------------------------- 1 | var config = require('../../package.json').babelBoilerplateOptions; 2 | 3 | global.mocha.setup('bdd'); 4 | global.onload = function() { 5 | global.mocha.checkLeaks(); 6 | global.mocha.globals(config.mochaGlobals); 7 | global.mocha.run(); 8 | require('./setup')(); 9 | }; 10 | -------------------------------------------------------------------------------- /test/setup/setup.js: -------------------------------------------------------------------------------- 1 | module.exports = function() { 2 | global.expect = global.chai.expect; 3 | 4 | beforeEach(function() { 5 | this.sandbox = global.sinon.sandbox.create(); 6 | global.stub = this.sandbox.stub.bind(this.sandbox); 7 | global.spy = this.sandbox.spy.bind(this.sandbox); 8 | }); 9 | 10 | afterEach(function() { 11 | delete global.stub; 12 | delete global.spy; 13 | this.sandbox.restore(); 14 | }); 15 | }; 16 | -------------------------------------------------------------------------------- /test/setup/node.js: -------------------------------------------------------------------------------- 1 | if (!global.document || !global.window) { 2 | var jsdom = require('jsdom').jsdom; 3 | 4 | global.document = jsdom('', null, { 5 | FetchExternalResources : ['script'], 6 | ProcessExternalResources : ['script'], 7 | MutationEvents : '2.0', 8 | QuerySelector : false 9 | }); 10 | 11 | global.window = global.document.parentWindow; 12 | global.navigator = global.window.navigator; 13 | } 14 | 15 | global.chai = require('chai'); 16 | global.sinon = require('sinon'); 17 | global.chai.use(require('sinon-chai')); 18 | 19 | require('babel/register'); 20 | require('./setup')(); 21 | -------------------------------------------------------------------------------- /.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 | coverage 28 | tmp 29 | 30 | # Users Environment Variables 31 | .lock-wscript 32 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | ## ISC License 2 | 3 | Copyright (c) 2015, James Kyle 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 8 | -------------------------------------------------------------------------------- /test/runner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Tests 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise" : true, 3 | "camelcase" : true, 4 | "eqeqeq" : true, 5 | "forin" : false, 6 | "immed" : true, 7 | "indent" : 2, 8 | "latedef" : true, 9 | "newcap" : true, 10 | "noarg" : true, 11 | "nonbsp" : true, 12 | "nonew" : true, 13 | "plusplus" : false, 14 | "undef" : true, 15 | "unused" : true, 16 | "strict" : false, 17 | "maxparams" : 4, 18 | "maxdepth" : 2, 19 | "maxstatements" : 15, 20 | "maxcomplexity" : 10, 21 | "maxlen" : 100, 22 | 23 | "asi" : false, 24 | "boss" : false, 25 | "debug" : false, 26 | "eqnull" : false, 27 | "esnext" : true, 28 | "evil" : false, 29 | "expr" : false, 30 | "funcscope" : false, 31 | "globalstrict" : false, 32 | "iterator" : false, 33 | "lastsemic" : false, 34 | "loopfunc" : false, 35 | "maxerr" : 50, 36 | "notypeof" : false, 37 | "proto" : false, 38 | "scripturl" : false, 39 | "shadow" : false, 40 | "supernew" : false, 41 | "validthis" : false, 42 | "noyield" : false, 43 | 44 | "browser" : true, 45 | "couch" : false, 46 | "devel" : false, 47 | "dojo" : false, 48 | "jquery" : false, 49 | "mootools" : false, 50 | "node" : false, 51 | "nonstandard" : false, 52 | "prototypejs" : false, 53 | "rhino" : false, 54 | "worker" : false, 55 | "wsh" : false, 56 | "yui" : false 57 | } 58 | -------------------------------------------------------------------------------- /test/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise" : true, 3 | "camelcase" : true, 4 | "eqeqeq" : true, 5 | "forin" : false, 6 | "immed" : true, 7 | "indent" : 2, 8 | "latedef" : true, 9 | "newcap" : true, 10 | "noarg" : true, 11 | "nonbsp" : true, 12 | "nonew" : true, 13 | "plusplus" : false, 14 | "undef" : true, 15 | "unused" : true, 16 | "strict" : false, 17 | "maxparams" : 4, 18 | "maxdepth" : 2, 19 | "maxstatements" : 15, 20 | "maxcomplexity" : 10, 21 | "maxlen" : 200, 22 | 23 | "asi" : false, 24 | "boss" : false, 25 | "debug" : false, 26 | "eqnull" : false, 27 | "esnext" : true, 28 | "evil" : false, 29 | "expr" : true, 30 | "funcscope" : false, 31 | "globalstrict" : false, 32 | "iterator" : false, 33 | "lastsemic" : false, 34 | "loopfunc" : false, 35 | "maxerr" : 50, 36 | "notypeof" : false, 37 | "proto" : false, 38 | "scripturl" : false, 39 | "shadow" : false, 40 | "supernew" : false, 41 | "validthis" : false, 42 | "noyield" : false, 43 | 44 | "browser" : true, 45 | "couch" : false, 46 | "devel" : false, 47 | "dojo" : false, 48 | "jquery" : false, 49 | "mootools" : false, 50 | "node" : true, 51 | "nonstandard" : false, 52 | "prototypejs" : false, 53 | "rhino" : false, 54 | "worker" : false, 55 | "wsh" : false, 56 | "yui" : false, 57 | "globals": { 58 | "Backbone": true, 59 | "console": true, 60 | "sinon": true, 61 | "spy": true, 62 | "stub": true, 63 | "describe": true, 64 | "before": true, 65 | "after": true, 66 | "beforeEach": true, 67 | "afterEach": true, 68 | "it": true, 69 | "expect": true 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backbone-routing", 3 | "version": "0.2.0", 4 | "description": "", 5 | "main": "dist/backbone-routing.js", 6 | "scripts": { 7 | "test": "gulp", 8 | "test-browser": "gulp test-browser", 9 | "build": "gulp build", 10 | "coverage": "gulp coverage" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/thejameskyle/backbone-routing.git" 15 | }, 16 | "keywords": [], 17 | "author": "James Kyle ", 18 | "license": "MIT", 19 | "bugs": { 20 | "url": "https://github.com/thejameskyle/backbone-routing/issues" 21 | }, 22 | "homepage": "https://github.com/thejameskyle/backbone-routing", 23 | "devDependencies": { 24 | "babel": "^5.0.0", 25 | "babelify": "^6.0.0", 26 | "browserify": "^8.1.1", 27 | "chai": "^2.0.0", 28 | "del": "^1.1.1", 29 | "esperanto": "^0.6.7", 30 | "glob": "^4.3.5", 31 | "gulp": "^3.8.10", 32 | "gulp-babel": "^5.0.0", 33 | "gulp-file": "^0.2.0", 34 | "gulp-filter": "^2.0.0", 35 | "gulp-istanbul": "^0.6.0", 36 | "gulp-jscs": "^1.4.0", 37 | "gulp-jshint": "^1.9.0", 38 | "gulp-livereload": "^3.4.0", 39 | "gulp-load-plugins": "^0.8.0", 40 | "gulp-mocha": "^2.0.0", 41 | "gulp-notify": "^2.1.0", 42 | "gulp-plumber": "^0.6.6", 43 | "gulp-rename": "^1.2.0", 44 | "gulp-sourcemaps": "^1.3.0", 45 | "gulp-uglifyjs": "^0.6.0", 46 | "isparta": "^2.2.0", 47 | "jquery": "^2.1.4", 48 | "jsdom": "^1.1.0", 49 | "jshint-stylish": "^1.0.0", 50 | "lodash": "^3.8.0", 51 | "mkdirp": "^0.5.0", 52 | "mocha": "^2.1.0", 53 | "run-sequence": "^1.0.2", 54 | "sinon": "^1.12.2", 55 | "sinon-chai": "^2.7.0", 56 | "vinyl-source-stream": "^1.0.0" 57 | }, 58 | "babelBoilerplateOptions": { 59 | "entryFileName": "backbone-routing", 60 | "exportVarName": "Backbone.Routing", 61 | "mochaGlobals": [ 62 | "stub", 63 | "spy", 64 | "expect" 65 | ] 66 | }, 67 | "dependencies": { 68 | "backbone": "^1.1.2", 69 | "backbone-metal": "^0.5.0" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /dist/backbone-routing.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("backbone"),require("backbone-metal")):"function"==typeof define&&define.amd?define(["backbone","backbone-metal"],t):e.Backbone.Routing=t(e.Backbone,e.Metal)}(this,function(e,t){"use strict";var r=t.Error.extend({name:"CancellationError"}),n=t.Class.extend({enter:function(){var e=this,t=void 0===arguments[0]?[]:arguments[0];return this._isEntering=!0,this.trigger.apply(this,["before:enter before:fetch",this].concat(t)),Promise.resolve().then(function(){return e._isCancelled?Promise.reject(new r):e.fetch.apply(e,t)}).then(function(){return e.trigger.apply(e,["fetch before:render",e].concat(t))}).then(function(){return e._isCancelled?Promise.reject(new r):e.render.apply(e,t)}).then(function(){e._isEntering=!1,e.trigger.apply(e,["render enter",e].concat(t))})["catch"](function(t){if(e._isEntering=!1,!(t instanceof r))throw e.trigger("error error:enter",e,t),t;e.trigger("cancel",e)})},exit:function(){var e=this;return this._isEntering&&this.cancel(),this._isExiting=!0,this.trigger("before:exit before:destroy",this),Promise.resolve().then(function(){return e.destroy()}).then(function(){e._isExiting=!1,e.trigger("destroy exit",e),e.stopListening()})["catch"](function(t){throw e._isExiting=!1,e.trigger("error error:exit",e,t),e.stopListening(),t})},cancel:function(){var e=this;if(this._isEntering)return this.trigger("before:cancel",this),this._isCancelled=!0,new Promise(function(t,r){e.once("cancel",t),e.once("enter error",r)})},isEntering:function(){return!!this._isEntering},isExiting:function(){return!!this._isExiting},isCancelled:function(){return!!this._isCancelled},fetch:function(){},render:function(){},destroy:function(){}}),i=t.Class.extend(e.Router.prototype,e.Router).extend({constructor:function(){this.listenTo(e.history,"route",this._onHistoryRoute),this._super.apply(this,arguments)},isActive:function(){return!!this._isActive},execute:function(t,r){var n=this,i=!this._isActive;return i&&this.trigger("before:enter",this),this.trigger("before:route",this),Promise.resolve().then(function(){return n._execute(t,r)}).then(function(){n.trigger("route",n),i&&n.trigger("enter",n)})["catch"](function(t){throw n.trigger("error",n,t),e.history.trigger("error",n,t),t})},_execute:function(e,t){var r=this;return Promise.resolve().then(function(){return i._prevRoute instanceof n?i._prevRoute.exit():void 0}).then(function(){var o=i._prevRoute=e.apply(r,t);return o instanceof n?(o.router=r,o.enter(t)):void 0})},_onHistoryRoute:function(e){this._isActive=e===this}},{_prevRoute:null}),o={Route:n,Router:i,CancellationError:r};return o}); 2 | //# sourceMappingURL=backbone-routing.min.js.map -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Backbone Routing 2 | 3 | Simple router and route classes for Backbone. 4 | 5 | [![Travis build status](http://img.shields.io/travis/thejameskyle/backbone-routing.svg?style=flat)](https://travis-ci.org/thejameskyle/backbone-routing) 6 | [![Code Climate](https://codeclimate.com/github/thejameskyle/backbone-routing/badges/gpa.svg)](https://codeclimate.com/github/thejameskyle/backbone-routing) 7 | [![Test Coverage](https://codeclimate.com/github/thejameskyle/backbone-routing/badges/coverage.svg)](https://codeclimate.com/github/thejameskyle/backbone-routing) 8 | [![Dependency Status](https://david-dm.org/thejameskyle/backbone-routing.svg)](https://david-dm.org/thejameskyle/backbone-routing) 9 | [![devDependency Status](https://david-dm.org/thejameskyle/backbone-routing/dev-status.svg)](https://david-dm.org/thejameskyle/backbone-routing#info=devDependencies) 10 | 11 | ## Usage 12 | 13 | > _**Note:** Backbone-routing requires a global `Promise` object to 14 | > exist, please include a `Promise` polyfill if necessary._ 15 | 16 | ```js 17 | import {Route, Router} from 'backbone-routing'; 18 | 19 | const IndexRoute = Route.extend({ 20 | initialize(options) { 21 | this.collection = options.collection; 22 | }, 23 | 24 | fetch() { 25 | return this.collection.fetch(); 26 | }, 27 | 28 | render() { 29 | this.view = new View(); 30 | this.view.render(); 31 | }, 32 | 33 | destroy() { 34 | this.view.remove(); 35 | } 36 | }); 37 | 38 | const ShowRoute = Route.extend({ 39 | initialize(options) { 40 | this.collection = options.collection; 41 | }, 42 | 43 | fetch(id) { 44 | this.model = this.collection.get(id); 45 | 46 | if (!this.model) { 47 | this.model = new Model({id}); 48 | return this.model.fetch(); 49 | } 50 | }, 51 | 52 | render() { 53 | this.view = new View({ 54 | model: this.model 55 | }); 56 | }, 57 | 58 | destroy() { 59 | this.view.remove(); 60 | } 61 | }); 62 | 63 | const MyRouter = Router.extend({ 64 | initialize() { 65 | this.collection = new Collection(); 66 | }, 67 | 68 | routes: { 69 | '' : 'index', 70 | ':id' : 'show' 71 | }, 72 | 73 | index() { 74 | return new IndexRoute({ 75 | collection: this.collection 76 | }); 77 | }, 78 | 79 | show() { 80 | return new ShowRoute({ 81 | collection: this.collection 82 | }); 83 | } 84 | }); 85 | ``` 86 | 87 | ## Contibuting 88 | 89 | ### Getting Started 90 | 91 | [Fork](https://help.github.com/articles/fork-a-repo/) and 92 | [clone](http://git-scm.com/docs/git-clone) this repo. 93 | 94 | ``` 95 | git clone git@github.com:thejameskyle/backbone-routing.git && cd backbone-routing 96 | ``` 97 | 98 | Make sure [Node.js](http://nodejs.org/) and [npm](https://www.npmjs.org/) are 99 | [installed](http://nodejs.org/download/). 100 | 101 | ``` 102 | npm install 103 | ``` 104 | 105 | ### Running Tests 106 | 107 | ``` 108 | npm test 109 | ``` 110 | 111 | === 112 | 113 | © 2015 James Kyle. Distributed under ISC license. 114 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp'); 2 | const $ = require('gulp-load-plugins')(); 3 | const fs = require('fs'); 4 | const del = require('del'); 5 | const glob = require('glob'); 6 | const path = require('path'); 7 | const mkdirp = require('mkdirp'); 8 | const babelify = require('babelify'); 9 | const isparta = require('isparta'); 10 | const esperanto = require('esperanto'); 11 | const browserify = require('browserify'); 12 | const runSequence = require('run-sequence'); 13 | const source = require('vinyl-source-stream'); 14 | 15 | const manifest = require('./package.json'); 16 | const config = manifest.babelBoilerplateOptions; 17 | const mainFile = manifest.main; 18 | const destinationFolder = path.dirname(mainFile); 19 | const exportFileName = path.basename(mainFile, path.extname(mainFile)); 20 | 21 | // Remove the built files 22 | gulp.task('clean', function(cb) { 23 | del([destinationFolder], cb); 24 | }); 25 | 26 | // Remove our temporary files 27 | gulp.task('clean-tmp', function(cb) { 28 | del(['tmp'], cb); 29 | }); 30 | 31 | // Send a notification when JSHint fails, 32 | // so that you know your changes didn't build 33 | function jshintNotify(file) { 34 | if (!file.jshint) { return; } 35 | return file.jshint.success ? false : 'JSHint failed'; 36 | } 37 | 38 | function jscsNotify(file) { 39 | if (!file.jscs) { return; } 40 | return file.jscs.success ? false : 'JSRC failed'; 41 | } 42 | 43 | function createLintTask(taskName, files) { 44 | gulp.task(taskName, function() { 45 | return gulp.src(files) 46 | .pipe($.plumber()) 47 | .pipe($.jshint()) 48 | .pipe($.jshint.reporter('jshint-stylish')) 49 | .pipe($.notify(jshintNotify)) 50 | .pipe($.jscs()) 51 | .pipe($.notify(jscsNotify)) 52 | .pipe($.jshint.reporter('fail')); 53 | }); 54 | } 55 | 56 | // Lint our source code 57 | createLintTask('lint-src', ['src/**/*.js']); 58 | 59 | // Lint our test code 60 | createLintTask('lint-test', ['test/**/*.js']); 61 | 62 | // Build two versions of the library 63 | gulp.task('build', ['lint-src', 'clean'], function(done) { 64 | esperanto.bundle({ 65 | base: 'src', 66 | entry: config.entryFileName, 67 | }).then(function(bundle) { 68 | var res = bundle.toUmd({ 69 | sourceMap: true, 70 | sourceMapSource: config.entryFileName + '.js', 71 | sourceMapFile: exportFileName + '.js', 72 | name: config.exportVarName 73 | }); 74 | 75 | // Write the generated sourcemap 76 | mkdirp.sync(destinationFolder); 77 | fs.writeFileSync(path.join(destinationFolder, exportFileName + '.js'), res.map.toString()); 78 | 79 | $.file(exportFileName + '.js', res.code, { src: true }) 80 | .pipe($.plumber()) 81 | .pipe($.sourcemaps.init({ loadMaps: true })) 82 | .pipe($.babel({ blacklist: ['useStrict'], loose: 'all' })) 83 | .pipe($.sourcemaps.write('./', {addComment: false})) 84 | .pipe(gulp.dest(destinationFolder)) 85 | .pipe($.filter(['*', '!**/*.js.map'])) 86 | .pipe($.rename(exportFileName + '.min.js')) 87 | .pipe($.uglifyjs({ 88 | outSourceMap: true, 89 | inSourceMap: destinationFolder + '/' + exportFileName + '.js.map', 90 | })) 91 | .pipe(gulp.dest(destinationFolder)) 92 | .on('end', done); 93 | }) 94 | .catch(done); 95 | }); 96 | 97 | // Bundle our app for our unit tests 98 | gulp.task('browserify', function() { 99 | var testFiles = glob.sync('./test/unit/**/*'); 100 | var allFiles = ['./test/setup/browserify.js'].concat(testFiles); 101 | var bundler = browserify(allFiles); 102 | bundler.transform(babelify.configure({ 103 | sourceMapRelative: __dirname + '/src', 104 | blacklist: ['useStrict'], 105 | loose: 'all' 106 | })); 107 | var bundleStream = bundler.bundle(); 108 | return bundleStream 109 | .on('error', function(err){ 110 | console.log(err.message); 111 | this.emit('end'); 112 | }) 113 | .pipe($.plumber()) 114 | .pipe(source('./tmp/__spec-build.js')) 115 | .pipe(gulp.dest('')) 116 | .pipe($.livereload()); 117 | }); 118 | 119 | gulp.task('coverage', ['lint-src', 'lint-test'], function(done) { 120 | require('babel/register')({ modules: 'common', loose: 'all' }); 121 | gulp.src(['src/**/*.js']) 122 | .pipe($.istanbul({ instrumenter: isparta.Instrumenter })) 123 | .pipe($.istanbul.hookRequire()) 124 | .on('finish', function() { 125 | return test() 126 | .pipe($.istanbul.writeReports()) 127 | .on('end', done); 128 | }); 129 | }); 130 | 131 | function test() { 132 | return gulp.src(['test/setup/node.js', 'test/unit/**/*.js'], {read: false}) 133 | .pipe($.mocha({reporter: 'dot', globals: config.mochaGlobals})); 134 | }; 135 | 136 | // Lint and run our tests 137 | gulp.task('test', ['lint-src', 'lint-test'], function() { 138 | require('babel/register')({ modules: 'common', loose: 'all' }); 139 | return test(); 140 | }); 141 | 142 | // Ensure that linting occurs before browserify runs. This prevents 143 | // the build from breaking due to poorly formatted code. 144 | gulp.task('build-in-sequence', function(callback) { 145 | runSequence(['lint-src', 'lint-test'], 'browserify', callback); 146 | }); 147 | 148 | const watchFiles = ['src/**/*', 'test/**/*', 'package.json', '**/.jshintrc', '.jscsrc']; 149 | 150 | // Run the headless unit tests as you make changes. 151 | gulp.task('watch', function() { 152 | gulp.watch(watchFiles, ['test']); 153 | }); 154 | 155 | // Set up a livereload environment for our spec runner 156 | gulp.task('test-browser', ['build-in-sequence'], function() { 157 | $.livereload.listen({port: 35729, host: 'localhost', start: true}); 158 | return gulp.watch(watchFiles, ['build-in-sequence']); 159 | }); 160 | 161 | // An alias of test 162 | gulp.task('default', ['test']); 163 | -------------------------------------------------------------------------------- /src/backbone-routing.js: -------------------------------------------------------------------------------- 1 | import Backbone from 'backbone'; 2 | import Metal from 'backbone-metal'; 3 | 4 | /** 5 | * @public 6 | * @class CancellationError 7 | */ 8 | const CancellationError = Metal.Error.extend({ 9 | name: 'CancellationError' 10 | }); 11 | 12 | /** 13 | * @public 14 | * @class Route 15 | */ 16 | const Route = Metal.Class.extend({ 17 | 18 | /** 19 | * @public 20 | * @method enter 21 | * @returns {Promise} 22 | * @param {...*} [args=[]] 23 | */ 24 | enter(args = []) { 25 | this._isEntering = true; 26 | this.trigger('before:enter before:fetch', this, ...args); 27 | 28 | return Promise.resolve() 29 | .then(() => { 30 | if (this._isCancelled) { 31 | return Promise.reject(new CancellationError()); 32 | } 33 | return this.fetch(...args); 34 | }) 35 | .then(() => this.trigger('fetch before:render', this, ...args)) 36 | .then(() => { 37 | if (this._isCancelled) { 38 | return Promise.reject(new CancellationError()); 39 | } 40 | return this.render(...args); 41 | }) 42 | .then(() => { 43 | this._isEntering = false; 44 | this.trigger('render enter', this, ...args); 45 | }) 46 | .catch(err => { 47 | this._isEntering = false; 48 | if (err instanceof CancellationError) { 49 | this.trigger('cancel', this); 50 | } else { 51 | this.trigger('error error:enter', this, err); 52 | throw err; 53 | } 54 | }); 55 | }, 56 | 57 | /** 58 | * @public 59 | * @method exit 60 | * @returns {Promise} 61 | */ 62 | exit() { 63 | if (this._isEntering) { 64 | this.cancel(); 65 | } 66 | this._isExiting = true; 67 | this.trigger('before:exit before:destroy', this); 68 | 69 | return Promise.resolve() 70 | .then(() => this.destroy()) 71 | .then(() => { 72 | this._isExiting = false; 73 | this.trigger('destroy exit', this); 74 | this.stopListening(); 75 | }) 76 | .catch(err => { 77 | this._isExiting = false; 78 | this.trigger('error error:exit', this, err); 79 | this.stopListening(); 80 | throw err; 81 | }); 82 | }, 83 | 84 | /** 85 | * @public 86 | * @method cancel 87 | * @returns {Promise} 88 | */ 89 | cancel() { 90 | if (!this._isEntering) { 91 | return; 92 | } 93 | this.trigger('before:cancel', this); 94 | this._isCancelled = true; 95 | return new Promise((resolve, reject) => { 96 | this.once('cancel', resolve); 97 | this.once('enter error', reject); 98 | }); 99 | }, 100 | 101 | /** 102 | * @public 103 | * @method isEntering 104 | * @returns {Boolean} 105 | */ 106 | isEntering() { 107 | return !!this._isEntering; 108 | }, 109 | 110 | /** 111 | * @public 112 | * @method isExiting 113 | * @returns {Boolean} 114 | */ 115 | isExiting() { 116 | return !!this._isExiting; 117 | }, 118 | 119 | /** 120 | * @public 121 | * @method isCancelled 122 | * @returns {Boolean} 123 | */ 124 | isCancelled() { 125 | return !!this._isCancelled; 126 | }, 127 | 128 | /** 129 | * @public 130 | * @abstract 131 | * @method fetch 132 | * @param {...*} [args=[]] 133 | */ 134 | fetch() {}, 135 | 136 | /** 137 | * @public 138 | * @abstract 139 | * @method render 140 | * @param {...*} [args=[]] 141 | */ 142 | render() {}, 143 | 144 | /** 145 | * @public 146 | * @abstract 147 | * @method destroy 148 | */ 149 | destroy() {} 150 | }); 151 | 152 | /** 153 | * @public 154 | * @class Router 155 | */ 156 | const Router = Metal.Class.extend(Backbone.Router.prototype, Backbone.Router).extend({ 157 | constructor() { 158 | this.listenTo(Backbone.history, 'route', this._onHistoryRoute); 159 | this._super(...arguments); 160 | }, 161 | 162 | /** 163 | * @public 164 | * @method isActive 165 | * @returns {Boolean} 166 | */ 167 | isActive() { 168 | return !!this._isActive; 169 | }, 170 | 171 | /** 172 | * @public 173 | * @method execute 174 | * @param {Function} callback 175 | * @param {Array} [args] 176 | */ 177 | execute(callback, args) { 178 | let wasInactive = !this._isActive; 179 | if (wasInactive) { 180 | this.trigger('before:enter', this); 181 | } 182 | 183 | this.trigger('before:route', this); 184 | 185 | return Promise.resolve().then(() => { 186 | return this._execute(callback, args); 187 | }).then(() => { 188 | this.trigger('route', this); 189 | 190 | if (wasInactive) { 191 | this.trigger('enter', this); 192 | } 193 | }).catch(err => { 194 | this.trigger('error', this, err); 195 | Backbone.history.trigger('error', this, err); 196 | throw err; 197 | }); 198 | }, 199 | 200 | /** 201 | * @public 202 | * @method execute 203 | * @param {Function} callback 204 | * @param {Array} [args] 205 | * @returns {Promise} 206 | */ 207 | _execute(callback, args) { 208 | return Promise.resolve().then(() => { 209 | if (Router._prevRoute instanceof Route) { 210 | return Router._prevRoute.exit(); 211 | } 212 | }).then(() => { 213 | let route = Router._prevRoute = callback.apply(this, args); 214 | if (route instanceof Route) { 215 | route.router = this; 216 | return route.enter(args); 217 | } 218 | }); 219 | }, 220 | 221 | /** 222 | * @private 223 | * @method _onHistoryRoute 224 | * @param {Router} router 225 | */ 226 | _onHistoryRoute(router) { 227 | this._isActive = (router === this); 228 | } 229 | }, { 230 | 231 | /** 232 | * @private 233 | * @member _prevRoute 234 | */ 235 | _prevRoute: null 236 | }); 237 | 238 | export default {Route, Router, CancellationError}; 239 | -------------------------------------------------------------------------------- /dist/backbone-routing.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('backbone'), require('backbone-metal')) : typeof define === 'function' && define.amd ? define(['backbone', 'backbone-metal'], factory) : global.Backbone.Routing = factory(global.Backbone, global.Metal); 3 | })(this, function (Backbone, Metal) { 4 | 'use strict'; 5 | 6 | var CancellationError = Metal.Error.extend({ 7 | name: 'CancellationError' 8 | }); 9 | 10 | /** 11 | * @public 12 | * @class Route 13 | */ 14 | var Route = Metal.Class.extend({ 15 | 16 | /** 17 | * @public 18 | * @method enter 19 | * @returns {Promise} 20 | * @param {...*} [args=[]] 21 | */ 22 | enter: function enter() { 23 | var _this = this; 24 | 25 | var args = arguments[0] === undefined ? [] : arguments[0]; 26 | 27 | this._isEntering = true; 28 | this.trigger.apply(this, ['before:enter before:fetch', this].concat(args)); 29 | 30 | return Promise.resolve().then(function () { 31 | if (_this._isCancelled) { 32 | return Promise.reject(new CancellationError()); 33 | } 34 | return _this.fetch.apply(_this, args); 35 | }).then(function () { 36 | return _this.trigger.apply(_this, ['fetch before:render', _this].concat(args)); 37 | }).then(function () { 38 | if (_this._isCancelled) { 39 | return Promise.reject(new CancellationError()); 40 | } 41 | return _this.render.apply(_this, args); 42 | }).then(function () { 43 | _this._isEntering = false; 44 | _this.trigger.apply(_this, ['render enter', _this].concat(args)); 45 | })['catch'](function (err) { 46 | _this._isEntering = false; 47 | if (err instanceof CancellationError) { 48 | _this.trigger('cancel', _this); 49 | } else { 50 | _this.trigger('error error:enter', _this, err); 51 | throw err; 52 | } 53 | }); 54 | }, 55 | 56 | /** 57 | * @public 58 | * @method exit 59 | * @returns {Promise} 60 | */ 61 | exit: function exit() { 62 | var _this2 = this; 63 | 64 | if (this._isEntering) { 65 | this.cancel(); 66 | } 67 | this._isExiting = true; 68 | this.trigger('before:exit before:destroy', this); 69 | 70 | return Promise.resolve().then(function () { 71 | return _this2.destroy(); 72 | }).then(function () { 73 | _this2._isExiting = false; 74 | _this2.trigger('destroy exit', _this2); 75 | _this2.stopListening(); 76 | })['catch'](function (err) { 77 | _this2._isExiting = false; 78 | _this2.trigger('error error:exit', _this2, err); 79 | _this2.stopListening(); 80 | throw err; 81 | }); 82 | }, 83 | 84 | /** 85 | * @public 86 | * @method cancel 87 | * @returns {Promise} 88 | */ 89 | cancel: function cancel() { 90 | var _this3 = this; 91 | 92 | if (!this._isEntering) { 93 | return; 94 | } 95 | this.trigger('before:cancel', this); 96 | this._isCancelled = true; 97 | return new Promise(function (resolve, reject) { 98 | _this3.once('cancel', resolve); 99 | _this3.once('enter error', reject); 100 | }); 101 | }, 102 | 103 | /** 104 | * @public 105 | * @method isEntering 106 | * @returns {Boolean} 107 | */ 108 | isEntering: function isEntering() { 109 | return !!this._isEntering; 110 | }, 111 | 112 | /** 113 | * @public 114 | * @method isExiting 115 | * @returns {Boolean} 116 | */ 117 | isExiting: function isExiting() { 118 | return !!this._isExiting; 119 | }, 120 | 121 | /** 122 | * @public 123 | * @method isCancelled 124 | * @returns {Boolean} 125 | */ 126 | isCancelled: function isCancelled() { 127 | return !!this._isCancelled; 128 | }, 129 | 130 | /** 131 | * @public 132 | * @abstract 133 | * @method fetch 134 | * @param {...*} [args=[]] 135 | */ 136 | fetch: function fetch() {}, 137 | 138 | /** 139 | * @public 140 | * @abstract 141 | * @method render 142 | * @param {...*} [args=[]] 143 | */ 144 | render: function render() {}, 145 | 146 | /** 147 | * @public 148 | * @abstract 149 | * @method destroy 150 | */ 151 | destroy: function destroy() {} 152 | }); 153 | 154 | /** 155 | * @public 156 | * @class Router 157 | */ 158 | var Router = Metal.Class.extend(Backbone.Router.prototype, Backbone.Router).extend({ 159 | constructor: function constructor() { 160 | this.listenTo(Backbone.history, 'route', this._onHistoryRoute); 161 | this._super.apply(this, arguments); 162 | }, 163 | 164 | /** 165 | * @public 166 | * @method isActive 167 | * @returns {Boolean} 168 | */ 169 | isActive: function isActive() { 170 | return !!this._isActive; 171 | }, 172 | 173 | /** 174 | * @public 175 | * @method execute 176 | * @param {Function} callback 177 | * @param {Array} [args] 178 | */ 179 | execute: function execute(callback, args) { 180 | var _this4 = this; 181 | 182 | var wasInactive = !this._isActive; 183 | if (wasInactive) { 184 | this.trigger('before:enter', this); 185 | } 186 | 187 | this.trigger('before:route', this); 188 | 189 | return Promise.resolve().then(function () { 190 | return _this4._execute(callback, args); 191 | }).then(function () { 192 | _this4.trigger('route', _this4); 193 | 194 | if (wasInactive) { 195 | _this4.trigger('enter', _this4); 196 | } 197 | })['catch'](function (err) { 198 | _this4.trigger('error', _this4, err); 199 | Backbone.history.trigger('error', _this4, err); 200 | throw err; 201 | }); 202 | }, 203 | 204 | /** 205 | * @public 206 | * @method execute 207 | * @param {Function} callback 208 | * @param {Array} [args] 209 | * @returns {Promise} 210 | */ 211 | _execute: function _execute(callback, args) { 212 | var _this5 = this; 213 | 214 | return Promise.resolve().then(function () { 215 | if (Router._prevRoute instanceof Route) { 216 | return Router._prevRoute.exit(); 217 | } 218 | }).then(function () { 219 | var route = Router._prevRoute = callback.apply(_this5, args); 220 | if (route instanceof Route) { 221 | route.router = _this5; 222 | return route.enter(args); 223 | } 224 | }); 225 | }, 226 | 227 | /** 228 | * @private 229 | * @method _onHistoryRoute 230 | * @param {Router} router 231 | */ 232 | _onHistoryRoute: function _onHistoryRoute(router) { 233 | this._isActive = router === this; 234 | } 235 | }, { 236 | 237 | /** 238 | * @private 239 | * @member _prevRoute 240 | */ 241 | _prevRoute: null 242 | }); 243 | 244 | var backbone_routing = { Route: Route, Router: Router, CancellationError: CancellationError }; 245 | 246 | return backbone_routing; 247 | }); 248 | //# sourceMappingURL=./backbone-routing.js.map -------------------------------------------------------------------------------- /dist/backbone-routing.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["backbone-routing.js"],"names":[],"mappings":"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","file":"backbone-routing.js","sourcesContent":["(function (global, factory) {\n typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('backbone'), require('backbone-metal')) :\n typeof define === 'function' && define.amd ? define(['backbone', 'backbone-metal'], factory) :\n global.Backbone.Routing = factory(global.Backbone, global.Metal)\n}(this, function (Backbone, Metal) { 'use strict';\n\n const CancellationError = Metal.Error.extend({\n name: 'CancellationError'\n });\n\n /**\n * @public\n * @class Route\n */\n const Route = Metal.Class.extend({\n\n /**\n * @public\n * @method enter\n * @returns {Promise}\n * @param {...*} [args=[]]\n */\n enter(args = []) {\n this._isEntering = true;\n this.trigger('before:enter before:fetch', this, ...args);\n\n return Promise.resolve()\n .then(() => {\n if (this._isCancelled) {\n return Promise.reject(new CancellationError());\n }\n return this.fetch(...args);\n })\n .then(() => this.trigger('fetch before:render', this, ...args))\n .then(() => {\n if (this._isCancelled) {\n return Promise.reject(new CancellationError());\n }\n return this.render(...args);\n })\n .then(() => {\n this._isEntering = false;\n this.trigger('render enter', this, ...args);\n })\n .catch(err => {\n this._isEntering = false;\n if (err instanceof CancellationError) {\n this.trigger('cancel', this);\n } else {\n this.trigger('error error:enter', this, err);\n throw err;\n }\n });\n },\n\n /**\n * @public\n * @method exit\n * @returns {Promise}\n */\n exit() {\n if (this._isEntering) {\n this.cancel();\n }\n this._isExiting = true;\n this.trigger('before:exit before:destroy', this);\n\n return Promise.resolve()\n .then(() => this.destroy())\n .then(() => {\n this._isExiting = false;\n this.trigger('destroy exit', this);\n this.stopListening();\n })\n .catch(err => {\n this._isExiting = false;\n this.trigger('error error:exit', this, err);\n this.stopListening();\n throw err;\n });\n },\n\n /**\n * @public\n * @method cancel\n * @returns {Promise}\n */\n cancel() {\n if (!this._isEntering) {\n return;\n }\n this.trigger('before:cancel', this);\n this._isCancelled = true;\n return new Promise((resolve, reject) => {\n this.once('cancel', resolve);\n this.once('enter error', reject);\n });\n },\n\n /**\n * @public\n * @method isEntering\n * @returns {Boolean}\n */\n isEntering() {\n return !!this._isEntering;\n },\n\n /**\n * @public\n * @method isExiting\n * @returns {Boolean}\n */\n isExiting() {\n return !!this._isExiting;\n },\n\n /**\n * @public\n * @method isCancelled\n * @returns {Boolean}\n */\n isCancelled() {\n return !!this._isCancelled;\n },\n\n /**\n * @public\n * @abstract\n * @method fetch\n * @param {...*} [args=[]]\n */\n fetch() {},\n\n /**\n * @public\n * @abstract\n * @method render\n * @param {...*} [args=[]]\n */\n render() {},\n\n /**\n * @public\n * @abstract\n * @method destroy\n */\n destroy() {}\n });\n\n /**\n * @public\n * @class Router\n */\n const Router = Metal.Class.extend(Backbone.Router.prototype, Backbone.Router).extend({\n constructor() {\n this.listenTo(Backbone.history, 'route', this._onHistoryRoute);\n this._super(...arguments);\n },\n\n /**\n * @public\n * @method isActive\n * @returns {Boolean}\n */\n isActive() {\n return !!this._isActive;\n },\n\n /**\n * @public\n * @method execute\n * @param {Function} callback\n * @param {Array} [args]\n */\n execute(callback, args) {\n let wasInactive = !this._isActive;\n if (wasInactive) {\n this.trigger('before:enter', this);\n }\n\n this.trigger('before:route', this);\n\n return Promise.resolve().then(() => {\n return this._execute(callback, args);\n }).then(() => {\n this.trigger('route', this);\n\n if (wasInactive) {\n this.trigger('enter', this);\n }\n }).catch(err => {\n this.trigger('error', this, err);\n Backbone.history.trigger('error', this, err);\n throw err;\n });\n },\n\n /**\n * @public\n * @method execute\n * @param {Function} callback\n * @param {Array} [args]\n * @returns {Promise}\n */\n _execute(callback, args) {\n return Promise.resolve().then(() => {\n if (Router._prevRoute instanceof Route) {\n return Router._prevRoute.exit();\n }\n }).then(() => {\n let route = Router._prevRoute = callback.apply(this, args);\n if (route instanceof Route) {\n route.router = this;\n return route.enter(args);\n }\n });\n },\n\n /**\n * @private\n * @method _onHistoryRoute\n * @param {Router} router\n */\n _onHistoryRoute(router) {\n this._isActive = (router === this);\n }\n }, {\n\n /**\n * @private\n * @member _prevRoute\n */\n _prevRoute: null\n });\n\n var backbone_routing = {Route, Router, CancellationError};\n\n return backbone_routing;\n\n}));\n"],"sourceRoot":"/source/"} -------------------------------------------------------------------------------- /test/unit/routing.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import Backbone from 'backbone'; 3 | import $ from 'jquery'; 4 | import {Route, Router} from '../../src/backbone-routing'; 5 | 6 | Backbone.$ = $; 7 | 8 | function stubHooks(target, hooks) { 9 | spy(target, 'trigger'); 10 | 11 | _.each(hooks, (hook) => { 12 | if (hook.method) { 13 | hook.stub = stub(target, hook.method); 14 | } else if (hook.event) { 15 | hook.stub = stub(); 16 | target.on(hook.event, hook.stub); 17 | } 18 | }); 19 | } 20 | 21 | function checkHooks(target, hooks, calledHooks) { 22 | let prev; 23 | _.each(hooks, (hook) => { 24 | let type = hook.event ? 'event' : 'method'; 25 | let name = hook[type]; 26 | 27 | let match = _.findWhere(calledHooks, { [type]: name }); 28 | 29 | if (!match) { 30 | expect(hook.stub, name).not.to.have.been.called; 31 | } else { 32 | expect(hook.stub, name).to.have.been.calledOnce.and.calledWith(...match.args); 33 | 34 | if (prev) { 35 | expect(prev.stub).to.have.been.calledBefore(hook.stub); 36 | } 37 | 38 | prev = hook; 39 | } 40 | }); 41 | } 42 | 43 | function resetHooks(hooks) { 44 | _.each(hooks, (hook) => { 45 | hook.stub.reset(); 46 | }); 47 | } 48 | 49 | let routeHooks = [ 50 | { event : 'before:enter' }, 51 | { event : 'before:enter' }, 52 | { method : 'fetch' }, 53 | { event : 'fetch' }, 54 | { event : 'before:render' }, 55 | { method : 'render' }, 56 | { event : 'render' }, 57 | { event : 'enter' }, 58 | 59 | { event : 'before:exit' }, 60 | { event : 'before:destroy' }, 61 | { method : 'destroy' }, 62 | { event : 'destroy' }, 63 | { event : 'exit' }, 64 | { event : 'error' }, 65 | { event : 'error:enter' }, 66 | { event : 'error:exit' }, 67 | ]; 68 | 69 | describe('Route', function() { 70 | beforeEach(function() { 71 | this.router = new Router(); 72 | this.route = new Route(); 73 | }); 74 | 75 | describe('#enter', function() { 76 | beforeEach(function() { 77 | stubHooks(this.route, routeHooks); 78 | }); 79 | 80 | afterEach(function() { 81 | resetHooks(routeHooks); 82 | }); 83 | 84 | it('should call all the methods in the appropriate order', function() { 85 | let args = [1, 2, 3]; 86 | 87 | return this.route.enter(args).then(() => { 88 | checkHooks(this.route, routeHooks, [ 89 | { event : 'before:enter', args: [this.route, ...args] }, 90 | { event : 'before:fetch', args: [this.route, ...args] }, 91 | { method : 'fetch', args }, 92 | { event : 'fetch', args: [this.route, ...args] }, 93 | { event : 'before:render', args: [this.route, ...args] }, 94 | { method : 'render', args }, 95 | { event : 'render', args: [this.route, ...args] }, 96 | { event : 'enter', args: [this.route, ...args] }, 97 | ]); 98 | }); 99 | }); 100 | 101 | describe('when fetch errors', function() { 102 | it('should call all the methods in the appropriate order', function() { 103 | let args = [1, 2, 3]; 104 | let err = new Error('FooError'); 105 | 106 | this.route.fetch.throws(err); 107 | 108 | return this.route.enter(args).catch(_err => { 109 | expect(_err).to.equal(err); 110 | }).then(() => { 111 | checkHooks(this.route, routeHooks, [ 112 | { event : 'before:enter', args: [this.route, ...args] }, 113 | { event : 'before:fetch', args: [this.route, ...args] }, 114 | { method : 'fetch', args }, 115 | { event : 'error', args: [this.route, err] }, 116 | { event : 'error:enter', args: [this.route, err] }, 117 | ]); 118 | }); 119 | }); 120 | }); 121 | 122 | describe('when render errors', function() { 123 | it('should call all the methods in the appropriate order', function() { 124 | let args = [1, 2, 3]; 125 | let err = new Error('FooError'); 126 | 127 | this.route.render.throws(err); 128 | 129 | return this.route.enter(args).catch(_err => { 130 | expect(_err).to.equal(err); 131 | }).then(() => { 132 | checkHooks(this.route, routeHooks, [ 133 | { event : 'before:enter', args: [this.route, ...args] }, 134 | { event : 'before:fetch', args: [this.route, ...args] }, 135 | { method : 'fetch', args }, 136 | { event : 'fetch', args: [this.route, ...args] }, 137 | { event : 'before:render', args: [this.route, ...args] }, 138 | { method : 'render', args }, 139 | { event : 'error', args: [this.route, err] }, 140 | { event : 'error:enter', args: [this.route, err] }, 141 | ]); 142 | }); 143 | }); 144 | }); 145 | }); 146 | 147 | describe('#exit', function() { 148 | beforeEach(function() { 149 | stubHooks(this.route, routeHooks); 150 | }); 151 | 152 | afterEach(function() { 153 | resetHooks(routeHooks); 154 | }); 155 | 156 | it('should call all the methods in the appropriate order', function() { 157 | return this.route.exit().then(() => { 158 | checkHooks(this.route, routeHooks, [ 159 | { event : 'before:exit', args: [this.route] }, 160 | { event : 'before:destroy', args: [this.route] }, 161 | { method : 'destroy', args: [] }, 162 | { event : 'destroy', args: [this.route] }, 163 | { event : 'exit', args: [this.route] }, 164 | ]); 165 | }); 166 | }); 167 | 168 | describe('when destroy errors', function() { 169 | it('should call all the methods in the appropriate order', function() { 170 | let err = new Error('FooError'); 171 | 172 | this.route.destroy.throws(err); 173 | 174 | return this.route.exit().catch(_err => { 175 | expect(_err).to.equal(err); 176 | }).then(() => { 177 | checkHooks(this.route, routeHooks, [ 178 | { event : 'before:exit', args: [this.route] }, 179 | { event : 'before:destroy', args: [this.route] }, 180 | { method : 'destroy', args: [] }, 181 | { event : 'error', args: [this.route, err] }, 182 | { event : 'error:exit', args: [this.route, err] } 183 | ]); 184 | }); 185 | }); 186 | }); 187 | }); 188 | 189 | describe('#cancel', function() { 190 | beforeEach(function() { 191 | stubHooks(this.route, routeHooks); 192 | }); 193 | 194 | afterEach(function() { 195 | resetHooks(routeHooks); 196 | }); 197 | }); 198 | 199 | describe('#isEntering', function() { 200 | it('should return false before entering', function() { 201 | expect(this.route.isEntering()).to.be.false; 202 | }); 203 | 204 | it('should return true while entering', function() { 205 | let entering = this.route.enter(); 206 | expect(this.route.isEntering()).to.be.true; 207 | return entering; 208 | }); 209 | 210 | it('should return false after entering', function() { 211 | return this.route.enter().then(() => { 212 | expect(this.route.isEntering()).to.be.false; 213 | }); 214 | }); 215 | }); 216 | 217 | describe('#isExiting', function() { 218 | beforeEach(function() { 219 | return this.route.enter(); 220 | }); 221 | 222 | it('should return false before exiting', function() { 223 | expect(this.route.isExiting()).to.be.false; 224 | }); 225 | 226 | it('should return true while exiting', function() { 227 | let exiting = this.route.exit(); 228 | expect(this.route.isExiting()).to.be.true; 229 | return exiting; 230 | }); 231 | 232 | it('should return false after exiting', function() { 233 | return this.route.exit().then(() => { 234 | expect(this.route.isExiting()).to.be.false; 235 | }); 236 | }); 237 | }); 238 | 239 | describe('#isCancelled', function() { 240 | it('should return false before cancelling', function() { 241 | let entering = this.route.enter(); 242 | expect(this.route.isCancelled()).to.be.false; 243 | return entering; 244 | }); 245 | 246 | it('should return true after cancelling', function() { 247 | let entering = this.route.enter(); 248 | let cancelling = this.route.cancel(); 249 | expect(this.route.isCancelled()).to.be.true; 250 | return Promise.all([entering, cancelling]); 251 | }); 252 | }); 253 | }); 254 | 255 | let routerHooks = [ 256 | { event : 'before:enter' }, 257 | { event : 'before:route' }, 258 | { method : 'callback' }, 259 | { event : 'route' }, 260 | { event : 'enter' }, 261 | { event : 'error' }, 262 | ]; 263 | 264 | describe('Router', function() { 265 | beforeEach(function() { 266 | this.router = new Router(); 267 | this.route = new Route(); 268 | this.router.callback = function() {}; 269 | }); 270 | 271 | describe('#execute', function() { 272 | it('should call the callback', function() { 273 | let args = [1, 2, 3]; 274 | 275 | stub(this.router, 'callback'); 276 | 277 | return this.router.execute(this.router.callback, args).then(() => { 278 | expect(this.router.callback).to.have.been.calledWith(...args); 279 | }); 280 | }); 281 | 282 | it('should call all the methods in the appropriate order', function() { 283 | let args = [1, 2, 3]; 284 | 285 | stubHooks(this.router, routerHooks); 286 | 287 | return this.router.execute(this.router.callback, args).then(() => { 288 | checkHooks(this.router, routerHooks, [ 289 | { event : 'before:enter', args: [] }, 290 | { event : 'before:route', args: [] }, 291 | { method : 'callback', args }, 292 | { event : 'route', args: [] }, 293 | { event : 'enter', args: [] }, 294 | ]); 295 | }); 296 | }); 297 | }); 298 | 299 | describe('#isActive', function() { 300 | it('should return false before a router becomes active', function() { 301 | expect(this.router.isActive()).to.be.false; 302 | }); 303 | 304 | it('should return true when a router is active', function() { 305 | Backbone.history.trigger('route', this.router); 306 | expect(this.router.isActive()).to.be.true; 307 | }); 308 | 309 | it('should return false after a router becomes inactive', function() { 310 | Backbone.history.trigger('route', this.router); 311 | Backbone.history.trigger('route', {}); 312 | expect(this.router.isActive()).to.be.false; 313 | }); 314 | }); 315 | }); 316 | 317 | function createStubbedRoute() { 318 | let route = new Route(); 319 | route.fetch = stub(); 320 | route.render = stub(); 321 | route.destroy = stub(); 322 | return route; 323 | } 324 | 325 | describe('Integration', function() { 326 | beforeEach(function() { 327 | Backbone.history.navigate(''); 328 | Backbone.history.start(); 329 | }); 330 | 331 | afterEach(function() { 332 | Backbone.history.navigate(''); 333 | Backbone.history.stop(); 334 | }); 335 | 336 | describe('Entering', function() { 337 | beforeEach(function() { 338 | this.route = createStubbedRoute(); 339 | this.router = new Router({ 340 | routes: { 341 | 'route/:arg1/:arg2': () => this.route 342 | } 343 | }); 344 | }); 345 | 346 | it('should enter the route successfully', function(done) { 347 | this.route.on('before:enter', () => { 348 | expect(this.route.fetch).not.to.have.been.called; 349 | expect(this.route.render).not.to.have.been.called; 350 | }); 351 | 352 | this.route.on('enter', () => { 353 | expect(this.route.fetch).to.have.been.called; 354 | expect(this.route.render).to.have.been.called; 355 | done(); 356 | }); 357 | 358 | this.route.on('error', (route, err) => done(err)); 359 | 360 | Backbone.history.navigate('route/1/2', true); 361 | }); 362 | }); 363 | 364 | describe('Exiting', function() { 365 | beforeEach(function(done) { 366 | this.route1 = createStubbedRoute(); 367 | this.route2 = createStubbedRoute(); 368 | 369 | this.router = new Router({ 370 | routes: { 371 | 'route1/:arg1/:arg2': () => this.route1, 372 | 'route2/:arg1/:arg2': () => this.route2 373 | } 374 | }); 375 | 376 | this.route1.on('enter', () => done()); 377 | Backbone.history.navigate('route1/1/2', true); 378 | }); 379 | 380 | it('should exit the route successfully', function(done) { 381 | this.route1.on('before:exit', () => { 382 | expect(this.route1.destroy).not.to.have.been.called; 383 | }); 384 | 385 | this.route1.on('exit', () => { 386 | expect(this.route1.destroy).to.have.been.called; 387 | expect(this.route2.fetch).not.to.have.been.called; 388 | expect(this.route2.render).not.to.have.been.called; 389 | }); 390 | 391 | this.route2.on('enter', () => { 392 | expect(this.route2.fetch).to.have.been.called; 393 | expect(this.route2.render).to.have.been.called; 394 | done(); 395 | }); 396 | 397 | this.route1.on('error', (route, err) => done(err)); 398 | this.route2.on('error', (route, err) => done(err)); 399 | 400 | Backbone.history.navigate('route2/1/2', true); 401 | }); 402 | }); 403 | 404 | describe('Cancelling', function() { 405 | beforeEach(function() { 406 | this.route = createStubbedRoute(); 407 | this.router = new Router({ 408 | routes: { 409 | 'route/:arg1/:arg2': () => this.route 410 | } 411 | }); 412 | }); 413 | 414 | it('should cancel the route successfully', function(done) { 415 | this.route.on('fetch', this.route.cancel); 416 | this.route.on('render cancel', () => { 417 | expect(this.route.render).not.to.have.been.called; 418 | done(); 419 | }); 420 | 421 | this.route.on('error', (route, err) => done(err)); 422 | Backbone.history.navigate('route/1/2', true); 423 | }); 424 | }); 425 | }); 426 | -------------------------------------------------------------------------------- /dist/backbone-routing.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"backbone-routing.js","sources":["backbone-routing.js","/source/backbone-routing.js"],"names":["global","factory","exports","module","require","define","amd","Backbone","Routing","Metal","this","CancellationError","Error","extend","name","Route","Class","enter","args","undefined","arguments","_isEntering","trigger","apply","concat","Promise","resolve","then","_this","_isCancelled","reject","fetch","render","err","exit","cancel","_isExiting","_this2","destroy","stopListening","_this3","once","isEntering","isExiting","isCancelled","Router","prototype","constructor","listenTo","history","_onHistoryRoute","_super","isActive","_isActive","execute","callback","wasInactive","_this4","_execute","_prevRoute","route","_this5","router","backbone_routing"],"mappings":"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","sourceRoot":"/source/","sourcesContent":["(function (global, factory) {\n typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('backbone'), require('backbone-metal')) :\n typeof define === 'function' && define.amd ? define(['backbone', 'backbone-metal'], factory) :\n global.Backbone.Routing = factory(global.Backbone, global.Metal)\n}(this, function (Backbone, Metal) { 'use strict';\n\n const CancellationError = Metal.Error.extend({\n name: 'CancellationError'\n });\n\n /**\n * @public\n * @class Route\n */\n const Route = Metal.Class.extend({\n\n /**\n * @public\n * @method enter\n * @returns {Promise}\n * @param {...*} [args=[]]\n */\n enter(args = []) {\n this._isEntering = true;\n this.trigger('before:enter before:fetch', this, ...args);\n\n return Promise.resolve()\n .then(() => {\n if (this._isCancelled) {\n return Promise.reject(new CancellationError());\n }\n return this.fetch(...args);\n })\n .then(() => this.trigger('fetch before:render', this, ...args))\n .then(() => {\n if (this._isCancelled) {\n return Promise.reject(new CancellationError());\n }\n return this.render(...args);\n })\n .then(() => {\n this._isEntering = false;\n this.trigger('render enter', this, ...args);\n })\n .catch(err => {\n this._isEntering = false;\n if (err instanceof CancellationError) {\n this.trigger('cancel', this);\n } else {\n this.trigger('error error:enter', this, err);\n throw err;\n }\n });\n },\n\n /**\n * @public\n * @method exit\n * @returns {Promise}\n */\n exit() {\n if (this._isEntering) {\n this.cancel();\n }\n this._isExiting = true;\n this.trigger('before:exit before:destroy', this);\n\n return Promise.resolve()\n .then(() => this.destroy())\n .then(() => {\n this._isExiting = false;\n this.trigger('destroy exit', this);\n this.stopListening();\n })\n .catch(err => {\n this._isExiting = false;\n this.trigger('error error:exit', this, err);\n this.stopListening();\n throw err;\n });\n },\n\n /**\n * @public\n * @method cancel\n * @returns {Promise}\n */\n cancel() {\n if (!this._isEntering) {\n return;\n }\n this.trigger('before:cancel', this);\n this._isCancelled = true;\n return new Promise((resolve, reject) => {\n this.once('cancel', resolve);\n this.once('enter error', reject);\n });\n },\n\n /**\n * @public\n * @method isEntering\n * @returns {Boolean}\n */\n isEntering() {\n return !!this._isEntering;\n },\n\n /**\n * @public\n * @method isExiting\n * @returns {Boolean}\n */\n isExiting() {\n return !!this._isExiting;\n },\n\n /**\n * @public\n * @method isCancelled\n * @returns {Boolean}\n */\n isCancelled() {\n return !!this._isCancelled;\n },\n\n /**\n * @public\n * @abstract\n * @method fetch\n * @param {...*} [args=[]]\n */\n fetch() {},\n\n /**\n * @public\n * @abstract\n * @method render\n * @param {...*} [args=[]]\n */\n render() {},\n\n /**\n * @public\n * @abstract\n * @method destroy\n */\n destroy() {}\n });\n\n /**\n * @public\n * @class Router\n */\n const Router = Metal.Class.extend(Backbone.Router.prototype, Backbone.Router).extend({\n constructor() {\n this.listenTo(Backbone.history, 'route', this._onHistoryRoute);\n this._super(...arguments);\n },\n\n /**\n * @public\n * @method isActive\n * @returns {Boolean}\n */\n isActive() {\n return !!this._isActive;\n },\n\n /**\n * @public\n * @method execute\n * @param {Function} callback\n * @param {Array} [args]\n */\n execute(callback, args) {\n let wasInactive = !this._isActive;\n if (wasInactive) {\n this.trigger('before:enter', this);\n }\n\n this.trigger('before:route', this);\n\n return Promise.resolve().then(() => {\n return this._execute(callback, args);\n }).then(() => {\n this.trigger('route', this);\n\n if (wasInactive) {\n this.trigger('enter', this);\n }\n }).catch(err => {\n this.trigger('error', this, err);\n Backbone.history.trigger('error', this, err);\n throw err;\n });\n },\n\n /**\n * @public\n * @method execute\n * @param {Function} callback\n * @param {Array} [args]\n * @returns {Promise}\n */\n _execute(callback, args) {\n return Promise.resolve().then(() => {\n if (Router._prevRoute instanceof Route) {\n return Router._prevRoute.exit();\n }\n }).then(() => {\n let route = Router._prevRoute = callback.apply(this, args);\n if (route instanceof Route) {\n route.router = this;\n return route.enter(args);\n }\n });\n },\n\n /**\n * @private\n * @method _onHistoryRoute\n * @param {Router} router\n */\n _onHistoryRoute(router) {\n this._isActive = (router === this);\n }\n }, {\n\n /**\n * @private\n * @member _prevRoute\n */\n _prevRoute: null\n });\n\n var backbone_routing = {Route, Router, CancellationError};\n\n return backbone_routing;\n\n}));\n","(function (global, factory) {\n typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('backbone'), require('backbone-metal')) :\n typeof define === 'function' && define.amd ? define(['backbone', 'backbone-metal'], factory) :\n global.Backbone.Routing = factory(global.Backbone, global.Metal)\n}(this, function (Backbone, Metal) { 'use strict';\n\n const CancellationError = Metal.Error.extend({\n name: 'CancellationError'\n });\n\n /**\n * @public\n * @class Route\n */\n const Route = Metal.Class.extend({\n\n /**\n * @public\n * @method enter\n * @returns {Promise}\n * @param {...*} [args=[]]\n */\n enter(args = []) {\n this._isEntering = true;\n this.trigger('before:enter before:fetch', this, ...args);\n\n return Promise.resolve()\n .then(() => {\n if (this._isCancelled) {\n return Promise.reject(new CancellationError());\n }\n return this.fetch(...args);\n })\n .then(() => this.trigger('fetch before:render', this, ...args))\n .then(() => {\n if (this._isCancelled) {\n return Promise.reject(new CancellationError());\n }\n return this.render(...args);\n })\n .then(() => {\n this._isEntering = false;\n this.trigger('render enter', this, ...args);\n })\n .catch(err => {\n this._isEntering = false;\n if (err instanceof CancellationError) {\n this.trigger('cancel', this);\n } else {\n this.trigger('error error:enter', this, err);\n throw err;\n }\n });\n },\n\n /**\n * @public\n * @method exit\n * @returns {Promise}\n */\n exit() {\n if (this._isEntering) {\n this.cancel();\n }\n this._isExiting = true;\n this.trigger('before:exit before:destroy', this);\n\n return Promise.resolve()\n .then(() => this.destroy())\n .then(() => {\n this._isExiting = false;\n this.trigger('destroy exit', this);\n this.stopListening();\n })\n .catch(err => {\n this._isExiting = false;\n this.trigger('error error:exit', this, err);\n this.stopListening();\n throw err;\n });\n },\n\n /**\n * @public\n * @method cancel\n * @returns {Promise}\n */\n cancel() {\n if (!this._isEntering) {\n return;\n }\n this.trigger('before:cancel', this);\n this._isCancelled = true;\n return new Promise((resolve, reject) => {\n this.once('cancel', resolve);\n this.once('enter error', reject);\n });\n },\n\n /**\n * @public\n * @method isEntering\n * @returns {Boolean}\n */\n isEntering() {\n return !!this._isEntering;\n },\n\n /**\n * @public\n * @method isExiting\n * @returns {Boolean}\n */\n isExiting() {\n return !!this._isExiting;\n },\n\n /**\n * @public\n * @method isCancelled\n * @returns {Boolean}\n */\n isCancelled() {\n return !!this._isCancelled;\n },\n\n /**\n * @public\n * @abstract\n * @method fetch\n * @param {...*} [args=[]]\n */\n fetch() {},\n\n /**\n * @public\n * @abstract\n * @method render\n * @param {...*} [args=[]]\n */\n render() {},\n\n /**\n * @public\n * @abstract\n * @method destroy\n */\n destroy() {}\n });\n\n /**\n * @public\n * @class Router\n */\n const Router = Metal.Class.extend(Backbone.Router.prototype, Backbone.Router).extend({\n constructor() {\n this.listenTo(Backbone.history, 'route', this._onHistoryRoute);\n this._super(...arguments);\n },\n\n /**\n * @public\n * @method isActive\n * @returns {Boolean}\n */\n isActive() {\n return !!this._isActive;\n },\n\n /**\n * @public\n * @method execute\n * @param {Function} callback\n * @param {Array} [args]\n */\n execute(callback, args) {\n let wasInactive = !this._isActive;\n if (wasInactive) {\n this.trigger('before:enter', this);\n }\n\n this.trigger('before:route', this);\n\n return Promise.resolve().then(() => {\n return this._execute(callback, args);\n }).then(() => {\n this.trigger('route', this);\n\n if (wasInactive) {\n this.trigger('enter', this);\n }\n }).catch(err => {\n this.trigger('error', this, err);\n Backbone.history.trigger('error', this, err);\n throw err;\n });\n },\n\n /**\n * @public\n * @method execute\n * @param {Function} callback\n * @param {Array} [args]\n * @returns {Promise}\n */\n _execute(callback, args) {\n return Promise.resolve().then(() => {\n if (Router._prevRoute instanceof Route) {\n return Router._prevRoute.exit();\n }\n }).then(() => {\n let route = Router._prevRoute = callback.apply(this, args);\n if (route instanceof Route) {\n route.router = this;\n return route.enter(args);\n }\n });\n },\n\n /**\n * @private\n * @method _onHistoryRoute\n * @param {Router} router\n */\n _onHistoryRoute(router) {\n this._isActive = (router === this);\n }\n }, {\n\n /**\n * @private\n * @member _prevRoute\n */\n _prevRoute: null\n });\n\n var backbone_routing = {Route, Router, CancellationError};\n\n return backbone_routing;\n\n}));\n"]} --------------------------------------------------------------------------------