backbone_mvc 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/backbone_mvc/engine.rb +4 -0
- data/lib/backbone_mvc/version.rb +3 -0
- data/lib/backbone_mvc.rb +4 -0
- data/lib/tasks/backbone_mvc_tasks.rake +4 -0
- data/test/backbone_mvc_test.rb +7 -0
- data/test/dummy/README.rdoc +28 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/assets/javascripts/application.js +13 -0
- data/test/dummy/app/assets/stylesheets/application.css +13 -0
- data/test/dummy/app/controllers/application_controller.rb +5 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/bin/bundle +3 -0
- data/test/dummy/bin/rails +4 -0
- data/test/dummy/bin/rake +4 -0
- data/test/dummy/config/application.rb +23 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +29 -0
- data/test/dummy/config/environments/production.rb +80 -0
- data/test/dummy/config/environments/test.rb +36 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +5 -0
- data/test/dummy/config/initializers/secret_token.rb +12 -0
- data/test/dummy/config/initializers/session_store.rb +3 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +23 -0
- data/test/dummy/config/routes.rb +56 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/public/404.html +58 -0
- data/test/dummy/public/422.html +58 -0
- data/test/dummy/public/500.html +57 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/test_helper.rb +15 -0
- data/vendor/assets/javascripts/backbonemvc.js +542 -0
- metadata +142 -0
@@ -0,0 +1,57 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>We're sorry, but something went wrong (500)</title>
|
5
|
+
<style>
|
6
|
+
body {
|
7
|
+
background-color: #EFEFEF;
|
8
|
+
color: #2E2F30;
|
9
|
+
text-align: center;
|
10
|
+
font-family: arial, sans-serif;
|
11
|
+
}
|
12
|
+
|
13
|
+
div.dialog {
|
14
|
+
width: 25em;
|
15
|
+
margin: 4em auto 0 auto;
|
16
|
+
border: 1px solid #CCC;
|
17
|
+
border-right-color: #999;
|
18
|
+
border-left-color: #999;
|
19
|
+
border-bottom-color: #BBB;
|
20
|
+
border-top: #B00100 solid 4px;
|
21
|
+
border-top-left-radius: 9px;
|
22
|
+
border-top-right-radius: 9px;
|
23
|
+
background-color: white;
|
24
|
+
padding: 7px 4em 0 4em;
|
25
|
+
}
|
26
|
+
|
27
|
+
h1 {
|
28
|
+
font-size: 100%;
|
29
|
+
color: #730E15;
|
30
|
+
line-height: 1.5em;
|
31
|
+
}
|
32
|
+
|
33
|
+
body > p {
|
34
|
+
width: 33em;
|
35
|
+
margin: 0 auto 1em;
|
36
|
+
padding: 1em 0;
|
37
|
+
background-color: #F7F7F7;
|
38
|
+
border: 1px solid #CCC;
|
39
|
+
border-right-color: #999;
|
40
|
+
border-bottom-color: #999;
|
41
|
+
border-bottom-left-radius: 4px;
|
42
|
+
border-bottom-right-radius: 4px;
|
43
|
+
border-top-color: #DADADA;
|
44
|
+
color: #666;
|
45
|
+
box-shadow:0 3px 8px rgba(50, 50, 50, 0.17);
|
46
|
+
}
|
47
|
+
</style>
|
48
|
+
</head>
|
49
|
+
|
50
|
+
<body>
|
51
|
+
<!-- This file lives in public/500.html -->
|
52
|
+
<div class="dialog">
|
53
|
+
<h1>We're sorry, but something went wrong.</h1>
|
54
|
+
</div>
|
55
|
+
<p>If you are the application owner check the logs for more information.</p>
|
56
|
+
</body>
|
57
|
+
</html>
|
File without changes
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# Configure Rails Environment
|
2
|
+
ENV["RAILS_ENV"] = "test"
|
3
|
+
|
4
|
+
require File.expand_path("../dummy/config/environment.rb", __FILE__)
|
5
|
+
require "rails/test_help"
|
6
|
+
|
7
|
+
Rails.backtrace_cleaner.remove_silencers!
|
8
|
+
|
9
|
+
# Load support files
|
10
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
|
11
|
+
|
12
|
+
# Load fixtures from the engine
|
13
|
+
if ActiveSupport::TestCase.method_defined?(:fixture_path=)
|
14
|
+
ActiveSupport::TestCase.fixture_path = File.expand_path("../fixtures", __FILE__)
|
15
|
+
end
|
@@ -0,0 +1,542 @@
|
|
1
|
+
//BackboneMVC 1.0
|
2
|
+
|
3
|
+
//Copyright 2012 Changsi An
|
4
|
+
|
5
|
+
//This file is part of Backbone-MVC.
|
6
|
+
//
|
7
|
+
//Backbone-MVC is free software: you can redistribute it and/or modify
|
8
|
+
//it under the terms of the GNU Lesser General Public License as published by
|
9
|
+
//the Free Software Foundation, either version 3 of the License, or
|
10
|
+
//(at your option) any later version.
|
11
|
+
//
|
12
|
+
//Backbone-MVC is distributed in the hope that it will be useful,
|
13
|
+
//but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
//GNU Lesser General Public License for more details.
|
16
|
+
//
|
17
|
+
//You should have received a copy of the GNU Lesser General Public License
|
18
|
+
//along with Backbone-MVC. If not, see <http://www.gnu.org/licenses/>.
|
19
|
+
//------------------------------------------------------------------------------
|
20
|
+
//Quick Start
|
21
|
+
//
|
22
|
+
//This software requires Backbone.js and Underscore.js to work correctly.
|
23
|
+
//
|
24
|
+
//Usage:
|
25
|
+
//To create a controller, use :
|
26
|
+
|
27
|
+
//BackboneMVC.Controller.extend({
|
28
|
+
// name:'controller1', //mandatory field
|
29
|
+
//
|
30
|
+
// //defined once, will be invoked before each action method
|
31
|
+
// beforeFilter:function () {
|
32
|
+
// },
|
33
|
+
//
|
34
|
+
// //defined once, will be invoked after each action method
|
35
|
+
// afterRender:function () {
|
36
|
+
// },
|
37
|
+
//
|
38
|
+
// //used with secure methods, expect true/false or Deferred Object.
|
39
|
+
// checkSession:function () {
|
40
|
+
// },
|
41
|
+
//
|
42
|
+
// //action method
|
43
|
+
// action1:function () {
|
44
|
+
// this._privateMethod("Hello");
|
45
|
+
// },
|
46
|
+
//
|
47
|
+
// //secure method, checkSession method will be invoked first
|
48
|
+
// user_action2:function () {
|
49
|
+
// },
|
50
|
+
//
|
51
|
+
// //a private method starts with _
|
52
|
+
// _privateMethod:function (message) {
|
53
|
+
// alert(message);
|
54
|
+
// }
|
55
|
+
//})
|
56
|
+
|
57
|
+
|
58
|
+
//------------------------------------------------------------------------------
|
59
|
+
(function () {
|
60
|
+
'use strict';
|
61
|
+
var PRODUCT_NAME = 'BackboneMVC';
|
62
|
+
|
63
|
+
//check prerequisites
|
64
|
+
if (typeof Backbone === 'undefined' || typeof _ === 'undefined') {
|
65
|
+
return;
|
66
|
+
}
|
67
|
+
|
68
|
+
/**
|
69
|
+
* @namesapce BackboneMVC
|
70
|
+
*/
|
71
|
+
var BackboneMVC = window[PRODUCT_NAME] = {};
|
72
|
+
|
73
|
+
/**
|
74
|
+
* This is the base prototype of the Controller classes.
|
75
|
+
* The inheriting classes only expand the prototype so the trouble of handling
|
76
|
+
* private constructor is saved.
|
77
|
+
* It makes sense that each controller is a singleton. The cases that a
|
78
|
+
* controller's state need to be shared across the application are more than the
|
79
|
+
* cases that the states need to be kept independently. It also helps the user
|
80
|
+
* logic shares the same controller state as the one the Router uses.
|
81
|
+
* However, if independent states are vital, one can extend a controller with
|
82
|
+
* empty members or define their method statelessly.
|
83
|
+
* @type {Class}
|
84
|
+
*/
|
85
|
+
var ControllerSingleton = (function () {
|
86
|
+
function BaseController() {
|
87
|
+
this._created = (new Date()).getTime(); //this can be useful for development
|
88
|
+
}
|
89
|
+
|
90
|
+
_.extend(BaseController.prototype, {
|
91
|
+
_created:null
|
92
|
+
});
|
93
|
+
|
94
|
+
BaseController.extend = function (properties) {
|
95
|
+
var instance;
|
96
|
+
var klass = function Controller() {
|
97
|
+
if (instance !== undefined) { //try to simulate Singleton
|
98
|
+
return instance;
|
99
|
+
}
|
100
|
+
BaseController.apply(this, arguments);
|
101
|
+
|
102
|
+
//'initialize()' method works as explicit constructor, if it is defined,
|
103
|
+
// then run it
|
104
|
+
if (this.initialize !== undefined) {
|
105
|
+
this.initialize.apply(this, arguments);
|
106
|
+
}
|
107
|
+
|
108
|
+
instance = this;
|
109
|
+
return instance;
|
110
|
+
};
|
111
|
+
|
112
|
+
klass.prototype = new BaseController();
|
113
|
+
_.extend(klass.prototype, properties);
|
114
|
+
|
115
|
+
klass.prototype.constructor = klass;
|
116
|
+
klass.prototype.classId = _.uniqueId('controller_');
|
117
|
+
|
118
|
+
return klass;
|
119
|
+
};
|
120
|
+
return BaseController;
|
121
|
+
}());
|
122
|
+
|
123
|
+
_.extend(BackboneMVC, {
|
124
|
+
/**
|
125
|
+
* A utility method used to create namespace object levels
|
126
|
+
* @param {String} namespaceString levels in namespaces
|
127
|
+
* @example "Mammalia.Cetacea.Delphinidae.Dolphin"
|
128
|
+
*/
|
129
|
+
namespace:function (namespaceString) {
|
130
|
+
var components = namespaceString.split('.');
|
131
|
+
var node = window;
|
132
|
+
for (var i = 0, l = components.length; i < l; i++) {
|
133
|
+
if (node[components[i]] === undefined) {
|
134
|
+
node[components[i]] = {};
|
135
|
+
}
|
136
|
+
node = node[components[i]];
|
137
|
+
}
|
138
|
+
},
|
139
|
+
|
140
|
+
/**
|
141
|
+
* Backbone MVC Controller class
|
142
|
+
* @type {Class} BackboneMVC.Controller
|
143
|
+
*/
|
144
|
+
//some default implementations for the methods are listed here:
|
145
|
+
Controller:{
|
146
|
+
beforeFilter:function () {
|
147
|
+
return (new $.Deferred()).resolve();
|
148
|
+
},
|
149
|
+
|
150
|
+
afterRender:function () {
|
151
|
+
return (new $.Deferred()).resolve();
|
152
|
+
},
|
153
|
+
|
154
|
+
checkSession:function () {
|
155
|
+
//if not defined, then always succeed
|
156
|
+
return (new $.Deferred()).resolve(true);
|
157
|
+
},
|
158
|
+
|
159
|
+
'default':function () {
|
160
|
+
//TODO: this function will list all the actions of the controller
|
161
|
+
//intend to be overridden in most of the cases
|
162
|
+
return true;
|
163
|
+
}
|
164
|
+
},
|
165
|
+
|
166
|
+
/**
|
167
|
+
* This is the automatic Router class, it is an implementation of Backbone.Router.
|
168
|
+
* It must not be further customized, or the automatic routing feature cannot function.
|
169
|
+
* @class BackboneMVC.Router
|
170
|
+
*/
|
171
|
+
Router:(function(){
|
172
|
+
var _inherentRouterProperties = {
|
173
|
+
_history:[],
|
174
|
+
|
175
|
+
routes:{
|
176
|
+
"*components":'dispatch' // route everything to 'dispatch' method
|
177
|
+
},
|
178
|
+
|
179
|
+
dispatch:function (actionPath) {
|
180
|
+
var components = (actionPath || '').replace(/\/+$/g, '').split('/');
|
181
|
+
var controllerName;
|
182
|
+
|
183
|
+
//look for controllers
|
184
|
+
if (ControllersPool[components[0]]) {
|
185
|
+
controllerName = components[0];
|
186
|
+
} else if (typeof ControllersPool[camelCased(components[0])] !== 'undefined') {
|
187
|
+
controllerName = camelCased(components[0]);
|
188
|
+
} else if (typeof ControllersPool[underscored(components[0])] !== 'undefined') {
|
189
|
+
controllerName = underscored(components[0]);
|
190
|
+
}
|
191
|
+
|
192
|
+
//test if the controller exists, if not, return a deferred object and reject it.
|
193
|
+
if (typeof controllerName === 'undefined') {
|
194
|
+
return this['404'](); //no such controller, reject
|
195
|
+
}
|
196
|
+
|
197
|
+
var controller = new ControllersPool[controllerName]();
|
198
|
+
//if the action is omitted, it is 'default'.
|
199
|
+
var action = components.length > 1 ? components[1] : 'default';
|
200
|
+
|
201
|
+
if (typeof controller._actions[action] !== 'function') {
|
202
|
+
return this['404'](); //no such action, reject
|
203
|
+
}
|
204
|
+
|
205
|
+
//the URL components after the 2nd are passed to the action method
|
206
|
+
var _arguments = components.length > 2 ? _.rest(components, 2) : [];
|
207
|
+
|
208
|
+
addHistoryEntry(this, controllerName, action, _arguments);
|
209
|
+
return invokeAction(controllerName, action, _arguments);
|
210
|
+
},
|
211
|
+
|
212
|
+
'404':function () {
|
213
|
+
//do nothing, expect overriding
|
214
|
+
},
|
215
|
+
|
216
|
+
/**
|
217
|
+
* Return the last invoked action
|
218
|
+
* @return {object} the last action being invoked and it's parameters
|
219
|
+
*/
|
220
|
+
getLastAction:function () {
|
221
|
+
return _.last(this._history, 1)[0];
|
222
|
+
},
|
223
|
+
|
224
|
+
/**
|
225
|
+
* Make navigate() returns a deferred object
|
226
|
+
* @param fragment
|
227
|
+
* @param options may contain trigger and replace options.
|
228
|
+
* @return {*} Deferred
|
229
|
+
*/
|
230
|
+
navigate: function(fragment, options){
|
231
|
+
if (!options || options === true) {
|
232
|
+
options = {trigger: options};
|
233
|
+
}
|
234
|
+
var _options = _.extend({}, options);
|
235
|
+
_options.trigger = false; //too hard to port Backbone's mechanism without much refactory,
|
236
|
+
// but such logical flaw can be exploited. The goal is to not modify Backbone.js at all
|
237
|
+
|
238
|
+
Backbone.Router.prototype.navigate.call(this, fragment, _options);
|
239
|
+
if(options.trigger){
|
240
|
+
return this.dispatch(fragment);
|
241
|
+
}else{
|
242
|
+
return (new $.Deferred()).resolve();
|
243
|
+
}
|
244
|
+
}
|
245
|
+
};
|
246
|
+
|
247
|
+
function extend(properties){
|
248
|
+
var _routes = _.extend(properties.routes || {}, _inherentRouterProperties.routes );
|
249
|
+
return Backbone.Router.extend(_.extend(properties, _inherentRouterProperties, { routes: _routes }));
|
250
|
+
}
|
251
|
+
|
252
|
+
var RouterClass = Backbone.Router.extend(
|
253
|
+
_.extend({ extend: extend }, _inherentRouterProperties)
|
254
|
+
);
|
255
|
+
RouterClass.extend = extend;
|
256
|
+
return RouterClass;
|
257
|
+
})(),
|
258
|
+
|
259
|
+
/**
|
260
|
+
* An extension of BackboneMVC.Model, add events of 'read' and 'error' to
|
261
|
+
* a model, which will be triggered upon loading data from server.
|
262
|
+
*
|
263
|
+
* This class assumes the returned json packet contains both 'error' and 'data' fields
|
264
|
+
* as root properties, which is commonly seen in modern web service APIs. If you business
|
265
|
+
* logic cannot comply this standard. Then this model class might not fit.
|
266
|
+
* @class BackboneMVC.Model
|
267
|
+
* @example //TODO
|
268
|
+
*/
|
269
|
+
Model:{
|
270
|
+
extend:function (properties) {
|
271
|
+
properties = _.extend({
|
272
|
+
__fetchSuccessCallback:null,
|
273
|
+
__fetchErrorCallback:null,
|
274
|
+
|
275
|
+
fetch:function (options) {
|
276
|
+
options = options || {};
|
277
|
+
//wrap the success callback, so we get a chance of triggering 'read' event
|
278
|
+
//by taking over the '__fetchSuccessCallback()' defined in 'parse()'
|
279
|
+
var success = options.success;
|
280
|
+
options.success = function (model, resp) {
|
281
|
+
if (success) {
|
282
|
+
success(model, resp);
|
283
|
+
}
|
284
|
+
if (model.__fetchSuccessCallback) {
|
285
|
+
var tmp = model.__fetchSuccessCallback;
|
286
|
+
model.__fetchSuccessCallback = null; //remove the temporary method after use
|
287
|
+
tmp.apply(model);
|
288
|
+
}
|
289
|
+
};
|
290
|
+
//wrap the error callback, so we get a chance of triggering 'error' event
|
291
|
+
var error = options.error;
|
292
|
+
options.error = function (model, resp) {
|
293
|
+
if (error){
|
294
|
+
error(model, resp);
|
295
|
+
}
|
296
|
+
model.trigger('error', error);
|
297
|
+
};
|
298
|
+
Backbone.Model.prototype.fetch.apply(this, [options].concat(_.rest(arguments)));
|
299
|
+
},
|
300
|
+
|
301
|
+
/**
|
302
|
+
* Intercept the data return from server and see if there is any error.
|
303
|
+
* Overriding is discouraged.
|
304
|
+
* @param {object} response the returned and parsed json object
|
305
|
+
* @return {*}
|
306
|
+
*/
|
307
|
+
parse:function (response) {
|
308
|
+
this.__fetchSuccessCallback = null;
|
309
|
+
this.__fetchErrorCallback = null;
|
310
|
+
|
311
|
+
if (!response || response.error) {
|
312
|
+
//if response contains a non-null 'error' field, still trigger 'error' event
|
313
|
+
this.trigger('error', (response && response.error) || response);
|
314
|
+
return {};
|
315
|
+
}
|
316
|
+
this.__fetchSuccessCallback = function () {
|
317
|
+
this.trigger('read', response.data);
|
318
|
+
}.bind(this);
|
319
|
+
return response.data;
|
320
|
+
}
|
321
|
+
}, properties);
|
322
|
+
|
323
|
+
return Backbone.Model.extend(properties);
|
324
|
+
}
|
325
|
+
}
|
326
|
+
});
|
327
|
+
|
328
|
+
//_extendMethodGenerator is used to create a closure that can store class members(fields and members)
|
329
|
+
//in the ancestry, so as to provide a basis for the children controller to further derive
|
330
|
+
BackboneMVC.Controller.extend = _extendMethodGenerator(BackboneMVC.Controller, {});
|
331
|
+
|
332
|
+
//internal variables
|
333
|
+
var ControllersPool = {}; //hashmap, keeps a record of defined Controllers with their names as keys
|
334
|
+
|
335
|
+
//if a Controller class defines these actions, then they will not be treated as action methods
|
336
|
+
var systemActions = ['initialize', 'beforeFilter', 'afterRender', 'checkSession'];
|
337
|
+
|
338
|
+
//internal functions
|
339
|
+
/**
|
340
|
+
*
|
341
|
+
* @param {Class} klass the current klass object
|
342
|
+
* @param {object} _inheritedMethodsDefinition store all inherited methods from the ancestors(in closure only)
|
343
|
+
* @return {Function}
|
344
|
+
* @private
|
345
|
+
*/
|
346
|
+
function _extendMethodGenerator(klass, _inheritedMethodsDefinition) {
|
347
|
+
//create closure
|
348
|
+
return function (properties) {
|
349
|
+
var name = properties.name;
|
350
|
+
if (typeof name === 'undefined') {
|
351
|
+
throw '\'name\' property is mandatory ';
|
352
|
+
}
|
353
|
+
|
354
|
+
// also inherits the methods from ancestry
|
355
|
+
properties = _.extend({}, _inheritedMethodsDefinition, properties);
|
356
|
+
|
357
|
+
//special handling of method override in inheritance
|
358
|
+
var tmpControllerProperties = _.extend({}, BackboneMVC.Controller);
|
359
|
+
|
360
|
+
var actionMethods = {}, secureActions = {};
|
361
|
+
//try to pick out action methods
|
362
|
+
_.each(properties, function (value, propertyName) {
|
363
|
+
tmpControllerProperties[propertyName] = value; // transfer the property, which will be later
|
364
|
+
//filter the non-action methods
|
365
|
+
if (typeof value !== 'function' || propertyName[0] === '_' ||
|
366
|
+
_.indexOf(systemActions, propertyName) >= 0) {
|
367
|
+
return false;
|
368
|
+
}
|
369
|
+
|
370
|
+
actionMethods[propertyName] = value;
|
371
|
+
if (propertyName.match(/^user_/i)) { //special handling to secure methods
|
372
|
+
secureActions[propertyName] = value;
|
373
|
+
// even though secure methods start with 'user_', it's useful if they can be invoked without
|
374
|
+
// that prefix
|
375
|
+
var shortName = propertyName.replace(/^user_/i, '');
|
376
|
+
if (typeof properties[shortName] !== 'function') {
|
377
|
+
// if the shortname function is not defined separately, also account it for a secure method
|
378
|
+
secureActions[shortName] = value;
|
379
|
+
actionMethods[shortName] = value;
|
380
|
+
}
|
381
|
+
}
|
382
|
+
});
|
383
|
+
|
384
|
+
//_actions and _secureActions are only used to tag those two types of methods, the action methods
|
385
|
+
//are still with the controller
|
386
|
+
_.extend(tmpControllerProperties, actionMethods, {
|
387
|
+
_actions:actionMethods,
|
388
|
+
_secureActions:secureActions
|
389
|
+
});
|
390
|
+
//remove the extend method if there is one, so it doesn't stay in the property history
|
391
|
+
if ('extend' in tmpControllerProperties) {
|
392
|
+
delete tmpControllerProperties.extend;
|
393
|
+
}
|
394
|
+
//get around of singleton inheritance issue by using mixin
|
395
|
+
var _controllerClass = ControllerSingleton.extend(tmpControllerProperties);
|
396
|
+
//special handling for utility method of inheritance
|
397
|
+
_.extend(_controllerClass, {
|
398
|
+
extend:_extendMethodGenerator(_controllerClass, _.extend({}, _inheritedMethodsDefinition, properties))
|
399
|
+
});
|
400
|
+
|
401
|
+
//Register Controller
|
402
|
+
ControllersPool[name] = _controllerClass;
|
403
|
+
|
404
|
+
return _controllerClass;
|
405
|
+
};
|
406
|
+
}
|
407
|
+
|
408
|
+
function _d(a) {
|
409
|
+
console.log(a);
|
410
|
+
}
|
411
|
+
|
412
|
+
/**
|
413
|
+
* use duck-typing to check if an object is a Deferred Object.
|
414
|
+
* @param suspiciousObject
|
415
|
+
* @return {boolean}
|
416
|
+
*/
|
417
|
+
function isDeferred(suspiciousObject) {
|
418
|
+
//duck-typing
|
419
|
+
return _.isObject(suspiciousObject) && suspiciousObject.promise &&
|
420
|
+
typeof suspiciousObject.promise === 'function';
|
421
|
+
}
|
422
|
+
|
423
|
+
/**
|
424
|
+
* Convert a non-deferred object ot deferred object, and resolve or reject the deferred object based on the value
|
425
|
+
* of the non-deferred object.
|
426
|
+
* @param deferred
|
427
|
+
* @param result
|
428
|
+
* @return {object} a Deferred Object
|
429
|
+
*/
|
430
|
+
function assertDeferredByResult(deferred, result) {
|
431
|
+
if (typeof result === 'undefined') {
|
432
|
+
result = true;
|
433
|
+
}
|
434
|
+
return deferred[result ? 'resolve' : 'reject'](result);
|
435
|
+
}
|
436
|
+
|
437
|
+
/**
|
438
|
+
* convert to CamelCased form
|
439
|
+
* @param string the non-camel-cased form
|
440
|
+
* @return {string}
|
441
|
+
*/
|
442
|
+
function camelCased(string) {
|
443
|
+
if (typeof string !== 'string') {
|
444
|
+
return null;
|
445
|
+
}
|
446
|
+
string = string.replace(/\s{2,}/g, ' ');
|
447
|
+
|
448
|
+
return (_.map(string.split(' '), function (entry) {
|
449
|
+
return entry.replace(/(^|_)[a-z]/gi,function (match) {
|
450
|
+
return match.toUpperCase();
|
451
|
+
}).replace(/_/g, '');
|
452
|
+
})).join(' ');
|
453
|
+
|
454
|
+
}
|
455
|
+
|
456
|
+
/**
|
457
|
+
* convert to underscored form
|
458
|
+
* @param string the non-underscored form
|
459
|
+
* @return {string}
|
460
|
+
*/
|
461
|
+
function underscored(string) {
|
462
|
+
if (typeof string !== 'string') {
|
463
|
+
return null;
|
464
|
+
}
|
465
|
+
string = string.replace(/\s{2,}/g, ' ');
|
466
|
+
|
467
|
+
return (_.map(string.split(' '), function (entry) {
|
468
|
+
return entry.replace(/^[A-Z]/g, function (match) {
|
469
|
+
return match.toLowerCase();
|
470
|
+
})
|
471
|
+
.replace(/([a-z])([A-Z])/g, function ($, $1, $2) {
|
472
|
+
return $1 + '_' + $2.toLowerCase();
|
473
|
+
});
|
474
|
+
})).join(' ');
|
475
|
+
}
|
476
|
+
|
477
|
+
/**
|
478
|
+
* Invoke the action method under a controller, also takes care of event callbacks and session checking method
|
479
|
+
* on the call chain.
|
480
|
+
* @param controllerName the controller name
|
481
|
+
* @param {string} action action method name
|
482
|
+
* @param {Array} _arguments the parameters sent ot the action method
|
483
|
+
* @return {*}
|
484
|
+
*/
|
485
|
+
function invokeAction(controllerName, action, _arguments) {
|
486
|
+
var controller = new ControllersPool[controllerName]();
|
487
|
+
|
488
|
+
var hooksParameters = [action].concat(_arguments);
|
489
|
+
var deferred = $.Deferred();
|
490
|
+
//do beforeFilter
|
491
|
+
var result = controller.beforeFilter.apply(controller, hooksParameters);
|
492
|
+
if (isDeferred(result)) {
|
493
|
+
deferred = result;
|
494
|
+
} else {
|
495
|
+
assertDeferredByResult(deferred, result);
|
496
|
+
}
|
497
|
+
|
498
|
+
//check if secure method
|
499
|
+
if (typeof controller._secureActions[action] === 'function') {
|
500
|
+
//do session checking
|
501
|
+
deferred = deferred.pipe(function () {
|
502
|
+
var result = controller.checkSession.apply(controller, _arguments);
|
503
|
+
|
504
|
+
if (isDeferred(result)) {
|
505
|
+
return result;
|
506
|
+
} else {
|
507
|
+
return assertDeferredByResult(new $.Deferred(), result);
|
508
|
+
}
|
509
|
+
});
|
510
|
+
}
|
511
|
+
|
512
|
+
//invoke the action
|
513
|
+
deferred = deferred.pipe(function () {
|
514
|
+
var result = controller[action].apply(controller, _arguments);
|
515
|
+
|
516
|
+
if (isDeferred(result)) {
|
517
|
+
return result;
|
518
|
+
} else {
|
519
|
+
return assertDeferredByResult(new $.Deferred(), result);
|
520
|
+
}
|
521
|
+
});
|
522
|
+
|
523
|
+
//do afterRender
|
524
|
+
deferred = deferred.pipe(function () {
|
525
|
+
var result = controller.afterRender.apply(controller, hooksParameters);
|
526
|
+
if (isDeferred(result)) {
|
527
|
+
return result;
|
528
|
+
} else {
|
529
|
+
return assertDeferredByResult(new $.Deferred(), result);
|
530
|
+
}
|
531
|
+
});
|
532
|
+
|
533
|
+
return deferred;
|
534
|
+
}
|
535
|
+
|
536
|
+
function addHistoryEntry(router, controller_name, action, _arguments) {
|
537
|
+
if (router._history.length > 888) {
|
538
|
+
router._history = _.last(router._history, 888);
|
539
|
+
}
|
540
|
+
router._history.push([controller_name, action, _arguments]);
|
541
|
+
}
|
542
|
+
})();
|