├── .gitignore
├── .travis.yml
├── Gruntfile.js
├── LICENSE
├── README.md
├── backbone.controller.js
├── bower.json
├── package.json
└── tests
├── config.js
├── spec
├── basic.js
├── router.js
└── routerCancel.js
└── vendor
├── Q.js
├── backbone.js
├── chai.js
├── jquery.js
├── sinon.js
└── underscore.js
/.gitignore:
--------------------------------------------------------------------------------
1 | lib-cov
2 | *.seed
3 | *.log
4 | *.csv
5 | *.dat
6 | *.out
7 | *.pid
8 | *.gz
9 |
10 | pids
11 | logs
12 | results
13 |
14 | npm-debug.log
15 | node_modules
16 |
17 | .DS_Store
18 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 0.10
4 | before_install:
5 | - export DISPLAY=:99.0
6 | - sh -e /etc/init.d/xvfb start
7 | - sleep 3
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | module.exports = function(grunt) {
2 | grunt.initConfig({
3 | jshint: {
4 | options: {
5 | browser: true,
6 | globals: {
7 | console: true
8 | }
9 | },
10 | all: [
11 | 'Gruntfile.js',
12 | 'backbone.controller.js',
13 | 'tests/**/*.js',
14 | '!tests/vendor/*.js'
15 | ]
16 | },
17 |
18 | karma: {
19 | test: {
20 | configFile: 'tests/config.js',
21 | singleRun: true,
22 | browsers: ['PhantomJS']
23 | }
24 | }
25 | });
26 |
27 | grunt.loadNpmTasks('grunt-contrib-jshint');
28 | grunt.loadNpmTasks('grunt-karma');
29 |
30 | grunt.registerTask('test', ['jshint', 'karma']);
31 | grunt.registerTask('default', ['test']);
32 | };
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Artyom Trityak
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Backbone.Controller
2 | ===================
3 |
4 | [](https://travis-ci.org/artyomtrityak/backbone.controller)
5 |
6 |
7 |
8 |
9 | Controller for Backbone MV*
10 |
11 | Keep controller logic separated, split your routes to controllers.
12 |
13 | ## Usage
14 |
15 | DEMO: [Just Test It](https://github.com/artyomtrityak/just-test-it)
16 |
17 | Usage examples:
18 |
19 | ### Basic
20 |
21 | ```js
22 | var Controller = Backbone.Controller.extend({
23 | initialize: function() {
24 | // do init stuff
25 | },
26 |
27 | search: function(query, page) {
28 | // create search model and view
29 | }
30 | });
31 |
32 | var searchController = new Controller();
33 | ```
34 |
35 | ### Controller supports default Backbone events
36 |
37 | ```js
38 | var Controller = Backbone.Controller.extend({
39 | initialize: function() {
40 | this.model = new Backbone.Model();
41 | this.listenTo(this.model, 'add', this._onAdd);
42 | },
43 |
44 | _onAdd: function(model) {
45 | // show notification view
46 | }
47 | });
48 |
49 | var catsController = new Controller();
50 | ```
51 |
52 | ### Controller has remove method for cleanup
53 |
54 | Remove method should do correct remove for all controller views and models, stop listening controller events and clear state.
55 |
56 | ```js
57 | var Controller = Backbone.Controller.extend({
58 | initialize: function() {
59 | this.model = new Backbone.Model();
60 | this.listenTo(this.model, 'add', this._onAdd);
61 | },
62 |
63 | _onAdd: function(model) {
64 | // show notification view
65 | }
66 | });
67 |
68 | var catsController = new Controller();
69 | //...
70 | catsController.remove();
71 | ```
72 |
73 | Also remove method is calling automatically when user goes from one controller to another. See routing section for details.
74 |
75 | ### Controller supports declarative routes definition.
76 |
77 | It's little more complex than previous examples but can be used to keep all routes separately
78 | which is good idea for any size app.
79 |
80 |
81 | ```js
82 | var CatsController = Backbone.Controller.extend({
83 | routes: {
84 | 'cats': 'list',
85 | 'cats/:id': 'showCat'
86 | },
87 |
88 | initialize: function() {
89 | // do some init stuff
90 | },
91 |
92 | list: function() {
93 | // show cats list
94 | },
95 |
96 | showCat: function(catId) {
97 | // show cat view
98 | }
99 | });
100 |
101 | var DogsController = Backbone.Controller.extend({
102 | routes: {
103 | '': 'list',
104 | 'dogs': 'list',
105 | 'dogs/:id': 'showDog'
106 | },
107 |
108 | initialize: function() {
109 | // do some init stuff
110 | },
111 |
112 | list: function() {
113 | // show dogs list
114 | },
115 |
116 | showDog: function(catId) {
117 | // show cat view
118 | },
119 |
120 | remove: functin() {
121 | // cleanup
122 | }
123 | });
124 |
125 | var Application = Backbone.Router.extend({
126 | controllers: {},
127 |
128 | initialize: function() {
129 | this.controllers.cats = new CatsController({router: this});
130 | this.controllers.dogs = new DogsController({router: this});
131 |
132 | Backbone.history.start();
133 | }
134 | });
135 | ```
136 |
137 | The main idea - pass `{router: routerInstance}` as controller option.
138 | This allows to define controller specific routes in separated controllers.
139 |
140 | When url changes from `#dogs` / `#dogs/:id` to any route which defined in another controller, remove method is calling automatically.
141 |
142 | This case controller should clear state, remove controller specific views and models.
143 |
144 | ### Controller can automatically add router without creating Backbone.Router instance
145 |
146 | ```js
147 | var CatsController = Backbone.Controller.extend({
148 | routes: {
149 | 'cats': 'list',
150 | 'cats/:id': 'showCat'
151 | },
152 |
153 | initialize: function() {
154 | // do some init stuff
155 | },
156 |
157 | list: function() {
158 | // show cats list
159 | },
160 |
161 | showCat: function(catId) {
162 | // show cat view
163 | }
164 | });
165 |
166 | var DogsController = Backbone.Controller.extend({
167 | routes: {
168 | '': 'list',
169 | 'dogs': 'list',
170 | 'dogs/:id': 'showDog'
171 | },
172 |
173 | initialize: function() {
174 | // do some init stuff
175 | },
176 |
177 | list: function() {
178 | // show dogs list
179 | },
180 |
181 | showDog: function(catId) {
182 | // show cat view
183 | }
184 | });
185 |
186 | var cats = new CatsController({router: true});
187 | var dogs = new DogsController({router: true});
188 | ```
189 |
190 | ### Before / after routing
191 |
192 | Controller automatically calls `onBeforeRoute` / `onAfterRoute` functions when processing routes.
193 |
194 | ```js
195 | var DogsController = Backbone.Controller.extend({
196 | routes: {
197 | '': 'list',
198 | 'dogs': 'list'
199 | },
200 |
201 | initialize: function() {
202 | // do some init stuff
203 | },
204 |
205 | onBeforeRoute: function(url, param1, param2, ...) {
206 | // called before `#dogs` / `#` routes
207 | // Set some state variables, create controller layout etc
208 | },
209 |
210 | onAfterRoute: function(url, param1, param2, ...) {
211 | // called after `#dogs` / `#` routes
212 | },
213 |
214 | list: function() {
215 | // show dogs list
216 | }
217 | });
218 |
219 | var dogs = new DogsController({router: true});
220 |
221 |
222 | //Cancel route
223 | var DogsController = Backbone.Controller.extend({
224 | routes: {
225 | 'dogs': 'list',
226 | 'dogs/:id': 'showDog'
227 | },
228 |
229 | initialize: function() {
230 | // do some init stuff
231 | },
232 |
233 | list: function() {
234 | // show dogs list
235 | },
236 |
237 | showDog: function(catId) {
238 | // show cat view
239 | },
240 | onBeforeRoute : function(url) {
241 | console.log('before route');
242 | var deferred = Q.defer();
243 |
244 | setTimeout(function() {
245 | deferred.resolve('ggg');
246 | }, 2000);
247 |
248 | return deferred.promise;
249 | //return false;
250 | },
251 | onAfterRoute : function() {
252 | console.log('afterRoute');
253 | }
254 | });
255 |
256 | var dogs = new DogsController({router : true});
257 | Backbone.history.start();
258 | ```
259 |
260 | ### Redirecting to another route
261 |
262 | If declarative routing has been used in project, you don't have access directly to Router instance.
263 | Backbone Controller provides Controller.navigate method as proxy for Backbone.Router.navigate method.
264 |
265 | ```js
266 | var DogsController = Backbone.Controller.extend({
267 | routes: {
268 | 'dogs': 'list'
269 | },
270 |
271 | list: function() {
272 | // show dogs list
273 | // if something
274 | this.navigate('cats/', {trigger: true});
275 | }
276 | });
277 |
278 | var dogs = new DogsController({router: true});
279 | ```
280 |
281 | ## Dependencies loading
282 |
283 | ### Require.js AMD
284 |
285 | ```js
286 | requirejs.config({
287 | baseUrl: 'static/',
288 | urlArgs: 'bust=' + Date.now(),
289 | paths: {
290 | jquery: 'assets/js/jquery',
291 | underscore: 'assets/js/underscore',
292 | backbone: 'assets/js/backbone',
293 | controller: 'assets/js/backbone.controller'
294 | },
295 |
296 | shim: {
297 | backbone: {
298 | deps: ['underscore', 'jquery'],
299 | exports: 'Backbone'
300 | },
301 | controller: {
302 | deps: ['underscore', 'backbone']
303 | },
304 | app: ['controller']
305 | }
306 | });
307 | ```
308 |
309 | ### CommonJS
310 |
311 | ```js
312 | var Controller = require('controller');
313 | // or require Backbone, both fine
314 |
315 | var HomeController = Controller.extend({
316 | ...
317 | });
318 | ```
319 |
320 | ### Old style
321 |
322 | ```html
323 |
324 |
325 |
326 |
327 | ```
328 |
329 | ### Bower
330 |
331 | ```sh
332 | bower install backbone.controller
333 | ```
334 |
--------------------------------------------------------------------------------
/backbone.controller.js:
--------------------------------------------------------------------------------
1 | // Backbone.Controller 0.3.0
2 | // (c) Artyom Trityak
3 | // Backbone.Controller may be freely distributed under the MIT license.
4 | // For all details and documentation:
5 | // https://github.com/artyomtrityak/backbone.controller
6 |
7 | (function(root, factory) {
8 |
9 | // Set up Backbone.Controller appropriately for the environment. Start with AMD.
10 | if (typeof define === 'function' && define.amd) {
11 | define(['underscore', 'backbone', 'exports'], function(_, Backbone, exports) {
12 | // Export global even in AMD case in case this script is loaded with
13 | // others that may still expect a global Backbone.
14 | root.Backbone.Controller = factory(root, exports, _, Backbone);
15 | return root.Backbone.Controller;
16 | });
17 |
18 | // Next for Node.js or CommonJS.
19 | } else if (typeof exports !== 'undefined') {
20 | var _ = require('underscore'),
21 | Backbone = require('backbone');
22 | module.exports = factory(root, exports, _, Backbone);
23 |
24 | // Finally, as a browser global.
25 | } else {
26 | root.Backbone.Controller = factory(root, {}, root._, root.Backbone);
27 | }
28 |
29 | }(this, function(root, exports, _, Backbone) {
30 |
31 | // Binds your routes to Backbone router.
32 | // Allows define routes separated in each controller.
33 | // For example:
34 | //
35 | // Backbone.Controller.extend({
36 | // routes: {
37 | // '': 'index',
38 | // 'cat/:query/p:page': 'showCat'
39 | // },
40 | //
41 | // initialize: function() {
42 | // // do init stuff
43 | // },
44 | //
45 | // index: function() {
46 | // // create index model and view
47 | // },
48 | //
49 | // showCat: function(query, page) {
50 | // // create cat model and view
51 | // // if something - call navigate as proxy to Backbone.Router.navigate
52 | // this.navigate('dogs/', {trigger: true});
53 | // }
54 | // });
55 | //
56 | // For router initialization router option should be given.
57 | // For example:
58 | //
59 | // var Application = Backbone.Router.extend({
60 | // controllers: {},
61 | //
62 | // initialize: function() {
63 | // this.controllers.home = new HomeController({router: this});
64 | // this.controllers.search = new SearchController({router: this});
65 | //
66 | // Backbone.history.start();
67 | // }
68 | // });
69 | //
70 | // ========
71 | //
72 | // Auto router
73 | //
74 | // var CatsController = Backbone.Controller.extend({
75 | // routes: {
76 | // '': 'index',
77 | // 'cat/:query/p:page': 'showCat'
78 | // },
79 | //
80 | // onBeforeRequest: function(url, param1, param2 ...) {
81 | // // do before request actions
82 | // },
83 | //
84 | // onAfterRequest: function(url, param1, param2 ...) {
85 | // // do after request actions
86 | // },
87 | //
88 | // remove: function() {
89 | // // make cleanup
90 | // }
91 | // ...
92 | // });
93 | //
94 | // var cats = new CatsController({router: true});
95 | //
96 | var bindRoutes = function(Router) {
97 | for (var url in this.routes) {
98 | // Using default Backbone.js route method.
99 | // Same URLs from different controllers are not allowed.
100 | // Last controller with same URL will be used.
101 | Router.route(url, url, _.bind(onRoute, this, url));
102 | }
103 | },
104 | onRoute = function() {
105 | var self = this,
106 | args = _.toArray(arguments),
107 | url = args[0],
108 | methodName = this.routes[url],
109 | params = args.slice(1),
110 | triggerRouteAndAfterRoute = function() {
111 | // Call route method with routing parameters like :id, *path etc
112 | self[methodName].apply(self, params);
113 |
114 | // Call onAfterRoute after route
115 | if ( _.isFunction(self.onAfterRoute)) {
116 | self.onAfterRoute.apply(self, args);
117 | }
118 | },
119 | beforeRouteResult, isPromiseObj;
120 |
121 | // Call remove if router goes to another controller
122 | if (cachedController && cachedController !== this &&
123 | typeof cachedController.remove === 'function') {
124 |
125 | cachedController.remove.apply(cachedController);
126 | }
127 | cachedController = this;
128 |
129 | // Call onBeforeRoute before route
130 | if ( _.isFunction(this.onBeforeRoute) ) {
131 | beforeRouteResult = this.onBeforeRoute.apply(this, args);
132 | }
133 |
134 | if (beforeRouteResult === false || beforeRouteResult === null) return this;
135 | isPromiseObj = beforeRouteResult && beforeRouteResult.done && _.isFunction(beforeRouteResult.done);
136 |
137 | if (isPromiseObj) {
138 | beforeRouteResult.done(triggerRouteAndAfterRoute);
139 | } else {
140 | triggerRouteAndAfterRoute();
141 | }
142 | },
143 | cachedRouter,
144 | cachedController;
145 |
146 | Backbone.Controller = function(options){
147 | this.options = options || {};
148 | if (_.isFunction(this.initialize)){
149 | this.initialize(this.options);
150 | }
151 | if (this.options.router === true) {
152 | // Save/get to/from closure router instance for binding routes
153 | cachedRouter = cachedRouter || new Backbone.Router();
154 | this.options.router = cachedRouter;
155 | }
156 | if (this.options.router) {
157 | cachedRouter = this.options.router;
158 | bindRoutes.call(this, this.options.router);
159 | }
160 | };
161 |
162 | // Method uses cached Backbone Router and allows navigate to another route
163 | Backbone.Controller.prototype.navigate = function() {
164 | var params = _.toArray(arguments).slice(0);
165 | cachedRouter.navigate.apply(this, params);
166 | };
167 |
168 | Backbone.Controller.extend = Backbone.Router.extend;
169 |
170 | // Supporting default Backbone events like on, off, trigger, listenTo etc
171 | // Provides remove method which can be called on controller removal.
172 | _.extend(Backbone.Controller.prototype, Backbone.Events, {
173 | remove: function() {
174 | this.stopListening();
175 | }
176 | });
177 |
178 | return Backbone.Controller;
179 |
180 | }));
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "backbone.controller",
3 | "main": "backbone.controller.js",
4 | "version": "0.3.1",
5 | "homepage": "https://github.com/artyomtrityak/backbone.controller",
6 | "authors": [
7 | "Artyom Trityak "
8 | ],
9 | "description": "Controller for Backbone.js applications",
10 | "moduleType": [
11 | "amd",
12 | "globals"
13 | ],
14 | "keywords": [
15 | "controller",
16 | "backbone",
17 | "router",
18 | "client",
19 | "browser",
20 | "mvc"
21 | ],
22 | "license": "MIT",
23 | "ignore": [
24 | "**/.*",
25 | "node_modules",
26 | "bower_components",
27 | "test",
28 | "tests",
29 | "bower.json",
30 | "Gruntfile.js",
31 | "package.json",
32 | ".gitignore",
33 | ".travis.yml",
34 | "README.md"
35 | ],
36 | "dependencies": {
37 | "backbone": "latest",
38 | "underscore": "latest"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "backbone.controller",
3 | "main": "backbone.controller",
4 | "description": "Controller for Backbone.js applications",
5 | "version": "0.3.1",
6 | "author": "Artyom Trityak ",
7 | "url": "https://github.com/artyomtrityak/backbone.controller",
8 | "license": "MIT",
9 | "keywords": [
10 | "controller",
11 | "backbone",
12 | "router",
13 | "client",
14 | "browser"
15 | ],
16 | "engines": {
17 | "node": "0.10.28",
18 | "npm": "1.4.10"
19 | },
20 |
21 | "repository": {
22 | "type": "git",
23 | "url": "git@github.com:artyomtrityak/backbone.controller.git"
24 | },
25 | "scripts": {
26 | "test": "grunt test"
27 | },
28 | "dependencies": {
29 | "backbone": ">=1.0.0"
30 | },
31 | "devDependencies": {
32 | "grunt": "~0.4.5",
33 | "grunt-cli": "~0.1.9",
34 | "grunt-contrib-jshint": "~0.6.4",
35 | "grunt-karma": "~0.8.3",
36 | "karma": "~0.12.16",
37 | "karma-chrome-launcher": "~0.1.4",
38 | "karma-firefox-launcher": "~0.1.3",
39 | "karma-html2js-preprocessor": "~0.1.0",
40 | "karma-mocha": "~0.1.4",
41 | "karma-phantomjs-launcher": "~0.1.4",
42 | "karma-script-launcher": "~0.1.0",
43 | "mocha": "~1.20.1",
44 | "phantomjs": "~1.9.2-6"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/tests/config.js:
--------------------------------------------------------------------------------
1 | module.exports = function(config) {
2 | config.set({
3 | // base path, that will be used to resolve files and exclude
4 | basePath: '../',
5 |
6 | frameworks: ['mocha'],
7 |
8 | // list of files / patterns to load in the browser
9 | files: [
10 | 'tests/vendor/sinon.js',
11 | 'tests/vendor/chai.js',
12 | 'tests/vendor/jquery.js',
13 | 'tests/vendor/underscore.js',
14 | 'tests/vendor/backbone.js',
15 | 'tests/vendor/Q.js',
16 | 'backbone.controller.js',
17 | 'tests/spec/*.js'
18 | ],
19 |
20 | // list of files to exclude
21 | exclude: [
22 | ],
23 |
24 | client: {
25 | mocha: {
26 | ui: 'bdd'
27 | }
28 | },
29 |
30 | // use dots reporter, as travis terminal does not support escaping sequences
31 | // possible values: 'dots', 'progress'
32 | // CLI --reporters progress
33 | reporters: ['progress'],
34 |
35 | // web server port
36 | // CLI --port 9876
37 | port: 9876,
38 |
39 | // enable / disable colors in the output (reporters and logs)
40 | // CLI --colors --no-colors
41 | colors: true,
42 |
43 | // level of logging
44 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
45 | // CLI --log-level debug
46 | logLevel: config.LOG_INFO,
47 |
48 | // enable / disable watching file and executing tests whenever any file changes
49 | // CLI --auto-watch --no-auto-watch
50 | autoWatch: true,
51 |
52 | // Start these browsers, currently available:
53 | // - Chrome
54 | // - ChromeCanary
55 | // - Firefox
56 | // - Opera
57 | // - Safari (only Mac)
58 | // - PhantomJS
59 | // - IE (only Windows)
60 | // CLI --browsers Chrome,Firefox,Safari
61 | browsers: [process.env.TRAVIS ? 'Firefox' : 'Chrome'],
62 |
63 | // If browser does not capture in given timeout [ms], kill it
64 | // CLI --capture-timeout 5000
65 | captureTimeout: 20000,
66 |
67 | // Auto run tests on start (when browsers are captured) and exit
68 | // CLI --single-run --no-single-run
69 | singleRun: false,
70 |
71 | // report which specs are slower than 500ms
72 | // CLI --report-slower-than 500
73 | reportSlowerThan: 500,
74 |
75 | plugins: [
76 | 'karma-mocha',
77 | 'karma-phantomjs-launcher',
78 | 'karma-chrome-launcher',
79 | 'karma-firefox-launcher'
80 | ]
81 | });
82 | };
--------------------------------------------------------------------------------
/tests/spec/basic.js:
--------------------------------------------------------------------------------
1 | var expect = chai.expect;
2 |
3 | describe('Backbone.Controller', function(){
4 |
5 | it('should export correct controller', function(){
6 | expect(Backbone.Controller).to.be.a('function');
7 | });
8 |
9 | it('should have possibility to extend', function() {
10 | expect(Backbone.Controller.extend).to.be.a('function');
11 | expect(Backbone.Controller.extend).to.equal(
12 | Backbone.History.extend
13 | );
14 |
15 | var callback = sinon.stub(),
16 | params = {name: 'test', param2: 22},
17 | Controller;
18 |
19 | Controller = Backbone.Controller.extend({
20 | initialize: callback
21 | });
22 |
23 | expect(callback.callCount).to.be.equal(0);
24 |
25 | controllerInstance = new Controller(params);
26 | expect(callback.callCount).to.be.equal(1);
27 | expect(callback.calledWith(params)).to.be.equal(true);
28 | });
29 |
30 | it('should have default remove method', function() {
31 | var Controller = Backbone.Controller.extend({}),
32 | controllerInstance = new Controller();
33 |
34 | expect(controllerInstance.remove).to.be.a('function');
35 |
36 | sinon.spy(controllerInstance, 'stopListening');
37 | expect(controllerInstance.stopListening.callCount).to.be.equal(0);
38 | controllerInstance.remove();
39 | expect(controllerInstance.stopListening.callCount).to.be.equal(1);
40 |
41 | });
42 | });
--------------------------------------------------------------------------------
/tests/spec/router.js:
--------------------------------------------------------------------------------
1 | var expect = chai.expect,
2 | router;
3 |
4 | describe('Backbone.Controller routes', function(){
5 |
6 | before(function() {
7 | Backbone.history.start();
8 | });
9 |
10 | beforeEach(function() {
11 | router = new Backbone.Router();
12 | sinon.spy(router, 'route');
13 | });
14 |
15 | afterEach(function() {
16 | router.route.restore();
17 | });
18 |
19 | it('should not bind routes without config', function(){
20 | var Controller = Backbone.Controller.extend({
21 | routes: {
22 | 'test': 'method1'
23 | }
24 | }),
25 | controllerIns = new Controller(),
26 | controllerIns2 = new Controller();
27 |
28 | expect(router.route.callCount).to.be.equal(0);
29 | expect(router.route.callCount).to.be.equal(0);
30 | });
31 |
32 | it('should bind routes with router option', function() {
33 | var Controller, controllerIns, callback;
34 |
35 | callback = sinon.stub();
36 |
37 | Controller = Backbone.Controller.extend({
38 | routes: {
39 | 'test/': 'method1'
40 | },
41 | method1: callback
42 | });
43 |
44 | expect(router.route.callCount).to.be.equal(0);
45 | controllerIns = new Controller({router: router});
46 | expect(router.route.callCount).to.be.equal(1);
47 |
48 | expect(router.route.calledWith('test/', 'test/')).to.be.equal(true);
49 |
50 | router.navigate('test/', {trigger: true});
51 |
52 | expect(callback.callCount).to.be.equal(1);
53 | });
54 |
55 | it('should support auto router', function() {
56 | var Controller, controllerIns, callback;
57 |
58 | callback = sinon.stub();
59 |
60 | Controller = Backbone.Controller.extend({
61 | routes: {
62 | 'test2/': 'method1'
63 | },
64 | method1: callback
65 | });
66 |
67 | controllerIns = new Controller({router: true});
68 |
69 | router.navigate('test2/', {trigger: true});
70 | expect(callback.callCount).to.be.equal(1);
71 | });
72 |
73 | it('should work correct with many controllers and call remove', function() {
74 | var Controller1, controllerIns1, callback1, remove1,
75 | Controller2, controllerIns2, callback2, remove2;
76 |
77 | callback1 = sinon.stub();
78 | remove1 = sinon.stub();
79 | callback2 = sinon.stub();
80 | remove2 = sinon.stub();
81 |
82 | Controller1 = Backbone.Controller.extend({
83 | routes: {
84 | 'test3/': 'method1',
85 | 'test31/': 'method2'
86 | },
87 | method1: callback1,
88 | method2: function(){},
89 | remove: remove1
90 | });
91 |
92 | Controller2 = Backbone.Controller.extend({
93 | routes: {
94 | 'test4/': 'method1'
95 | },
96 | method1: callback2,
97 | remove: remove2
98 | });
99 |
100 | controllerIns1 = new Controller1({router: true});
101 | controllerIns2 = new Controller2({router: true});
102 |
103 | expect(callback1.callCount).to.be.equal(0);
104 | expect(remove1.callCount).to.be.equal(0);
105 | expect(remove2.callCount).to.be.equal(0);
106 |
107 | router.navigate('test3/', {trigger: true});
108 |
109 | expect(callback1.callCount).to.be.equal(1);
110 | expect(remove1.callCount).to.be.equal(0);
111 | expect(remove2.callCount).to.be.equal(0);
112 | expect(callback2.callCount).to.be.equal(0);
113 |
114 | router.navigate('test31/', {trigger: true});
115 |
116 | expect(remove1.callCount).to.be.equal(0);
117 | expect(remove2.callCount).to.be.equal(0);
118 |
119 | router.navigate('test4/', {trigger: true});
120 |
121 | expect(callback1.callCount).to.be.equal(1);
122 | expect(remove1.callCount).to.be.equal(1);
123 | expect(remove2.callCount).to.be.equal(0);
124 |
125 | router.navigate('test3/', {trigger: true});
126 |
127 | expect(remove2.callCount).to.be.equal(1);
128 |
129 | router.navigate('test31/', {trigger: true});
130 |
131 | expect(remove2.callCount).to.be.equal(1);
132 | expect(remove1.callCount).to.be.equal(1);
133 | });
134 |
135 | it('should support before/after', function() {
136 | var Controller, controllerIns, beforeCallback, afterCallback, callback;
137 |
138 | beforeCallback = sinon.stub();
139 | afterCallback = sinon.stub();
140 | callback = sinon.stub();
141 |
142 | Controller = Backbone.Controller.extend({
143 | routes: {
144 | 'test5/': 'method1',
145 | 'test5/:id': 'method1'
146 | },
147 | method1: callback,
148 | onBeforeRoute: beforeCallback,
149 | onAfterRoute: afterCallback
150 | });
151 |
152 | controllerIns = new Controller({router: true});
153 |
154 | expect(beforeCallback.callCount).to.be.equal(0);
155 | expect(afterCallback.callCount).to.be.equal(0);
156 | expect(beforeCallback.calledWith('test5/')).to.be.equal(false);
157 | expect(afterCallback.calledWith('test5/')).to.be.equal(false);
158 |
159 | router.navigate('test5/', {trigger: true});
160 |
161 | expect(callback.callCount).to.be.equal(1);
162 | expect(beforeCallback.callCount).to.be.equal(1);
163 | expect(afterCallback.callCount).to.be.equal(1);
164 |
165 | expect(beforeCallback.calledWith('test5/')).to.be.equal(true);
166 | expect(afterCallback.calledWith('test5/')).to.be.equal(true);
167 |
168 | router.navigate('test5/24', {trigger: true});
169 |
170 | expect(beforeCallback.calledWith('test5/:id', '24')).to.be.equal(true);
171 | expect(afterCallback.calledWith('test5/:id', '24')).to.be.equal(true);
172 | expect(callback.calledWith('24')).to.be.equal(true);
173 | });
174 |
175 | it('should support navigate', function() {
176 | var Controller, controllerIns, callback1, callback2;
177 |
178 | callback1 = sinon.stub();
179 | callback2 = sinon.stub();
180 |
181 | Controller = Backbone.Controller.extend({
182 | routes: {
183 | 'test6/': 'method1',
184 | 'test7/': 'method2'
185 | },
186 | method1: callback1,
187 | method2: callback2
188 | });
189 |
190 | controllerIns = new Controller({router: true});
191 |
192 | expect(controllerIns.navigate).to.be.a('function');
193 | expect(callback1.callCount).to.be.equal(0);
194 | expect(callback2.callCount).to.be.equal(0);
195 |
196 | controllerIns.navigate('test6/');
197 |
198 | expect(callback1.callCount).to.be.equal(0);
199 | expect(callback2.callCount).to.be.equal(0);
200 |
201 | controllerIns.navigate('test7/', {trigger: true});
202 |
203 | expect(callback1.callCount).to.be.equal(0);
204 | expect(callback2.callCount).to.be.equal(1);
205 |
206 | controllerIns.navigate('test6/', {trigger: true});
207 |
208 | expect(callback1.callCount).to.be.equal(1);
209 | expect(callback2.callCount).to.be.equal(1);
210 | });
211 | });
--------------------------------------------------------------------------------
/tests/spec/routerCancel.js:
--------------------------------------------------------------------------------
1 | var expect = chai.expect;
2 |
3 | describe('Backbone.Controller async', function() {
4 | var dogs,
5 | onAfterRouteQ = sinon.stub();
6 |
7 | before(function(done) {
8 | var DogsController = Backbone.Controller.extend({
9 | routes: {
10 | 'dogs': 'list'
11 | },
12 | list: function() {},
13 | onBeforeRoute: function(url) {
14 | var deferred = Q.defer();
15 |
16 | setTimeout(function() {
17 | deferred.resolve();
18 | done();
19 | }, 1000);
20 |
21 | return deferred.promise;
22 | //return false;
23 | },
24 | onAfterRoute: onAfterRouteQ
25 | });
26 |
27 | dogs = new DogsController({router: true});
28 | dogs.navigate('dogs', {trigger: true});
29 | });
30 |
31 | /* Cancel route navigate */
32 | it('should cancel routing', function() {
33 | var onAfterRoute = sinon.stub(),
34 | Controller = Backbone.Controller.extend({
35 | routes : {
36 | test700 : 'test700'
37 | },
38 | test700 : function() {},
39 | onBeforeRoute : function() {
40 | return false;
41 | },
42 | onAfterRoute : onAfterRoute
43 | }),
44 | controller = new Controller({router : true});
45 |
46 | controller.navigate('test700', {trigger: true});
47 | expect(onAfterRoute.callCount).to.be.equal(0);
48 | });
49 |
50 | it('should fire onAfterRoute', function() {
51 | var onAfterRoute = sinon.stub(),
52 | Controller = Backbone.Controller.extend({
53 | routes : {
54 | test701 : 'test701'
55 | },
56 | test701 : function() {},
57 | onBeforeRoute : function() {},
58 | onAfterRoute : onAfterRoute
59 | }),
60 | controller = new Controller({router : true});
61 |
62 | expect(Backbone.History.started).to.be.equal(true);
63 |
64 | controller.navigate('test701', {trigger: true});
65 | expect(onAfterRoute.callCount).to.be.equal(1);
66 | });
67 |
68 | it('should work with Promise object', function() {
69 | expect(onAfterRouteQ.callCount).to.be.equal(1);
70 | });
71 | });
72 |
--------------------------------------------------------------------------------
/tests/vendor/Q.js:
--------------------------------------------------------------------------------
1 | // vim:ts=4:sts=4:sw=4:
2 | /*!
3 | *
4 | * Copyright 2009-2012 Kris Kowal under the terms of the MIT
5 | * license found at http://github.com/kriskowal/q/raw/master/LICENSE
6 | *
7 | * With parts by Tyler Close
8 | * Copyright 2007-2009 Tyler Close under the terms of the MIT X license found
9 | * at http://www.opensource.org/licenses/mit-license.html
10 | * Forked at ref_send.js version: 2009-05-11
11 | *
12 | * With parts by Mark Miller
13 | * Copyright (C) 2011 Google Inc.
14 | *
15 | * Licensed under the Apache License, Version 2.0 (the "License");
16 | * you may not use this file except in compliance with the License.
17 | * You may obtain a copy of the License at
18 | *
19 | * http://www.apache.org/licenses/LICENSE-2.0
20 | *
21 | * Unless required by applicable law or agreed to in writing, software
22 | * distributed under the License is distributed on an "AS IS" BASIS,
23 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
24 | * See the License for the specific language governing permissions and
25 | * limitations under the License.
26 | *
27 | */
28 |
29 | (function (definition) {
30 | // Turn off strict mode for this function so we can assign to global.Q
31 | /* jshint strict: false */
32 |
33 | // This file will function properly as a