├── .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 | [](https://travis-ci.org/thejameskyle/backbone-routing)
6 | [](https://codeclimate.com/github/thejameskyle/backbone-routing)
7 | [](https://codeclimate.com/github/thejameskyle/backbone-routing)
8 | [](https://david-dm.org/thejameskyle/backbone-routing)
9 | [](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"]}
--------------------------------------------------------------------------------