js_stack 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -0
  3. data/README.md +117 -25
  4. data/lib/js_stack/version.rb +1 -1
  5. data/vendor/assets/javascripts/js_stack/{backbone/backbone-1.0.0.js → base/backbone/1.0.0.js} +0 -0
  6. data/vendor/assets/javascripts/js_stack/{backbone/backbone-1.1.0.js → base/backbone/1.1.0.js} +0 -0
  7. data/vendor/assets/javascripts/js_stack/base/backbone.js +1 -0
  8. data/vendor/assets/javascripts/js_stack/{hamlcoffee.js → base/hamlcoffee.js} +0 -0
  9. data/vendor/assets/javascripts/js_stack/{jsroutes.js → base/jsroutes.js} +0 -0
  10. data/vendor/assets/javascripts/js_stack/{marionette/marionette-1.1.0.js → base/marionette/1.1.0.js} +0 -0
  11. data/vendor/assets/javascripts/js_stack/{marionette/marionette-1.4.1.js → base/marionette/1.4.1.js} +0 -0
  12. data/vendor/assets/javascripts/js_stack/{marionette/marionette-1.5.1.js → base/marionette/1.5.1.js} +0 -0
  13. data/vendor/assets/javascripts/js_stack/base/marionette/1.6.2.js +2555 -0
  14. data/vendor/assets/javascripts/js_stack/base/marionette.js +1 -0
  15. data/vendor/assets/javascripts/js_stack/{underscore.js → base/underscore/1.5.2.js} +0 -0
  16. data/vendor/assets/javascripts/js_stack/base/underscore.js +1 -0
  17. data/vendor/assets/javascripts/js_stack/base.js +5 -0
  18. data/vendor/assets/javascripts/js_stack/{backbone.associations/backbone.associations-0.5.1.js → plugins/backbone/associations/0.5.1.js} +0 -0
  19. data/vendor/assets/javascripts/js_stack/{backbone.associations/backbone.associations-0.5.4.js → plugins/backbone/associations/0.5.4.js} +0 -0
  20. data/vendor/assets/javascripts/js_stack/{backbone.associations/backbone.associations-0.5.5.js → plugins/backbone/associations/0.5.5.js} +0 -0
  21. data/vendor/assets/javascripts/js_stack/{backbone.deepmodel/backbone.deepmodel-0.10.4.js → plugins/backbone/deepmodel/0.10.4.js} +0 -0
  22. data/vendor/assets/javascripts/js_stack/{backbone.pageable/backbone.pageable-1.3.2.js → plugins/backbone/pageable/1.3.2.js} +0 -0
  23. data/vendor/assets/javascripts/js_stack/{backbone.pageable/backbone.pageable-1.4.5.js → plugins/backbone/pageable/1.4.5.js} +0 -0
  24. data/vendor/assets/javascripts/js_stack/{backbone.stickit/backbone.stickit-0.6.3.js → plugins/backbone/stickit/0.6.3.js} +0 -0
  25. data/vendor/assets/javascripts/js_stack/{backbone.stickit/backbone.stickit-0.7.0.js → plugins/backbone/stickit/0.7.0.js} +0 -0
  26. data/vendor/assets/javascripts/js_stack/{backbone.validation/backbone.validation-0.8.1.js → plugins/backbone/validation/0.8.1.js} +0 -0
  27. data/vendor/assets/javascripts/js_stack/{backbone.validation/backbone.validation-0.9.1.js → plugins/backbone/validation/0.9.1.js} +0 -0
  28. data/vendor/assets/javascripts/js_stack/{backbone.virtualcollection/backbone.virtualcollection-0.4.5.js → plugins/backbone/virtualcollection/0.4.5.js} +0 -0
  29. data/vendor/assets/javascripts/js_stack/{backbone.virtualcollection/backbone.virtualcollection-0.4.8.js → plugins/backbone/virtualcollection/0.4.8.js} +0 -0
  30. data/vendor/assets/javascripts/js_stack/plugins/backbone.associations.js +1 -0
  31. data/vendor/assets/javascripts/js_stack/plugins/backbone.deepmodel.js +1 -0
  32. data/vendor/assets/javascripts/js_stack/plugins/backbone.pageable.js +1 -0
  33. data/vendor/assets/javascripts/js_stack/plugins/backbone.stickit.js +1 -0
  34. data/vendor/assets/javascripts/js_stack/plugins/backbone.validation.js +1 -0
  35. data/vendor/assets/javascripts/js_stack/plugins/backbone.virtualcollection.js +1 -0
  36. data/vendor/assets/javascripts/js_stack/{moment.js → plugins/moment.js} +0 -0
  37. data/vendor/assets/javascripts/js_stack/{underscore.inflections/underscore.inflections-0.2.1.js → plugins/underscore/inflections/0.2.1.js} +0 -0
  38. data/vendor/assets/javascripts/js_stack/{underscore.string/underscore.string-2.3.2.js → plugins/underscore/string/2.3.2.js} +0 -0
  39. data/vendor/assets/javascripts/js_stack/plugins/underscore.inflections.js +1 -0
  40. data/vendor/assets/javascripts/js_stack/plugins/underscore.string.js +1 -0
  41. data/vendor/assets/javascripts/js_stack/plugins.js +7 -0
  42. data/vendor/assets/javascripts/js_stack.js +2 -12
  43. metadata +39 -35
  44. data/vendor/assets/javascripts/js_stack/backbone.associations.js +0 -1
  45. data/vendor/assets/javascripts/js_stack/backbone.deepmodel.js +0 -1
  46. data/vendor/assets/javascripts/js_stack/backbone.js +0 -1
  47. data/vendor/assets/javascripts/js_stack/backbone.pageable.js +0 -1
  48. data/vendor/assets/javascripts/js_stack/backbone.stickit.js +0 -1
  49. data/vendor/assets/javascripts/js_stack/backbone.validation.js +0 -1
  50. data/vendor/assets/javascripts/js_stack/backbone.virtualcollection.js +0 -1
  51. data/vendor/assets/javascripts/js_stack/marionette.js +0 -1
  52. data/vendor/assets/javascripts/js_stack/underscore.inflections.js +0 -1
  53. data/vendor/assets/javascripts/js_stack/underscore.string.js +0 -1
@@ -0,0 +1,2555 @@
1
+ // MarionetteJS (Backbone.Marionette)
2
+ // ----------------------------------
3
+ // v1.6.2
4
+ //
5
+ // Copyright (c)2014 Derick Bailey, Muted Solutions, LLC.
6
+ // Distributed under MIT license
7
+ //
8
+ // http://marionettejs.com
9
+
10
+
11
+
12
+ /*!
13
+ * Includes BabySitter
14
+ * https://github.com/marionettejs/backbone.babysitter/
15
+ *
16
+ * Includes Wreqr
17
+ * https://github.com/marionettejs/backbone.wreqr/
18
+ */
19
+
20
+ // Backbone.BabySitter
21
+ // -------------------
22
+ // v0.0.6
23
+ //
24
+ // Copyright (c)2013 Derick Bailey, Muted Solutions, LLC.
25
+ // Distributed under MIT license
26
+ //
27
+ // http://github.com/babysitterjs/backbone.babysitter
28
+
29
+ // Backbone.ChildViewContainer
30
+ // ---------------------------
31
+ //
32
+ // Provide a container to store, retrieve and
33
+ // shut down child views.
34
+
35
+ Backbone.ChildViewContainer = (function(Backbone, _){
36
+
37
+ // Container Constructor
38
+ // ---------------------
39
+
40
+ var Container = function(views){
41
+ this._views = {};
42
+ this._indexByModel = {};
43
+ this._indexByCustom = {};
44
+ this._updateLength();
45
+
46
+ _.each(views, this.add, this);
47
+ };
48
+
49
+ // Container Methods
50
+ // -----------------
51
+
52
+ _.extend(Container.prototype, {
53
+
54
+ // Add a view to this container. Stores the view
55
+ // by `cid` and makes it searchable by the model
56
+ // cid (and model itself). Optionally specify
57
+ // a custom key to store an retrieve the view.
58
+ add: function(view, customIndex){
59
+ var viewCid = view.cid;
60
+
61
+ // store the view
62
+ this._views[viewCid] = view;
63
+
64
+ // index it by model
65
+ if (view.model){
66
+ this._indexByModel[view.model.cid] = viewCid;
67
+ }
68
+
69
+ // index by custom
70
+ if (customIndex){
71
+ this._indexByCustom[customIndex] = viewCid;
72
+ }
73
+
74
+ this._updateLength();
75
+ },
76
+
77
+ // Find a view by the model that was attached to
78
+ // it. Uses the model's `cid` to find it.
79
+ findByModel: function(model){
80
+ return this.findByModelCid(model.cid);
81
+ },
82
+
83
+ // Find a view by the `cid` of the model that was attached to
84
+ // it. Uses the model's `cid` to find the view `cid` and
85
+ // retrieve the view using it.
86
+ findByModelCid: function(modelCid){
87
+ var viewCid = this._indexByModel[modelCid];
88
+ return this.findByCid(viewCid);
89
+ },
90
+
91
+ // Find a view by a custom indexer.
92
+ findByCustom: function(index){
93
+ var viewCid = this._indexByCustom[index];
94
+ return this.findByCid(viewCid);
95
+ },
96
+
97
+ // Find by index. This is not guaranteed to be a
98
+ // stable index.
99
+ findByIndex: function(index){
100
+ return _.values(this._views)[index];
101
+ },
102
+
103
+ // retrieve a view by it's `cid` directly
104
+ findByCid: function(cid){
105
+ return this._views[cid];
106
+ },
107
+
108
+ // Remove a view
109
+ remove: function(view){
110
+ var viewCid = view.cid;
111
+
112
+ // delete model index
113
+ if (view.model){
114
+ delete this._indexByModel[view.model.cid];
115
+ }
116
+
117
+ // delete custom index
118
+ _.any(this._indexByCustom, function(cid, key) {
119
+ if (cid === viewCid) {
120
+ delete this._indexByCustom[key];
121
+ return true;
122
+ }
123
+ }, this);
124
+
125
+ // remove the view from the container
126
+ delete this._views[viewCid];
127
+
128
+ // update the length
129
+ this._updateLength();
130
+ },
131
+
132
+ // Call a method on every view in the container,
133
+ // passing parameters to the call method one at a
134
+ // time, like `function.call`.
135
+ call: function(method){
136
+ this.apply(method, _.tail(arguments));
137
+ },
138
+
139
+ // Apply a method on every view in the container,
140
+ // passing parameters to the call method one at a
141
+ // time, like `function.apply`.
142
+ apply: function(method, args){
143
+ _.each(this._views, function(view){
144
+ if (_.isFunction(view[method])){
145
+ view[method].apply(view, args || []);
146
+ }
147
+ });
148
+ },
149
+
150
+ // Update the `.length` attribute on this container
151
+ _updateLength: function(){
152
+ this.length = _.size(this._views);
153
+ }
154
+ });
155
+
156
+ // Borrowing this code from Backbone.Collection:
157
+ // http://backbonejs.org/docs/backbone.html#section-106
158
+ //
159
+ // Mix in methods from Underscore, for iteration, and other
160
+ // collection related features.
161
+ var methods = ['forEach', 'each', 'map', 'find', 'detect', 'filter',
162
+ 'select', 'reject', 'every', 'all', 'some', 'any', 'include',
163
+ 'contains', 'invoke', 'toArray', 'first', 'initial', 'rest',
164
+ 'last', 'without', 'isEmpty', 'pluck'];
165
+
166
+ _.each(methods, function(method) {
167
+ Container.prototype[method] = function() {
168
+ var views = _.values(this._views);
169
+ var args = [views].concat(_.toArray(arguments));
170
+ return _[method].apply(_, args);
171
+ };
172
+ });
173
+
174
+ // return the public API
175
+ return Container;
176
+ })(Backbone, _);
177
+
178
+ // Backbone.Wreqr (Backbone.Marionette)
179
+ // ----------------------------------
180
+ // v0.2.0
181
+ //
182
+ // Copyright (c)2013 Derick Bailey, Muted Solutions, LLC.
183
+ // Distributed under MIT license
184
+ //
185
+ // http://github.com/marionettejs/backbone.wreqr
186
+
187
+
188
+ Backbone.Wreqr = (function(Backbone, Marionette, _){
189
+ "use strict";
190
+ var Wreqr = {};
191
+
192
+ // Handlers
193
+ // --------
194
+ // A registry of functions to call, given a name
195
+
196
+ Wreqr.Handlers = (function(Backbone, _){
197
+ "use strict";
198
+
199
+ // Constructor
200
+ // -----------
201
+
202
+ var Handlers = function(options){
203
+ this.options = options;
204
+ this._wreqrHandlers = {};
205
+
206
+ if (_.isFunction(this.initialize)){
207
+ this.initialize(options);
208
+ }
209
+ };
210
+
211
+ Handlers.extend = Backbone.Model.extend;
212
+
213
+ // Instance Members
214
+ // ----------------
215
+
216
+ _.extend(Handlers.prototype, Backbone.Events, {
217
+
218
+ // Add multiple handlers using an object literal configuration
219
+ setHandlers: function(handlers){
220
+ _.each(handlers, function(handler, name){
221
+ var context = null;
222
+
223
+ if (_.isObject(handler) && !_.isFunction(handler)){
224
+ context = handler.context;
225
+ handler = handler.callback;
226
+ }
227
+
228
+ this.setHandler(name, handler, context);
229
+ }, this);
230
+ },
231
+
232
+ // Add a handler for the given name, with an
233
+ // optional context to run the handler within
234
+ setHandler: function(name, handler, context){
235
+ var config = {
236
+ callback: handler,
237
+ context: context
238
+ };
239
+
240
+ this._wreqrHandlers[name] = config;
241
+
242
+ this.trigger("handler:add", name, handler, context);
243
+ },
244
+
245
+ // Determine whether or not a handler is registered
246
+ hasHandler: function(name){
247
+ return !! this._wreqrHandlers[name];
248
+ },
249
+
250
+ // Get the currently registered handler for
251
+ // the specified name. Throws an exception if
252
+ // no handler is found.
253
+ getHandler: function(name){
254
+ var config = this._wreqrHandlers[name];
255
+
256
+ if (!config){
257
+ throw new Error("Handler not found for '" + name + "'");
258
+ }
259
+
260
+ return function(){
261
+ var args = Array.prototype.slice.apply(arguments);
262
+ return config.callback.apply(config.context, args);
263
+ };
264
+ },
265
+
266
+ // Remove a handler for the specified name
267
+ removeHandler: function(name){
268
+ delete this._wreqrHandlers[name];
269
+ },
270
+
271
+ // Remove all handlers from this registry
272
+ removeAllHandlers: function(){
273
+ this._wreqrHandlers = {};
274
+ }
275
+ });
276
+
277
+ return Handlers;
278
+ })(Backbone, _);
279
+
280
+ // Wreqr.CommandStorage
281
+ // --------------------
282
+ //
283
+ // Store and retrieve commands for execution.
284
+ Wreqr.CommandStorage = (function(){
285
+ "use strict";
286
+
287
+ // Constructor function
288
+ var CommandStorage = function(options){
289
+ this.options = options;
290
+ this._commands = {};
291
+
292
+ if (_.isFunction(this.initialize)){
293
+ this.initialize(options);
294
+ }
295
+ };
296
+
297
+ // Instance methods
298
+ _.extend(CommandStorage.prototype, Backbone.Events, {
299
+
300
+ // Get an object literal by command name, that contains
301
+ // the `commandName` and the `instances` of all commands
302
+ // represented as an array of arguments to process
303
+ getCommands: function(commandName){
304
+ var commands = this._commands[commandName];
305
+
306
+ // we don't have it, so add it
307
+ if (!commands){
308
+
309
+ // build the configuration
310
+ commands = {
311
+ command: commandName,
312
+ instances: []
313
+ };
314
+
315
+ // store it
316
+ this._commands[commandName] = commands;
317
+ }
318
+
319
+ return commands;
320
+ },
321
+
322
+ // Add a command by name, to the storage and store the
323
+ // args for the command
324
+ addCommand: function(commandName, args){
325
+ var command = this.getCommands(commandName);
326
+ command.instances.push(args);
327
+ },
328
+
329
+ // Clear all commands for the given `commandName`
330
+ clearCommands: function(commandName){
331
+ var command = this.getCommands(commandName);
332
+ command.instances = [];
333
+ }
334
+ });
335
+
336
+ return CommandStorage;
337
+ })();
338
+
339
+ // Wreqr.Commands
340
+ // --------------
341
+ //
342
+ // A simple command pattern implementation. Register a command
343
+ // handler and execute it.
344
+ Wreqr.Commands = (function(Wreqr){
345
+ "use strict";
346
+
347
+ return Wreqr.Handlers.extend({
348
+ // default storage type
349
+ storageType: Wreqr.CommandStorage,
350
+
351
+ constructor: function(options){
352
+ this.options = options || {};
353
+
354
+ this._initializeStorage(this.options);
355
+ this.on("handler:add", this._executeCommands, this);
356
+
357
+ var args = Array.prototype.slice.call(arguments);
358
+ Wreqr.Handlers.prototype.constructor.apply(this, args);
359
+ },
360
+
361
+ // Execute a named command with the supplied args
362
+ execute: function(name, args){
363
+ name = arguments[0];
364
+ args = Array.prototype.slice.call(arguments, 1);
365
+
366
+ if (this.hasHandler(name)){
367
+ this.getHandler(name).apply(this, args);
368
+ } else {
369
+ this.storage.addCommand(name, args);
370
+ }
371
+
372
+ },
373
+
374
+ // Internal method to handle bulk execution of stored commands
375
+ _executeCommands: function(name, handler, context){
376
+ var command = this.storage.getCommands(name);
377
+
378
+ // loop through and execute all the stored command instances
379
+ _.each(command.instances, function(args){
380
+ handler.apply(context, args);
381
+ });
382
+
383
+ this.storage.clearCommands(name);
384
+ },
385
+
386
+ // Internal method to initialize storage either from the type's
387
+ // `storageType` or the instance `options.storageType`.
388
+ _initializeStorage: function(options){
389
+ var storage;
390
+
391
+ var StorageType = options.storageType || this.storageType;
392
+ if (_.isFunction(StorageType)){
393
+ storage = new StorageType();
394
+ } else {
395
+ storage = StorageType;
396
+ }
397
+
398
+ this.storage = storage;
399
+ }
400
+ });
401
+
402
+ })(Wreqr);
403
+
404
+ // Wreqr.RequestResponse
405
+ // ---------------------
406
+ //
407
+ // A simple request/response implementation. Register a
408
+ // request handler, and return a response from it
409
+ Wreqr.RequestResponse = (function(Wreqr){
410
+ "use strict";
411
+
412
+ return Wreqr.Handlers.extend({
413
+ request: function(){
414
+ var name = arguments[0];
415
+ var args = Array.prototype.slice.call(arguments, 1);
416
+
417
+ return this.getHandler(name).apply(this, args);
418
+ }
419
+ });
420
+
421
+ })(Wreqr);
422
+
423
+ // Event Aggregator
424
+ // ----------------
425
+ // A pub-sub object that can be used to decouple various parts
426
+ // of an application through event-driven architecture.
427
+
428
+ Wreqr.EventAggregator = (function(Backbone, _){
429
+ "use strict";
430
+ var EA = function(){};
431
+
432
+ // Copy the `extend` function used by Backbone's classes
433
+ EA.extend = Backbone.Model.extend;
434
+
435
+ // Copy the basic Backbone.Events on to the event aggregator
436
+ _.extend(EA.prototype, Backbone.Events);
437
+
438
+ return EA;
439
+ })(Backbone, _);
440
+
441
+
442
+ return Wreqr;
443
+ })(Backbone, Backbone.Marionette, _);
444
+
445
+ var Marionette = (function(global, Backbone, _){
446
+ "use strict";
447
+
448
+ // Define and export the Marionette namespace
449
+ var Marionette = {};
450
+ Backbone.Marionette = Marionette;
451
+
452
+ // Get the DOM manipulator for later use
453
+ Marionette.$ = Backbone.$;
454
+
455
+ // Helpers
456
+ // -------
457
+
458
+ // For slicing `arguments` in functions
459
+ var protoSlice = Array.prototype.slice;
460
+ function slice(args) {
461
+ return protoSlice.call(args);
462
+ }
463
+
464
+ function throwError(message, name) {
465
+ var error = new Error(message);
466
+ error.name = name || 'Error';
467
+ throw error;
468
+ }
469
+
470
+ // Marionette.extend
471
+ // -----------------
472
+
473
+ // Borrow the Backbone `extend` method so we can use it as needed
474
+ Marionette.extend = Backbone.Model.extend;
475
+
476
+ // Marionette.getOption
477
+ // --------------------
478
+
479
+ // Retrieve an object, function or other value from a target
480
+ // object or its `options`, with `options` taking precedence.
481
+ Marionette.getOption = function(target, optionName){
482
+ if (!target || !optionName){ return; }
483
+ var value;
484
+
485
+ if (target.options && (optionName in target.options) && (target.options[optionName] !== undefined)){
486
+ value = target.options[optionName];
487
+ } else {
488
+ value = target[optionName];
489
+ }
490
+
491
+ return value;
492
+ };
493
+
494
+ // Marionette.normalizeMethods
495
+ // ----------------------
496
+
497
+ // Pass in a mapping of events => functions or function names
498
+ // and return a mapping of events => functions
499
+ Marionette.normalizeMethods = function(hash) {
500
+ var normalizedHash = {}, method;
501
+ _.each(hash, function(fn, name) {
502
+ method = fn;
503
+ if (!_.isFunction(method)) {
504
+ method = this[method];
505
+ }
506
+ if (!method) {
507
+ return;
508
+ }
509
+ normalizedHash[name] = method;
510
+ }, this);
511
+ return normalizedHash;
512
+ };
513
+ // Trigger an event and/or a corresponding method name. Examples:
514
+ //
515
+ // `this.triggerMethod("foo")` will trigger the "foo" event and
516
+ // call the "onFoo" method.
517
+ //
518
+ // `this.triggerMethod("foo:bar")` will trigger the "foo:bar" event and
519
+ // call the "onFooBar" method.
520
+ Marionette.triggerMethod = (function(){
521
+
522
+ // split the event name on the ":"
523
+ var splitter = /(^|:)(\w)/gi;
524
+
525
+ // take the event section ("section1:section2:section3")
526
+ // and turn it in to uppercase name
527
+ function getEventName(match, prefix, eventName) {
528
+ return eventName.toUpperCase();
529
+ }
530
+
531
+ // actual triggerMethod implementation
532
+ var triggerMethod = function(event) {
533
+ // get the method name from the event name
534
+ var methodName = 'on' + event.replace(splitter, getEventName);
535
+ var method = this[methodName];
536
+
537
+ // trigger the event, if a trigger method exists
538
+ if(_.isFunction(this.trigger)) {
539
+ this.trigger.apply(this, arguments);
540
+ }
541
+
542
+ // call the onMethodName if it exists
543
+ if (_.isFunction(method)) {
544
+ // pass all arguments, except the event name
545
+ return method.apply(this, _.tail(arguments));
546
+ }
547
+ };
548
+
549
+ return triggerMethod;
550
+ })();
551
+
552
+ // DOMRefresh
553
+ // ----------
554
+ //
555
+ // Monitor a view's state, and after it has been rendered and shown
556
+ // in the DOM, trigger a "dom:refresh" event every time it is
557
+ // re-rendered.
558
+
559
+ Marionette.MonitorDOMRefresh = (function(documentElement){
560
+ // track when the view has been shown in the DOM,
561
+ // using a Marionette.Region (or by other means of triggering "show")
562
+ function handleShow(view){
563
+ view._isShown = true;
564
+ triggerDOMRefresh(view);
565
+ }
566
+
567
+ // track when the view has been rendered
568
+ function handleRender(view){
569
+ view._isRendered = true;
570
+ triggerDOMRefresh(view);
571
+ }
572
+
573
+ // Trigger the "dom:refresh" event and corresponding "onDomRefresh" method
574
+ function triggerDOMRefresh(view){
575
+ if (view._isShown && view._isRendered && isInDOM(view)){
576
+ if (_.isFunction(view.triggerMethod)){
577
+ view.triggerMethod("dom:refresh");
578
+ }
579
+ }
580
+ }
581
+
582
+ function isInDOM(view) {
583
+ return documentElement.contains(view.el);
584
+ }
585
+
586
+ // Export public API
587
+ return function(view){
588
+ view.listenTo(view, "show", function(){
589
+ handleShow(view);
590
+ });
591
+
592
+ view.listenTo(view, "render", function(){
593
+ handleRender(view);
594
+ });
595
+ };
596
+ })(document.documentElement);
597
+
598
+
599
+ // Marionette.bindEntityEvents & unbindEntityEvents
600
+ // ---------------------------
601
+ //
602
+ // These methods are used to bind/unbind a backbone "entity" (collection/model)
603
+ // to methods on a target object.
604
+ //
605
+ // The first parameter, `target`, must have a `listenTo` method from the
606
+ // EventBinder object.
607
+ //
608
+ // The second parameter is the entity (Backbone.Model or Backbone.Collection)
609
+ // to bind the events from.
610
+ //
611
+ // The third parameter is a hash of { "event:name": "eventHandler" }
612
+ // configuration. Multiple handlers can be separated by a space. A
613
+ // function can be supplied instead of a string handler name.
614
+
615
+ (function(Marionette){
616
+ "use strict";
617
+
618
+ // Bind the event to handlers specified as a string of
619
+ // handler names on the target object
620
+ function bindFromStrings(target, entity, evt, methods){
621
+ var methodNames = methods.split(/\s+/);
622
+
623
+ _.each(methodNames,function(methodName) {
624
+
625
+ var method = target[methodName];
626
+ if(!method) {
627
+ throwError("Method '"+ methodName +"' was configured as an event handler, but does not exist.");
628
+ }
629
+
630
+ target.listenTo(entity, evt, method, target);
631
+ });
632
+ }
633
+
634
+ // Bind the event to a supplied callback function
635
+ function bindToFunction(target, entity, evt, method){
636
+ target.listenTo(entity, evt, method, target);
637
+ }
638
+
639
+ // Bind the event to handlers specified as a string of
640
+ // handler names on the target object
641
+ function unbindFromStrings(target, entity, evt, methods){
642
+ var methodNames = methods.split(/\s+/);
643
+
644
+ _.each(methodNames,function(methodName) {
645
+ var method = target[methodName];
646
+ target.stopListening(entity, evt, method, target);
647
+ });
648
+ }
649
+
650
+ // Bind the event to a supplied callback function
651
+ function unbindToFunction(target, entity, evt, method){
652
+ target.stopListening(entity, evt, method, target);
653
+ }
654
+
655
+
656
+ // generic looping function
657
+ function iterateEvents(target, entity, bindings, functionCallback, stringCallback){
658
+ if (!entity || !bindings) { return; }
659
+
660
+ // allow the bindings to be a function
661
+ if (_.isFunction(bindings)){
662
+ bindings = bindings.call(target);
663
+ }
664
+
665
+ // iterate the bindings and bind them
666
+ _.each(bindings, function(methods, evt){
667
+
668
+ // allow for a function as the handler,
669
+ // or a list of event names as a string
670
+ if (_.isFunction(methods)){
671
+ functionCallback(target, entity, evt, methods);
672
+ } else {
673
+ stringCallback(target, entity, evt, methods);
674
+ }
675
+
676
+ });
677
+ }
678
+
679
+ // Export Public API
680
+ Marionette.bindEntityEvents = function(target, entity, bindings){
681
+ iterateEvents(target, entity, bindings, bindToFunction, bindFromStrings);
682
+ };
683
+
684
+ Marionette.unbindEntityEvents = function(target, entity, bindings){
685
+ iterateEvents(target, entity, bindings, unbindToFunction, unbindFromStrings);
686
+ };
687
+
688
+ })(Marionette);
689
+
690
+
691
+ // Callbacks
692
+ // ---------
693
+
694
+ // A simple way of managing a collection of callbacks
695
+ // and executing them at a later point in time, using jQuery's
696
+ // `Deferred` object.
697
+ Marionette.Callbacks = function(){
698
+ this._deferred = Marionette.$.Deferred();
699
+ this._callbacks = [];
700
+ };
701
+
702
+ _.extend(Marionette.Callbacks.prototype, {
703
+
704
+ // Add a callback to be executed. Callbacks added here are
705
+ // guaranteed to execute, even if they are added after the
706
+ // `run` method is called.
707
+ add: function(callback, contextOverride){
708
+ this._callbacks.push({cb: callback, ctx: contextOverride});
709
+
710
+ this._deferred.done(function(context, options){
711
+ if (contextOverride){ context = contextOverride; }
712
+ callback.call(context, options);
713
+ });
714
+ },
715
+
716
+ // Run all registered callbacks with the context specified.
717
+ // Additional callbacks can be added after this has been run
718
+ // and they will still be executed.
719
+ run: function(options, context){
720
+ this._deferred.resolve(context, options);
721
+ },
722
+
723
+ // Resets the list of callbacks to be run, allowing the same list
724
+ // to be run multiple times - whenever the `run` method is called.
725
+ reset: function(){
726
+ var callbacks = this._callbacks;
727
+ this._deferred = Marionette.$.Deferred();
728
+ this._callbacks = [];
729
+
730
+ _.each(callbacks, function(cb){
731
+ this.add(cb.cb, cb.ctx);
732
+ }, this);
733
+ }
734
+ });
735
+
736
+
737
+ // Marionette Controller
738
+ // ---------------------
739
+ //
740
+ // A multi-purpose object to use as a controller for
741
+ // modules and routers, and as a mediator for workflow
742
+ // and coordination of other objects, views, and more.
743
+ Marionette.Controller = function(options){
744
+ this.triggerMethod = Marionette.triggerMethod;
745
+ this.options = options || {};
746
+
747
+ if (_.isFunction(this.initialize)){
748
+ this.initialize(this.options);
749
+ }
750
+ };
751
+
752
+ Marionette.Controller.extend = Marionette.extend;
753
+
754
+ // Controller Methods
755
+ // --------------
756
+
757
+ // Ensure it can trigger events with Backbone.Events
758
+ _.extend(Marionette.Controller.prototype, Backbone.Events, {
759
+ close: function(){
760
+ this.stopListening();
761
+ this.triggerMethod("close");
762
+ this.unbind();
763
+ }
764
+ });
765
+
766
+ // Region
767
+ // ------
768
+ //
769
+ // Manage the visual regions of your composite application. See
770
+ // http://lostechies.com/derickbailey/2011/12/12/composite-js-apps-regions-and-region-managers/
771
+
772
+ Marionette.Region = function(options){
773
+ this.options = options || {};
774
+ this.el = Marionette.getOption(this, "el");
775
+
776
+ if (!this.el){
777
+ var err = new Error("An 'el' must be specified for a region.");
778
+ err.name = "NoElError";
779
+ throw err;
780
+ }
781
+
782
+ if (this.initialize){
783
+ var args = Array.prototype.slice.apply(arguments);
784
+ this.initialize.apply(this, args);
785
+ }
786
+ };
787
+
788
+
789
+ // Region Type methods
790
+ // -------------------
791
+
792
+ _.extend(Marionette.Region, {
793
+
794
+ // Build an instance of a region by passing in a configuration object
795
+ // and a default region type to use if none is specified in the config.
796
+ //
797
+ // The config object should either be a string as a jQuery DOM selector,
798
+ // a Region type directly, or an object literal that specifies both
799
+ // a selector and regionType:
800
+ //
801
+ // ```js
802
+ // {
803
+ // selector: "#foo",
804
+ // regionType: MyCustomRegion
805
+ // }
806
+ // ```
807
+ //
808
+ buildRegion: function(regionConfig, defaultRegionType){
809
+ var regionIsString = (typeof regionConfig === "string");
810
+ var regionSelectorIsString = (typeof regionConfig.selector === "string");
811
+ var regionTypeIsUndefined = (typeof regionConfig.regionType === "undefined");
812
+ var regionIsType = (typeof regionConfig === "function");
813
+
814
+ if (!regionIsType && !regionIsString && !regionSelectorIsString) {
815
+ throw new Error("Region must be specified as a Region type, a selector string or an object with selector property");
816
+ }
817
+
818
+ var selector, RegionType;
819
+
820
+ // get the selector for the region
821
+
822
+ if (regionIsString) {
823
+ selector = regionConfig;
824
+ }
825
+
826
+ if (regionConfig.selector) {
827
+ selector = regionConfig.selector;
828
+ delete regionConfig.selector;
829
+ }
830
+
831
+ // get the type for the region
832
+
833
+ if (regionIsType){
834
+ RegionType = regionConfig;
835
+ }
836
+
837
+ if (!regionIsType && regionTypeIsUndefined) {
838
+ RegionType = defaultRegionType;
839
+ }
840
+
841
+ if (regionConfig.regionType) {
842
+ RegionType = regionConfig.regionType;
843
+ delete regionConfig.regionType;
844
+ }
845
+
846
+ if (regionIsString || regionIsType) {
847
+ regionConfig = {};
848
+ }
849
+
850
+ regionConfig.el = selector;
851
+
852
+ // build the region instance
853
+ var region = new RegionType(regionConfig);
854
+
855
+ // override the `getEl` function if we have a parentEl
856
+ // this must be overridden to ensure the selector is found
857
+ // on the first use of the region. if we try to assign the
858
+ // region's `el` to `parentEl.find(selector)` in the object
859
+ // literal to build the region, the element will not be
860
+ // guaranteed to be in the DOM already, and will cause problems
861
+ if (regionConfig.parentEl){
862
+ region.getEl = function(selector) {
863
+ var parentEl = regionConfig.parentEl;
864
+ if (_.isFunction(parentEl)){
865
+ parentEl = parentEl();
866
+ }
867
+ return parentEl.find(selector);
868
+ };
869
+ }
870
+
871
+ return region;
872
+ }
873
+
874
+ });
875
+
876
+ // Region Instance Methods
877
+ // -----------------------
878
+
879
+ _.extend(Marionette.Region.prototype, Backbone.Events, {
880
+
881
+ // Displays a backbone view instance inside of the region.
882
+ // Handles calling the `render` method for you. Reads content
883
+ // directly from the `el` attribute. Also calls an optional
884
+ // `onShow` and `close` method on your view, just after showing
885
+ // or just before closing the view, respectively.
886
+ show: function(view){
887
+ this.ensureEl();
888
+
889
+ var isViewClosed = view.isClosed || _.isUndefined(view.$el);
890
+ var isDifferentView = view !== this.currentView;
891
+
892
+ if (isDifferentView) {
893
+ this.close();
894
+ }
895
+
896
+ view.render();
897
+
898
+ if (isDifferentView || isViewClosed) {
899
+ this.open(view);
900
+ }
901
+
902
+ this.currentView = view;
903
+
904
+ Marionette.triggerMethod.call(this, "show", view);
905
+ Marionette.triggerMethod.call(view, "show");
906
+ },
907
+
908
+ ensureEl: function(){
909
+ if (!this.$el || this.$el.length === 0){
910
+ this.$el = this.getEl(this.el);
911
+ }
912
+ },
913
+
914
+ // Override this method to change how the region finds the
915
+ // DOM element that it manages. Return a jQuery selector object.
916
+ getEl: function(selector){
917
+ return Marionette.$(selector);
918
+ },
919
+
920
+ // Override this method to change how the new view is
921
+ // appended to the `$el` that the region is managing
922
+ open: function(view){
923
+ this.$el.empty().append(view.el);
924
+ },
925
+
926
+ // Close the current view, if there is one. If there is no
927
+ // current view, it does nothing and returns immediately.
928
+ close: function(){
929
+ var view = this.currentView;
930
+ if (!view || view.isClosed){ return; }
931
+
932
+ // call 'close' or 'remove', depending on which is found
933
+ if (view.close) { view.close(); }
934
+ else if (view.remove) { view.remove(); }
935
+
936
+ Marionette.triggerMethod.call(this, "close", view);
937
+
938
+ delete this.currentView;
939
+ },
940
+
941
+ // Attach an existing view to the region. This
942
+ // will not call `render` or `onShow` for the new view,
943
+ // and will not replace the current HTML for the `el`
944
+ // of the region.
945
+ attachView: function(view){
946
+ this.currentView = view;
947
+ },
948
+
949
+ // Reset the region by closing any existing view and
950
+ // clearing out the cached `$el`. The next time a view
951
+ // is shown via this region, the region will re-query the
952
+ // DOM for the region's `el`.
953
+ reset: function(){
954
+ this.close();
955
+ delete this.$el;
956
+ }
957
+ });
958
+
959
+ // Copy the `extend` function used by Backbone's classes
960
+ Marionette.Region.extend = Marionette.extend;
961
+
962
+ // Marionette.RegionManager
963
+ // ------------------------
964
+ //
965
+ // Manage one or more related `Marionette.Region` objects.
966
+ Marionette.RegionManager = (function(Marionette){
967
+
968
+ var RegionManager = Marionette.Controller.extend({
969
+ constructor: function(options){
970
+ this._regions = {};
971
+ Marionette.Controller.prototype.constructor.call(this, options);
972
+ },
973
+
974
+ // Add multiple regions using an object literal, where
975
+ // each key becomes the region name, and each value is
976
+ // the region definition.
977
+ addRegions: function(regionDefinitions, defaults){
978
+ var regions = {};
979
+
980
+ _.each(regionDefinitions, function(definition, name){
981
+ if (typeof definition === "string"){
982
+ definition = { selector: definition };
983
+ }
984
+
985
+ if (definition.selector){
986
+ definition = _.defaults({}, definition, defaults);
987
+ }
988
+
989
+ var region = this.addRegion(name, definition);
990
+ regions[name] = region;
991
+ }, this);
992
+
993
+ return regions;
994
+ },
995
+
996
+ // Add an individual region to the region manager,
997
+ // and return the region instance
998
+ addRegion: function(name, definition){
999
+ var region;
1000
+
1001
+ var isObject = _.isObject(definition);
1002
+ var isString = _.isString(definition);
1003
+ var hasSelector = !!definition.selector;
1004
+
1005
+ if (isString || (isObject && hasSelector)){
1006
+ region = Marionette.Region.buildRegion(definition, Marionette.Region);
1007
+ } else if (_.isFunction(definition)){
1008
+ region = Marionette.Region.buildRegion(definition, Marionette.Region);
1009
+ } else {
1010
+ region = definition;
1011
+ }
1012
+
1013
+ this._store(name, region);
1014
+ this.triggerMethod("region:add", name, region);
1015
+ return region;
1016
+ },
1017
+
1018
+ // Get a region by name
1019
+ get: function(name){
1020
+ return this._regions[name];
1021
+ },
1022
+
1023
+ // Remove a region by name
1024
+ removeRegion: function(name){
1025
+ var region = this._regions[name];
1026
+ this._remove(name, region);
1027
+ },
1028
+
1029
+ // Close all regions in the region manager, and
1030
+ // remove them
1031
+ removeRegions: function(){
1032
+ _.each(this._regions, function(region, name){
1033
+ this._remove(name, region);
1034
+ }, this);
1035
+ },
1036
+
1037
+ // Close all regions in the region manager, but
1038
+ // leave them attached
1039
+ closeRegions: function(){
1040
+ _.each(this._regions, function(region, name){
1041
+ region.close();
1042
+ }, this);
1043
+ },
1044
+
1045
+ // Close all regions and shut down the region
1046
+ // manager entirely
1047
+ close: function(){
1048
+ this.removeRegions();
1049
+ var args = Array.prototype.slice.call(arguments);
1050
+ Marionette.Controller.prototype.close.apply(this, args);
1051
+ },
1052
+
1053
+ // internal method to store regions
1054
+ _store: function(name, region){
1055
+ this._regions[name] = region;
1056
+ this._setLength();
1057
+ },
1058
+
1059
+ // internal method to remove a region
1060
+ _remove: function(name, region){
1061
+ region.close();
1062
+ delete this._regions[name];
1063
+ this._setLength();
1064
+ this.triggerMethod("region:remove", name, region);
1065
+ },
1066
+
1067
+ // set the number of regions current held
1068
+ _setLength: function(){
1069
+ this.length = _.size(this._regions);
1070
+ }
1071
+
1072
+ });
1073
+
1074
+ // Borrowing this code from Backbone.Collection:
1075
+ // http://backbonejs.org/docs/backbone.html#section-106
1076
+ //
1077
+ // Mix in methods from Underscore, for iteration, and other
1078
+ // collection related features.
1079
+ var methods = ['forEach', 'each', 'map', 'find', 'detect', 'filter',
1080
+ 'select', 'reject', 'every', 'all', 'some', 'any', 'include',
1081
+ 'contains', 'invoke', 'toArray', 'first', 'initial', 'rest',
1082
+ 'last', 'without', 'isEmpty', 'pluck'];
1083
+
1084
+ _.each(methods, function(method) {
1085
+ RegionManager.prototype[method] = function() {
1086
+ var regions = _.values(this._regions);
1087
+ var args = [regions].concat(_.toArray(arguments));
1088
+ return _[method].apply(_, args);
1089
+ };
1090
+ });
1091
+
1092
+ return RegionManager;
1093
+ })(Marionette);
1094
+
1095
+
1096
+ // Template Cache
1097
+ // --------------
1098
+
1099
+ // Manage templates stored in `<script>` blocks,
1100
+ // caching them for faster access.
1101
+ Marionette.TemplateCache = function(templateId){
1102
+ this.templateId = templateId;
1103
+ };
1104
+
1105
+ // TemplateCache object-level methods. Manage the template
1106
+ // caches from these method calls instead of creating
1107
+ // your own TemplateCache instances
1108
+ _.extend(Marionette.TemplateCache, {
1109
+ templateCaches: {},
1110
+
1111
+ // Get the specified template by id. Either
1112
+ // retrieves the cached version, or loads it
1113
+ // from the DOM.
1114
+ get: function(templateId){
1115
+ var cachedTemplate = this.templateCaches[templateId];
1116
+
1117
+ if (!cachedTemplate){
1118
+ cachedTemplate = new Marionette.TemplateCache(templateId);
1119
+ this.templateCaches[templateId] = cachedTemplate;
1120
+ }
1121
+
1122
+ return cachedTemplate.load();
1123
+ },
1124
+
1125
+ // Clear templates from the cache. If no arguments
1126
+ // are specified, clears all templates:
1127
+ // `clear()`
1128
+ //
1129
+ // If arguments are specified, clears each of the
1130
+ // specified templates from the cache:
1131
+ // `clear("#t1", "#t2", "...")`
1132
+ clear: function(){
1133
+ var i;
1134
+ var args = slice(arguments);
1135
+ var length = args.length;
1136
+
1137
+ if (length > 0){
1138
+ for(i=0; i<length; i++){
1139
+ delete this.templateCaches[args[i]];
1140
+ }
1141
+ } else {
1142
+ this.templateCaches = {};
1143
+ }
1144
+ }
1145
+ });
1146
+
1147
+ // TemplateCache instance methods, allowing each
1148
+ // template cache object to manage its own state
1149
+ // and know whether or not it has been loaded
1150
+ _.extend(Marionette.TemplateCache.prototype, {
1151
+
1152
+ // Internal method to load the template
1153
+ load: function(){
1154
+ // Guard clause to prevent loading this template more than once
1155
+ if (this.compiledTemplate){
1156
+ return this.compiledTemplate;
1157
+ }
1158
+
1159
+ // Load the template and compile it
1160
+ var template = this.loadTemplate(this.templateId);
1161
+ this.compiledTemplate = this.compileTemplate(template);
1162
+
1163
+ return this.compiledTemplate;
1164
+ },
1165
+
1166
+ // Load a template from the DOM, by default. Override
1167
+ // this method to provide your own template retrieval
1168
+ // For asynchronous loading with AMD/RequireJS, consider
1169
+ // using a template-loader plugin as described here:
1170
+ // https://github.com/marionettejs/backbone.marionette/wiki/Using-marionette-with-requirejs
1171
+ loadTemplate: function(templateId){
1172
+ var template = Marionette.$(templateId).html();
1173
+
1174
+ if (!template || template.length === 0){
1175
+ throwError("Could not find template: '" + templateId + "'", "NoTemplateError");
1176
+ }
1177
+
1178
+ return template;
1179
+ },
1180
+
1181
+ // Pre-compile the template before caching it. Override
1182
+ // this method if you do not need to pre-compile a template
1183
+ // (JST / RequireJS for example) or if you want to change
1184
+ // the template engine used (Handebars, etc).
1185
+ compileTemplate: function(rawTemplate){
1186
+ return _.template(rawTemplate);
1187
+ }
1188
+ });
1189
+
1190
+
1191
+ // Renderer
1192
+ // --------
1193
+
1194
+ // Render a template with data by passing in the template
1195
+ // selector and the data to render.
1196
+ Marionette.Renderer = {
1197
+
1198
+ // Render a template with data. The `template` parameter is
1199
+ // passed to the `TemplateCache` object to retrieve the
1200
+ // template function. Override this method to provide your own
1201
+ // custom rendering and template handling for all of Marionette.
1202
+ render: function(template, data){
1203
+
1204
+ if (!template) {
1205
+ var error = new Error("Cannot render the template since it's false, null or undefined.");
1206
+ error.name = "TemplateNotFoundError";
1207
+ throw error;
1208
+ }
1209
+
1210
+ var templateFunc;
1211
+ if (typeof template === "function"){
1212
+ templateFunc = template;
1213
+ } else {
1214
+ templateFunc = Marionette.TemplateCache.get(template);
1215
+ }
1216
+
1217
+ return templateFunc(data);
1218
+ }
1219
+ };
1220
+
1221
+
1222
+
1223
+ // Marionette.View
1224
+ // ---------------
1225
+
1226
+ // The core view type that other Marionette views extend from.
1227
+ Marionette.View = Backbone.View.extend({
1228
+
1229
+ constructor: function(options){
1230
+ _.bindAll(this, "render");
1231
+
1232
+ var args = Array.prototype.slice.apply(arguments);
1233
+
1234
+ // this exposes view options to the view initializer
1235
+ // this is a backfill since backbone removed the assignment
1236
+ // of this.options
1237
+ // at some point however this may be removed
1238
+ this.options = _.extend({}, _.result(this, 'options'), _.isFunction(options) ? options.call(this) : options);
1239
+
1240
+ // parses out the @ui DSL for events
1241
+ this.events = this.normalizeUIKeys(_.result(this, 'events'));
1242
+ Backbone.View.prototype.constructor.apply(this, args);
1243
+
1244
+ Marionette.MonitorDOMRefresh(this);
1245
+ this.listenTo(this, "show", this.onShowCalled, this);
1246
+ },
1247
+
1248
+ // import the "triggerMethod" to trigger events with corresponding
1249
+ // methods if the method exists
1250
+ triggerMethod: Marionette.triggerMethod,
1251
+
1252
+ // Imports the "normalizeMethods" to transform hashes of
1253
+ // events=>function references/names to a hash of events=>function references
1254
+ normalizeMethods: Marionette.normalizeMethods,
1255
+
1256
+ // Get the template for this view
1257
+ // instance. You can set a `template` attribute in the view
1258
+ // definition or pass a `template: "whatever"` parameter in
1259
+ // to the constructor options.
1260
+ getTemplate: function(){
1261
+ return Marionette.getOption(this, "template");
1262
+ },
1263
+
1264
+ // Mix in template helper methods. Looks for a
1265
+ // `templateHelpers` attribute, which can either be an
1266
+ // object literal, or a function that returns an object
1267
+ // literal. All methods and attributes from this object
1268
+ // are copies to the object passed in.
1269
+ mixinTemplateHelpers: function(target){
1270
+ target = target || {};
1271
+ var templateHelpers = Marionette.getOption(this, "templateHelpers");
1272
+ if (_.isFunction(templateHelpers)){
1273
+ templateHelpers = templateHelpers.call(this);
1274
+ }
1275
+ return _.extend(target, templateHelpers);
1276
+ },
1277
+
1278
+ // allows for the use of the @ui. syntax within
1279
+ // a given key for triggers and events
1280
+ // swaps the @ui with the associated selector
1281
+ normalizeUIKeys: function(hash) {
1282
+ if (typeof(hash) === "undefined") {
1283
+ return;
1284
+ }
1285
+
1286
+ _.each(_.keys(hash), function(v) {
1287
+ var split = v.split("@ui.");
1288
+ if (split.length === 2) {
1289
+ hash[split[0]+this.ui[split[1]]] = hash[v];
1290
+ delete hash[v];
1291
+ }
1292
+ }, this);
1293
+
1294
+ return hash;
1295
+ },
1296
+
1297
+ // Configure `triggers` to forward DOM events to view
1298
+ // events. `triggers: {"click .foo": "do:foo"}`
1299
+ configureTriggers: function(){
1300
+ if (!this.triggers) { return; }
1301
+
1302
+ var triggerEvents = {};
1303
+
1304
+ // Allow `triggers` to be configured as a function
1305
+ var triggers = this.normalizeUIKeys(_.result(this, "triggers"));
1306
+
1307
+ // Configure the triggers, prevent default
1308
+ // action and stop propagation of DOM events
1309
+ _.each(triggers, function(value, key){
1310
+
1311
+ var hasOptions = _.isObject(value);
1312
+ var eventName = hasOptions ? value.event : value;
1313
+
1314
+ // build the event handler function for the DOM event
1315
+ triggerEvents[key] = function(e){
1316
+
1317
+ // stop the event in its tracks
1318
+ if (e) {
1319
+ var prevent = e.preventDefault;
1320
+ var stop = e.stopPropagation;
1321
+
1322
+ var shouldPrevent = hasOptions ? value.preventDefault : prevent;
1323
+ var shouldStop = hasOptions ? value.stopPropagation : stop;
1324
+
1325
+ if (shouldPrevent && prevent) { prevent.apply(e); }
1326
+ if (shouldStop && stop) { stop.apply(e); }
1327
+ }
1328
+
1329
+ // build the args for the event
1330
+ var args = {
1331
+ view: this,
1332
+ model: this.model,
1333
+ collection: this.collection
1334
+ };
1335
+
1336
+ // trigger the event
1337
+ this.triggerMethod(eventName, args);
1338
+ };
1339
+
1340
+ }, this);
1341
+
1342
+ return triggerEvents;
1343
+ },
1344
+
1345
+ // Overriding Backbone.View's delegateEvents to handle
1346
+ // the `triggers`, `modelEvents`, and `collectionEvents` configuration
1347
+ delegateEvents: function(events){
1348
+ this._delegateDOMEvents(events);
1349
+ Marionette.bindEntityEvents(this, this.model, Marionette.getOption(this, "modelEvents"));
1350
+ Marionette.bindEntityEvents(this, this.collection, Marionette.getOption(this, "collectionEvents"));
1351
+ },
1352
+
1353
+ // internal method to delegate DOM events and triggers
1354
+ _delegateDOMEvents: function(events){
1355
+ events = events || this.events;
1356
+ if (_.isFunction(events)){ events = events.call(this); }
1357
+
1358
+ var combinedEvents = {};
1359
+ var triggers = this.configureTriggers();
1360
+ _.extend(combinedEvents, events, triggers);
1361
+
1362
+ Backbone.View.prototype.delegateEvents.call(this, combinedEvents);
1363
+ },
1364
+
1365
+ // Overriding Backbone.View's undelegateEvents to handle unbinding
1366
+ // the `triggers`, `modelEvents`, and `collectionEvents` config
1367
+ undelegateEvents: function(){
1368
+ var args = Array.prototype.slice.call(arguments);
1369
+ Backbone.View.prototype.undelegateEvents.apply(this, args);
1370
+
1371
+ Marionette.unbindEntityEvents(this, this.model, Marionette.getOption(this, "modelEvents"));
1372
+ Marionette.unbindEntityEvents(this, this.collection, Marionette.getOption(this, "collectionEvents"));
1373
+ },
1374
+
1375
+ // Internal method, handles the `show` event.
1376
+ onShowCalled: function(){},
1377
+
1378
+ // Default `close` implementation, for removing a view from the
1379
+ // DOM and unbinding it. Regions will call this method
1380
+ // for you. You can specify an `onClose` method in your view to
1381
+ // add custom code that is called after the view is closed.
1382
+ close: function(){
1383
+ if (this.isClosed) { return; }
1384
+
1385
+ // allow the close to be stopped by returning `false`
1386
+ // from the `onBeforeClose` method
1387
+ var shouldClose = this.triggerMethod("before:close");
1388
+ if (shouldClose === false){
1389
+ return;
1390
+ }
1391
+
1392
+ // mark as closed before doing the actual close, to
1393
+ // prevent infinite loops within "close" event handlers
1394
+ // that are trying to close other views
1395
+ this.isClosed = true;
1396
+ this.triggerMethod("close");
1397
+
1398
+ // unbind UI elements
1399
+ this.unbindUIElements();
1400
+
1401
+ // remove the view from the DOM
1402
+ this.remove();
1403
+ },
1404
+
1405
+ // This method binds the elements specified in the "ui" hash inside the view's code with
1406
+ // the associated jQuery selectors.
1407
+ bindUIElements: function(){
1408
+ if (!this.ui) { return; }
1409
+
1410
+ // store the ui hash in _uiBindings so they can be reset later
1411
+ // and so re-rendering the view will be able to find the bindings
1412
+ if (!this._uiBindings){
1413
+ this._uiBindings = this.ui;
1414
+ }
1415
+
1416
+ // get the bindings result, as a function or otherwise
1417
+ var bindings = _.result(this, "_uiBindings");
1418
+
1419
+ // empty the ui so we don't have anything to start with
1420
+ this.ui = {};
1421
+
1422
+ // bind each of the selectors
1423
+ _.each(_.keys(bindings), function(key) {
1424
+ var selector = bindings[key];
1425
+ this.ui[key] = this.$(selector);
1426
+ }, this);
1427
+ },
1428
+
1429
+ // This method unbinds the elements specified in the "ui" hash
1430
+ unbindUIElements: function(){
1431
+ if (!this.ui || !this._uiBindings){ return; }
1432
+
1433
+ // delete all of the existing ui bindings
1434
+ _.each(this.ui, function($el, name){
1435
+ delete this.ui[name];
1436
+ }, this);
1437
+
1438
+ // reset the ui element to the original bindings configuration
1439
+ this.ui = this._uiBindings;
1440
+ delete this._uiBindings;
1441
+ }
1442
+ });
1443
+
1444
+ // Item View
1445
+ // ---------
1446
+
1447
+ // A single item view implementation that contains code for rendering
1448
+ // with underscore.js templates, serializing the view's model or collection,
1449
+ // and calling several methods on extended views, such as `onRender`.
1450
+ Marionette.ItemView = Marionette.View.extend({
1451
+
1452
+ // Setting up the inheritance chain which allows changes to
1453
+ // Marionette.View.prototype.constructor which allows overriding
1454
+ constructor: function(){
1455
+ Marionette.View.prototype.constructor.apply(this, slice(arguments));
1456
+ },
1457
+
1458
+ // Serialize the model or collection for the view. If a model is
1459
+ // found, `.toJSON()` is called. If a collection is found, `.toJSON()`
1460
+ // is also called, but is used to populate an `items` array in the
1461
+ // resulting data. If both are found, defaults to the model.
1462
+ // You can override the `serializeData` method in your own view
1463
+ // definition, to provide custom serialization for your view's data.
1464
+ serializeData: function(){
1465
+ var data = {};
1466
+
1467
+ if (this.model) {
1468
+ data = this.model.toJSON();
1469
+ }
1470
+ else if (this.collection) {
1471
+ data = { items: this.collection.toJSON() };
1472
+ }
1473
+
1474
+ return data;
1475
+ },
1476
+
1477
+ // Render the view, defaulting to underscore.js templates.
1478
+ // You can override this in your view definition to provide
1479
+ // a very specific rendering for your view. In general, though,
1480
+ // you should override the `Marionette.Renderer` object to
1481
+ // change how Marionette renders views.
1482
+ render: function(){
1483
+ this.isClosed = false;
1484
+
1485
+ this.triggerMethod("before:render", this);
1486
+ this.triggerMethod("item:before:render", this);
1487
+
1488
+ var data = this.serializeData();
1489
+ data = this.mixinTemplateHelpers(data);
1490
+
1491
+ var template = this.getTemplate();
1492
+ var html = Marionette.Renderer.render(template, data);
1493
+
1494
+ this.$el.html(html);
1495
+ this.bindUIElements();
1496
+
1497
+ this.triggerMethod("render", this);
1498
+ this.triggerMethod("item:rendered", this);
1499
+
1500
+ return this;
1501
+ },
1502
+
1503
+ // Override the default close event to add a few
1504
+ // more events that are triggered.
1505
+ close: function(){
1506
+ if (this.isClosed){ return; }
1507
+
1508
+ this.triggerMethod('item:before:close');
1509
+
1510
+ Marionette.View.prototype.close.apply(this, slice(arguments));
1511
+
1512
+ this.triggerMethod('item:closed');
1513
+ }
1514
+ });
1515
+
1516
+ // Collection View
1517
+ // ---------------
1518
+
1519
+ // A view that iterates over a Backbone.Collection
1520
+ // and renders an individual ItemView for each model.
1521
+ Marionette.CollectionView = Marionette.View.extend({
1522
+ // used as the prefix for item view events
1523
+ // that are forwarded through the collectionview
1524
+ itemViewEventPrefix: "itemview",
1525
+
1526
+ // constructor
1527
+ constructor: function(options){
1528
+ this._initChildViewStorage();
1529
+
1530
+ Marionette.View.prototype.constructor.apply(this, slice(arguments));
1531
+
1532
+ this._initialEvents();
1533
+ this.initRenderBuffer();
1534
+ },
1535
+
1536
+ // Instead of inserting elements one by one into the page,
1537
+ // it's much more performant to insert elements into a document
1538
+ // fragment and then insert that document fragment into the page
1539
+ initRenderBuffer: function() {
1540
+ this.elBuffer = document.createDocumentFragment();
1541
+ this._bufferedChildren = [];
1542
+ },
1543
+
1544
+ startBuffering: function() {
1545
+ this.initRenderBuffer();
1546
+ this.isBuffering = true;
1547
+ },
1548
+
1549
+ endBuffering: function() {
1550
+ this.isBuffering = false;
1551
+ this.appendBuffer(this, this.elBuffer);
1552
+ this._triggerShowBufferedChildren();
1553
+ this.initRenderBuffer();
1554
+ },
1555
+
1556
+ _triggerShowBufferedChildren: function () {
1557
+ if (this._isShown) {
1558
+ _.each(this._bufferedChildren, function (child) {
1559
+ Marionette.triggerMethod.call(child, "show");
1560
+ });
1561
+ this._bufferedChildren = [];
1562
+ }
1563
+ },
1564
+
1565
+ // Configured the initial events that the collection view
1566
+ // binds to.
1567
+ _initialEvents: function(){
1568
+ if (this.collection){
1569
+ this.listenTo(this.collection, "add", this.addChildView, this);
1570
+ this.listenTo(this.collection, "remove", this.removeItemView, this);
1571
+ this.listenTo(this.collection, "reset", this.render, this);
1572
+ }
1573
+ },
1574
+
1575
+ // Handle a child item added to the collection
1576
+ addChildView: function(item, collection, options){
1577
+ this.closeEmptyView();
1578
+ var ItemView = this.getItemView(item);
1579
+ var index = this.collection.indexOf(item);
1580
+ this.addItemView(item, ItemView, index);
1581
+ },
1582
+
1583
+ // Override from `Marionette.View` to guarantee the `onShow` method
1584
+ // of child views is called.
1585
+ onShowCalled: function(){
1586
+ this.children.each(function(child){
1587
+ Marionette.triggerMethod.call(child, "show");
1588
+ });
1589
+ },
1590
+
1591
+ // Internal method to trigger the before render callbacks
1592
+ // and events
1593
+ triggerBeforeRender: function(){
1594
+ this.triggerMethod("before:render", this);
1595
+ this.triggerMethod("collection:before:render", this);
1596
+ },
1597
+
1598
+ // Internal method to trigger the rendered callbacks and
1599
+ // events
1600
+ triggerRendered: function(){
1601
+ this.triggerMethod("render", this);
1602
+ this.triggerMethod("collection:rendered", this);
1603
+ },
1604
+
1605
+ // Render the collection of items. Override this method to
1606
+ // provide your own implementation of a render function for
1607
+ // the collection view.
1608
+ render: function(){
1609
+ this.isClosed = false;
1610
+ this.triggerBeforeRender();
1611
+ this._renderChildren();
1612
+ this.triggerRendered();
1613
+ return this;
1614
+ },
1615
+
1616
+ // Internal method. Separated so that CompositeView can have
1617
+ // more control over events being triggered, around the rendering
1618
+ // process
1619
+ _renderChildren: function(){
1620
+ this.startBuffering();
1621
+
1622
+ this.closeEmptyView();
1623
+ this.closeChildren();
1624
+
1625
+ if (!this.isEmpty(this.collection)) {
1626
+ this.showCollection();
1627
+ } else {
1628
+ this.showEmptyView();
1629
+ }
1630
+
1631
+ this.endBuffering();
1632
+ },
1633
+
1634
+ // Internal method to loop through each item in the
1635
+ // collection view and show it
1636
+ showCollection: function(){
1637
+ var ItemView;
1638
+ this.collection.each(function(item, index){
1639
+ ItemView = this.getItemView(item);
1640
+ this.addItemView(item, ItemView, index);
1641
+ }, this);
1642
+ },
1643
+
1644
+ // Internal method to show an empty view in place of
1645
+ // a collection of item views, when the collection is
1646
+ // empty
1647
+ showEmptyView: function(){
1648
+ var EmptyView = this.getEmptyView();
1649
+
1650
+ if (EmptyView && !this._showingEmptyView){
1651
+ this._showingEmptyView = true;
1652
+ var model = new Backbone.Model();
1653
+ this.addItemView(model, EmptyView, 0);
1654
+ }
1655
+ },
1656
+
1657
+ // Internal method to close an existing emptyView instance
1658
+ // if one exists. Called when a collection view has been
1659
+ // rendered empty, and then an item is added to the collection.
1660
+ closeEmptyView: function(){
1661
+ if (this._showingEmptyView){
1662
+ this.closeChildren();
1663
+ delete this._showingEmptyView;
1664
+ }
1665
+ },
1666
+
1667
+ // Retrieve the empty view type
1668
+ getEmptyView: function(){
1669
+ return Marionette.getOption(this, "emptyView");
1670
+ },
1671
+
1672
+ // Retrieve the itemView type, either from `this.options.itemView`
1673
+ // or from the `itemView` in the object definition. The "options"
1674
+ // takes precedence.
1675
+ getItemView: function(item){
1676
+ var itemView = Marionette.getOption(this, "itemView");
1677
+
1678
+ if (!itemView){
1679
+ throwError("An `itemView` must be specified", "NoItemViewError");
1680
+ }
1681
+
1682
+ return itemView;
1683
+ },
1684
+
1685
+ // Render the child item's view and add it to the
1686
+ // HTML for the collection view.
1687
+ addItemView: function(item, ItemView, index){
1688
+ // get the itemViewOptions if any were specified
1689
+ var itemViewOptions = Marionette.getOption(this, "itemViewOptions");
1690
+ if (_.isFunction(itemViewOptions)){
1691
+ itemViewOptions = itemViewOptions.call(this, item, index);
1692
+ }
1693
+
1694
+ // build the view
1695
+ var view = this.buildItemView(item, ItemView, itemViewOptions);
1696
+
1697
+ // set up the child view event forwarding
1698
+ this.addChildViewEventForwarding(view);
1699
+
1700
+ // this view is about to be added
1701
+ this.triggerMethod("before:item:added", view);
1702
+
1703
+ // Store the child view itself so we can properly
1704
+ // remove and/or close it later
1705
+ this.children.add(view);
1706
+
1707
+ // Render it and show it
1708
+ this.renderItemView(view, index);
1709
+
1710
+ // call the "show" method if the collection view
1711
+ // has already been shown
1712
+ if (this._isShown && !this.isBuffering){
1713
+ Marionette.triggerMethod.call(view, "show");
1714
+ }
1715
+
1716
+ // this view was added
1717
+ this.triggerMethod("after:item:added", view);
1718
+
1719
+ return view;
1720
+ },
1721
+
1722
+ // Set up the child view event forwarding. Uses an "itemview:"
1723
+ // prefix in front of all forwarded events.
1724
+ addChildViewEventForwarding: function(view){
1725
+ var prefix = Marionette.getOption(this, "itemViewEventPrefix");
1726
+
1727
+ // Forward all child item view events through the parent,
1728
+ // prepending "itemview:" to the event name
1729
+ this.listenTo(view, "all", function(){
1730
+ var args = slice(arguments);
1731
+ var rootEvent = args[0];
1732
+ var itemEvents = this.normalizeMethods(this.getItemEvents());
1733
+
1734
+ args[0] = prefix + ":" + rootEvent;
1735
+ args.splice(1, 0, view);
1736
+
1737
+ // call collectionView itemEvent if defined
1738
+ if (typeof itemEvents !== "undefined" && _.isFunction(itemEvents[rootEvent])) {
1739
+ itemEvents[rootEvent].apply(this, args);
1740
+ }
1741
+
1742
+ Marionette.triggerMethod.apply(this, args);
1743
+ }, this);
1744
+ },
1745
+
1746
+ // returns the value of itemEvents depending on if a function
1747
+ getItemEvents: function() {
1748
+ if (_.isFunction(this.itemEvents)) {
1749
+ return this.itemEvents.call(this);
1750
+ }
1751
+
1752
+ return this.itemEvents;
1753
+ },
1754
+
1755
+ // render the item view
1756
+ renderItemView: function(view, index) {
1757
+ view.render();
1758
+ this.appendHtml(this, view, index);
1759
+ },
1760
+
1761
+ // Build an `itemView` for every model in the collection.
1762
+ buildItemView: function(item, ItemViewType, itemViewOptions){
1763
+ var options = _.extend({model: item}, itemViewOptions);
1764
+ return new ItemViewType(options);
1765
+ },
1766
+
1767
+ // get the child view by item it holds, and remove it
1768
+ removeItemView: function(item){
1769
+ var view = this.children.findByModel(item);
1770
+ this.removeChildView(view);
1771
+ this.checkEmpty();
1772
+ },
1773
+
1774
+ // Remove the child view and close it
1775
+ removeChildView: function(view){
1776
+
1777
+ // shut down the child view properly,
1778
+ // including events that the collection has from it
1779
+ if (view){
1780
+ this.stopListening(view);
1781
+
1782
+ // call 'close' or 'remove', depending on which is found
1783
+ if (view.close) { view.close(); }
1784
+ else if (view.remove) { view.remove(); }
1785
+
1786
+ this.children.remove(view);
1787
+ }
1788
+
1789
+ this.triggerMethod("item:removed", view);
1790
+ },
1791
+
1792
+ // helper to check if the collection is empty
1793
+ isEmpty: function(collection){
1794
+ // check if we're empty now
1795
+ return !this.collection || this.collection.length === 0;
1796
+ },
1797
+
1798
+ // If empty, show the empty view
1799
+ checkEmpty: function (){
1800
+ if (this.isEmpty(this.collection)){
1801
+ this.showEmptyView();
1802
+ }
1803
+ },
1804
+
1805
+ // You might need to override this if you've overridden appendHtml
1806
+ appendBuffer: function(collectionView, buffer) {
1807
+ collectionView.$el.append(buffer);
1808
+ },
1809
+
1810
+ // Append the HTML to the collection's `el`.
1811
+ // Override this method to do something other
1812
+ // then `.append`.
1813
+ appendHtml: function(collectionView, itemView, index){
1814
+ if (collectionView.isBuffering) {
1815
+ // buffering happens on reset events and initial renders
1816
+ // in order to reduce the number of inserts into the
1817
+ // document, which are expensive.
1818
+ collectionView.elBuffer.appendChild(itemView.el);
1819
+ collectionView._bufferedChildren.push(itemView);
1820
+ }
1821
+ else {
1822
+ // If we've already rendered the main collection, just
1823
+ // append the new items directly into the element.
1824
+ collectionView.$el.append(itemView.el);
1825
+ }
1826
+ },
1827
+
1828
+ // Internal method to set up the `children` object for
1829
+ // storing all of the child views
1830
+ _initChildViewStorage: function(){
1831
+ this.children = new Backbone.ChildViewContainer();
1832
+ },
1833
+
1834
+ // Handle cleanup and other closing needs for
1835
+ // the collection of views.
1836
+ close: function(){
1837
+ if (this.isClosed){ return; }
1838
+
1839
+ this.triggerMethod("collection:before:close");
1840
+ this.closeChildren();
1841
+ this.triggerMethod("collection:closed");
1842
+
1843
+ Marionette.View.prototype.close.apply(this, slice(arguments));
1844
+ },
1845
+
1846
+ // Close the child views that this collection view
1847
+ // is holding on to, if any
1848
+ closeChildren: function(){
1849
+ this.children.each(function(child){
1850
+ this.removeChildView(child);
1851
+ }, this);
1852
+ this.checkEmpty();
1853
+ }
1854
+ });
1855
+
1856
+
1857
+ // Composite View
1858
+ // --------------
1859
+
1860
+ // Used for rendering a branch-leaf, hierarchical structure.
1861
+ // Extends directly from CollectionView and also renders an
1862
+ // an item view as `modelView`, for the top leaf
1863
+ Marionette.CompositeView = Marionette.CollectionView.extend({
1864
+
1865
+ // Setting up the inheritance chain which allows changes to
1866
+ // Marionette.CollectionView.prototype.constructor which allows overriding
1867
+ constructor: function(){
1868
+ Marionette.CollectionView.prototype.constructor.apply(this, slice(arguments));
1869
+ },
1870
+
1871
+ // Configured the initial events that the composite view
1872
+ // binds to. Override this method to prevent the initial
1873
+ // events, or to add your own initial events.
1874
+ _initialEvents: function(){
1875
+
1876
+ // Bind only after composite view in rendered to avoid adding child views
1877
+ // to unexisting itemViewContainer
1878
+ this.once('render', function () {
1879
+ if (this.collection){
1880
+ this.listenTo(this.collection, "add", this.addChildView, this);
1881
+ this.listenTo(this.collection, "remove", this.removeItemView, this);
1882
+ this.listenTo(this.collection, "reset", this._renderChildren, this);
1883
+ }
1884
+ });
1885
+
1886
+ },
1887
+
1888
+ // Retrieve the `itemView` to be used when rendering each of
1889
+ // the items in the collection. The default is to return
1890
+ // `this.itemView` or Marionette.CompositeView if no `itemView`
1891
+ // has been defined
1892
+ getItemView: function(item){
1893
+ var itemView = Marionette.getOption(this, "itemView") || this.constructor;
1894
+
1895
+ if (!itemView){
1896
+ throwError("An `itemView` must be specified", "NoItemViewError");
1897
+ }
1898
+
1899
+ return itemView;
1900
+ },
1901
+
1902
+ // Serialize the collection for the view.
1903
+ // You can override the `serializeData` method in your own view
1904
+ // definition, to provide custom serialization for your view's data.
1905
+ serializeData: function(){
1906
+ var data = {};
1907
+
1908
+ if (this.model){
1909
+ data = this.model.toJSON();
1910
+ }
1911
+
1912
+ return data;
1913
+ },
1914
+
1915
+ // Renders the model once, and the collection once. Calling
1916
+ // this again will tell the model's view to re-render itself
1917
+ // but the collection will not re-render.
1918
+ render: function(){
1919
+ this.isRendered = true;
1920
+ this.isClosed = false;
1921
+ this.resetItemViewContainer();
1922
+
1923
+ this.triggerBeforeRender();
1924
+ var html = this.renderModel();
1925
+ this.$el.html(html);
1926
+ // the ui bindings is done here and not at the end of render since they
1927
+ // will not be available until after the model is rendered, but should be
1928
+ // available before the collection is rendered.
1929
+ this.bindUIElements();
1930
+ this.triggerMethod("composite:model:rendered");
1931
+
1932
+ this._renderChildren();
1933
+
1934
+ this.triggerMethod("composite:rendered");
1935
+ this.triggerRendered();
1936
+ return this;
1937
+ },
1938
+
1939
+ _renderChildren: function(){
1940
+ if (this.isRendered){
1941
+ this.triggerMethod("composite:collection:before:render");
1942
+ Marionette.CollectionView.prototype._renderChildren.call(this);
1943
+ this.triggerMethod("composite:collection:rendered");
1944
+ }
1945
+ },
1946
+
1947
+ // Render an individual model, if we have one, as
1948
+ // part of a composite view (branch / leaf). For example:
1949
+ // a treeview.
1950
+ renderModel: function(){
1951
+ var data = {};
1952
+ data = this.serializeData();
1953
+ data = this.mixinTemplateHelpers(data);
1954
+
1955
+ var template = this.getTemplate();
1956
+ return Marionette.Renderer.render(template, data);
1957
+ },
1958
+
1959
+
1960
+ // You might need to override this if you've overridden appendHtml
1961
+ appendBuffer: function(compositeView, buffer) {
1962
+ var $container = this.getItemViewContainer(compositeView);
1963
+ $container.append(buffer);
1964
+ },
1965
+
1966
+ // Appends the `el` of itemView instances to the specified
1967
+ // `itemViewContainer` (a jQuery selector). Override this method to
1968
+ // provide custom logic of how the child item view instances have their
1969
+ // HTML appended to the composite view instance.
1970
+ appendHtml: function(compositeView, itemView, index){
1971
+ if (compositeView.isBuffering) {
1972
+ compositeView.elBuffer.appendChild(itemView.el);
1973
+ compositeView._bufferedChildren.push(itemView);
1974
+ }
1975
+ else {
1976
+ // If we've already rendered the main collection, just
1977
+ // append the new items directly into the element.
1978
+ var $container = this.getItemViewContainer(compositeView);
1979
+ $container.append(itemView.el);
1980
+ }
1981
+ },
1982
+
1983
+
1984
+ // Internal method to ensure an `$itemViewContainer` exists, for the
1985
+ // `appendHtml` method to use.
1986
+ getItemViewContainer: function(containerView){
1987
+ if ("$itemViewContainer" in containerView){
1988
+ return containerView.$itemViewContainer;
1989
+ }
1990
+
1991
+ var container;
1992
+ var itemViewContainer = Marionette.getOption(containerView, "itemViewContainer");
1993
+ if (itemViewContainer){
1994
+
1995
+ var selector = _.isFunction(itemViewContainer) ? itemViewContainer.call(this) : itemViewContainer;
1996
+ container = containerView.$(selector);
1997
+ if (container.length <= 0) {
1998
+ throwError("The specified `itemViewContainer` was not found: " + containerView.itemViewContainer, "ItemViewContainerMissingError");
1999
+ }
2000
+
2001
+ } else {
2002
+ container = containerView.$el;
2003
+ }
2004
+
2005
+ containerView.$itemViewContainer = container;
2006
+ return container;
2007
+ },
2008
+
2009
+ // Internal method to reset the `$itemViewContainer` on render
2010
+ resetItemViewContainer: function(){
2011
+ if (this.$itemViewContainer){
2012
+ delete this.$itemViewContainer;
2013
+ }
2014
+ }
2015
+ });
2016
+
2017
+
2018
+ // Layout
2019
+ // ------
2020
+
2021
+ // Used for managing application layouts, nested layouts and
2022
+ // multiple regions within an application or sub-application.
2023
+ //
2024
+ // A specialized view type that renders an area of HTML and then
2025
+ // attaches `Region` instances to the specified `regions`.
2026
+ // Used for composite view management and sub-application areas.
2027
+ Marionette.Layout = Marionette.ItemView.extend({
2028
+ regionType: Marionette.Region,
2029
+
2030
+ // Ensure the regions are available when the `initialize` method
2031
+ // is called.
2032
+ constructor: function (options) {
2033
+ options = options || {};
2034
+
2035
+ this._firstRender = true;
2036
+ this._initializeRegions(options);
2037
+
2038
+ Marionette.ItemView.prototype.constructor.call(this, options);
2039
+ },
2040
+
2041
+ // Layout's render will use the existing region objects the
2042
+ // first time it is called. Subsequent calls will close the
2043
+ // views that the regions are showing and then reset the `el`
2044
+ // for the regions to the newly rendered DOM elements.
2045
+ render: function(){
2046
+
2047
+ if (this.isClosed){
2048
+ // a previously closed layout means we need to
2049
+ // completely re-initialize the regions
2050
+ this._initializeRegions();
2051
+ }
2052
+ if (this._firstRender) {
2053
+ // if this is the first render, don't do anything to
2054
+ // reset the regions
2055
+ this._firstRender = false;
2056
+ } else if (!this.isClosed){
2057
+ // If this is not the first render call, then we need to
2058
+ // re-initializing the `el` for each region
2059
+ this._reInitializeRegions();
2060
+ }
2061
+
2062
+ var args = Array.prototype.slice.apply(arguments);
2063
+ var result = Marionette.ItemView.prototype.render.apply(this, args);
2064
+
2065
+ return result;
2066
+ },
2067
+
2068
+ // Handle closing regions, and then close the view itself.
2069
+ close: function () {
2070
+ if (this.isClosed){ return; }
2071
+ this.regionManager.close();
2072
+ var args = Array.prototype.slice.apply(arguments);
2073
+ Marionette.ItemView.prototype.close.apply(this, args);
2074
+ },
2075
+
2076
+ // Add a single region, by name, to the layout
2077
+ addRegion: function(name, definition){
2078
+ var regions = {};
2079
+ regions[name] = definition;
2080
+ return this._buildRegions(regions)[name];
2081
+ },
2082
+
2083
+ // Add multiple regions as a {name: definition, name2: def2} object literal
2084
+ addRegions: function(regions){
2085
+ this.regions = _.extend({}, this.regions, regions);
2086
+ return this._buildRegions(regions);
2087
+ },
2088
+
2089
+ // Remove a single region from the Layout, by name
2090
+ removeRegion: function(name){
2091
+ delete this.regions[name];
2092
+ return this.regionManager.removeRegion(name);
2093
+ },
2094
+
2095
+ // internal method to build regions
2096
+ _buildRegions: function(regions){
2097
+ var that = this;
2098
+
2099
+ var defaults = {
2100
+ regionType: Marionette.getOption(this, "regionType"),
2101
+ parentEl: function(){ return that.$el; }
2102
+ };
2103
+
2104
+ return this.regionManager.addRegions(regions, defaults);
2105
+ },
2106
+
2107
+ // Internal method to initialize the regions that have been defined in a
2108
+ // `regions` attribute on this layout.
2109
+ _initializeRegions: function (options) {
2110
+ var regions;
2111
+ this._initRegionManager();
2112
+
2113
+ if (_.isFunction(this.regions)) {
2114
+ regions = this.regions(options);
2115
+ } else {
2116
+ regions = this.regions || {};
2117
+ }
2118
+
2119
+ this.addRegions(regions);
2120
+ },
2121
+
2122
+ // Internal method to re-initialize all of the regions by updating the `el` that
2123
+ // they point to
2124
+ _reInitializeRegions: function(){
2125
+ this.regionManager.closeRegions();
2126
+ this.regionManager.each(function(region){
2127
+ region.reset();
2128
+ });
2129
+ },
2130
+
2131
+ // Internal method to initialize the region manager
2132
+ // and all regions in it
2133
+ _initRegionManager: function(){
2134
+ this.regionManager = new Marionette.RegionManager();
2135
+
2136
+ this.listenTo(this.regionManager, "region:add", function(name, region){
2137
+ this[name] = region;
2138
+ this.trigger("region:add", name, region);
2139
+ });
2140
+
2141
+ this.listenTo(this.regionManager, "region:remove", function(name, region){
2142
+ delete this[name];
2143
+ this.trigger("region:remove", name, region);
2144
+ });
2145
+ }
2146
+ });
2147
+
2148
+
2149
+ // AppRouter
2150
+ // ---------
2151
+
2152
+ // Reduce the boilerplate code of handling route events
2153
+ // and then calling a single method on another object.
2154
+ // Have your routers configured to call the method on
2155
+ // your object, directly.
2156
+ //
2157
+ // Configure an AppRouter with `appRoutes`.
2158
+ //
2159
+ // App routers can only take one `controller` object.
2160
+ // It is recommended that you divide your controller
2161
+ // objects in to smaller pieces of related functionality
2162
+ // and have multiple routers / controllers, instead of
2163
+ // just one giant router and controller.
2164
+ //
2165
+ // You can also add standard routes to an AppRouter.
2166
+
2167
+ Marionette.AppRouter = Backbone.Router.extend({
2168
+
2169
+ constructor: function(options){
2170
+ Backbone.Router.prototype.constructor.apply(this, slice(arguments));
2171
+
2172
+ this.options = options || {};
2173
+
2174
+ var appRoutes = Marionette.getOption(this, "appRoutes");
2175
+ var controller = this._getController();
2176
+ this.processAppRoutes(controller, appRoutes);
2177
+ },
2178
+
2179
+ // Similar to route method on a Backbone Router but
2180
+ // method is called on the controller
2181
+ appRoute: function(route, methodName) {
2182
+ var controller = this._getController();
2183
+ this._addAppRoute(controller, route, methodName);
2184
+ },
2185
+
2186
+ // Internal method to process the `appRoutes` for the
2187
+ // router, and turn them in to routes that trigger the
2188
+ // specified method on the specified `controller`.
2189
+ processAppRoutes: function(controller, appRoutes) {
2190
+ if (!appRoutes){ return; }
2191
+
2192
+ var routeNames = _.keys(appRoutes).reverse(); // Backbone requires reverted order of routes
2193
+
2194
+ _.each(routeNames, function(route) {
2195
+ this._addAppRoute(controller, route, appRoutes[route]);
2196
+ }, this);
2197
+ },
2198
+
2199
+ _getController: function(){
2200
+ return Marionette.getOption(this, "controller");
2201
+ },
2202
+
2203
+ _addAppRoute: function(controller, route, methodName){
2204
+ var method = controller[methodName];
2205
+
2206
+ if (!method) {
2207
+ throw new Error("Method '" + methodName + "' was not found on the controller");
2208
+ }
2209
+
2210
+ this.route(route, methodName, _.bind(method, controller));
2211
+ }
2212
+ });
2213
+
2214
+
2215
+ // Application
2216
+ // -----------
2217
+
2218
+ // Contain and manage the composite application as a whole.
2219
+ // Stores and starts up `Region` objects, includes an
2220
+ // event aggregator as `app.vent`
2221
+ Marionette.Application = function(options){
2222
+ this._initRegionManager();
2223
+ this._initCallbacks = new Marionette.Callbacks();
2224
+ this.vent = new Backbone.Wreqr.EventAggregator();
2225
+ this.commands = new Backbone.Wreqr.Commands();
2226
+ this.reqres = new Backbone.Wreqr.RequestResponse();
2227
+ this.submodules = {};
2228
+
2229
+ _.extend(this, options);
2230
+
2231
+ this.triggerMethod = Marionette.triggerMethod;
2232
+ };
2233
+
2234
+ _.extend(Marionette.Application.prototype, Backbone.Events, {
2235
+ // Command execution, facilitated by Backbone.Wreqr.Commands
2236
+ execute: function(){
2237
+ var args = Array.prototype.slice.apply(arguments);
2238
+ this.commands.execute.apply(this.commands, args);
2239
+ },
2240
+
2241
+ // Request/response, facilitated by Backbone.Wreqr.RequestResponse
2242
+ request: function(){
2243
+ var args = Array.prototype.slice.apply(arguments);
2244
+ return this.reqres.request.apply(this.reqres, args);
2245
+ },
2246
+
2247
+ // Add an initializer that is either run at when the `start`
2248
+ // method is called, or run immediately if added after `start`
2249
+ // has already been called.
2250
+ addInitializer: function(initializer){
2251
+ this._initCallbacks.add(initializer);
2252
+ },
2253
+
2254
+ // kick off all of the application's processes.
2255
+ // initializes all of the regions that have been added
2256
+ // to the app, and runs all of the initializer functions
2257
+ start: function(options){
2258
+ this.triggerMethod("initialize:before", options);
2259
+ this._initCallbacks.run(options, this);
2260
+ this.triggerMethod("initialize:after", options);
2261
+
2262
+ this.triggerMethod("start", options);
2263
+ },
2264
+
2265
+ // Add regions to your app.
2266
+ // Accepts a hash of named strings or Region objects
2267
+ // addRegions({something: "#someRegion"})
2268
+ // addRegions({something: Region.extend({el: "#someRegion"}) });
2269
+ addRegions: function(regions){
2270
+ return this._regionManager.addRegions(regions);
2271
+ },
2272
+
2273
+ // Close all regions in the app, without removing them
2274
+ closeRegions: function(){
2275
+ this._regionManager.closeRegions();
2276
+ },
2277
+
2278
+ // Removes a region from your app, by name
2279
+ // Accepts the regions name
2280
+ // removeRegion('myRegion')
2281
+ removeRegion: function(region) {
2282
+ this._regionManager.removeRegion(region);
2283
+ },
2284
+
2285
+ // Provides alternative access to regions
2286
+ // Accepts the region name
2287
+ // getRegion('main')
2288
+ getRegion: function(region) {
2289
+ return this._regionManager.get(region);
2290
+ },
2291
+
2292
+ // Create a module, attached to the application
2293
+ module: function(moduleNames, moduleDefinition){
2294
+ var ModuleClass = Marionette.Module;
2295
+
2296
+ // Overwrite the module class if the user specifies one
2297
+ if (moduleDefinition) {
2298
+ ModuleClass = moduleDefinition.moduleClass || ModuleClass;
2299
+ }
2300
+
2301
+ // slice the args, and add this application object as the
2302
+ // first argument of the array
2303
+ var args = slice(arguments);
2304
+ args.unshift(this);
2305
+
2306
+ // see the Marionette.Module object for more information
2307
+ return ModuleClass.create.apply(ModuleClass, args);
2308
+ },
2309
+
2310
+ // Internal method to set up the region manager
2311
+ _initRegionManager: function(){
2312
+ this._regionManager = new Marionette.RegionManager();
2313
+
2314
+ this.listenTo(this._regionManager, "region:add", function(name, region){
2315
+ this[name] = region;
2316
+ });
2317
+
2318
+ this.listenTo(this._regionManager, "region:remove", function(name, region){
2319
+ delete this[name];
2320
+ });
2321
+ }
2322
+ });
2323
+
2324
+ // Copy the `extend` function used by Backbone's classes
2325
+ Marionette.Application.extend = Marionette.extend;
2326
+
2327
+ // Module
2328
+ // ------
2329
+
2330
+ // A simple module system, used to create privacy and encapsulation in
2331
+ // Marionette applications
2332
+ Marionette.Module = function(moduleName, app, options){
2333
+ this.moduleName = moduleName;
2334
+ this.options = _.extend({}, this.options, options);
2335
+ this.initialize = options.initialize || this.initialize;
2336
+
2337
+ // store sub-modules
2338
+ this.submodules = {};
2339
+
2340
+ this._setupInitializersAndFinalizers();
2341
+
2342
+ // store the configuration for this module
2343
+ this.app = app;
2344
+ this.startWithParent = true;
2345
+
2346
+ this.triggerMethod = Marionette.triggerMethod;
2347
+
2348
+ if (_.isFunction(this.initialize)){
2349
+ this.initialize(this.options, moduleName, app);
2350
+ }
2351
+ };
2352
+
2353
+ Marionette.Module.extend = Marionette.extend;
2354
+
2355
+ // Extend the Module prototype with events / listenTo, so that the module
2356
+ // can be used as an event aggregator or pub/sub.
2357
+ _.extend(Marionette.Module.prototype, Backbone.Events, {
2358
+
2359
+ // Initialize is an empty function by default. Override it with your own
2360
+ // initialization logic when extending Marionette.Module.
2361
+ initialize: function(){},
2362
+
2363
+ // Initializer for a specific module. Initializers are run when the
2364
+ // module's `start` method is called.
2365
+ addInitializer: function(callback){
2366
+ this._initializerCallbacks.add(callback);
2367
+ },
2368
+
2369
+ // Finalizers are run when a module is stopped. They are used to teardown
2370
+ // and finalize any variables, references, events and other code that the
2371
+ // module had set up.
2372
+ addFinalizer: function(callback){
2373
+ this._finalizerCallbacks.add(callback);
2374
+ },
2375
+
2376
+ // Start the module, and run all of its initializers
2377
+ start: function(options){
2378
+ // Prevent re-starting a module that is already started
2379
+ if (this._isInitialized){ return; }
2380
+
2381
+ // start the sub-modules (depth-first hierarchy)
2382
+ _.each(this.submodules, function(mod){
2383
+ // check to see if we should start the sub-module with this parent
2384
+ if (mod.startWithParent){
2385
+ mod.start(options);
2386
+ }
2387
+ });
2388
+
2389
+ // run the callbacks to "start" the current module
2390
+ this.triggerMethod("before:start", options);
2391
+
2392
+ this._initializerCallbacks.run(options, this);
2393
+ this._isInitialized = true;
2394
+
2395
+ this.triggerMethod("start", options);
2396
+ },
2397
+
2398
+ // Stop this module by running its finalizers and then stop all of
2399
+ // the sub-modules for this module
2400
+ stop: function(){
2401
+ // if we are not initialized, don't bother finalizing
2402
+ if (!this._isInitialized){ return; }
2403
+ this._isInitialized = false;
2404
+
2405
+ Marionette.triggerMethod.call(this, "before:stop");
2406
+
2407
+ // stop the sub-modules; depth-first, to make sure the
2408
+ // sub-modules are stopped / finalized before parents
2409
+ _.each(this.submodules, function(mod){ mod.stop(); });
2410
+
2411
+ // run the finalizers
2412
+ this._finalizerCallbacks.run(undefined,this);
2413
+
2414
+ // reset the initializers and finalizers
2415
+ this._initializerCallbacks.reset();
2416
+ this._finalizerCallbacks.reset();
2417
+
2418
+ Marionette.triggerMethod.call(this, "stop");
2419
+ },
2420
+
2421
+ // Configure the module with a definition function and any custom args
2422
+ // that are to be passed in to the definition function
2423
+ addDefinition: function(moduleDefinition, customArgs){
2424
+ this._runModuleDefinition(moduleDefinition, customArgs);
2425
+ },
2426
+
2427
+ // Internal method: run the module definition function with the correct
2428
+ // arguments
2429
+ _runModuleDefinition: function(definition, customArgs){
2430
+ if (!definition){ return; }
2431
+
2432
+ // build the correct list of arguments for the module definition
2433
+ var args = _.flatten([
2434
+ this,
2435
+ this.app,
2436
+ Backbone,
2437
+ Marionette,
2438
+ Marionette.$, _,
2439
+ customArgs
2440
+ ]);
2441
+
2442
+ definition.apply(this, args);
2443
+ },
2444
+
2445
+ // Internal method: set up new copies of initializers and finalizers.
2446
+ // Calling this method will wipe out all existing initializers and
2447
+ // finalizers.
2448
+ _setupInitializersAndFinalizers: function(){
2449
+ this._initializerCallbacks = new Marionette.Callbacks();
2450
+ this._finalizerCallbacks = new Marionette.Callbacks();
2451
+ }
2452
+ });
2453
+
2454
+ // Type methods to create modules
2455
+ _.extend(Marionette.Module, {
2456
+
2457
+ // Create a module, hanging off the app parameter as the parent object.
2458
+ create: function(app, moduleNames, moduleDefinition){
2459
+ var module = app;
2460
+
2461
+ // get the custom args passed in after the module definition and
2462
+ // get rid of the module name and definition function
2463
+ var customArgs = slice(arguments);
2464
+ customArgs.splice(0, 3);
2465
+
2466
+ // split the module names and get the length
2467
+ moduleNames = moduleNames.split(".");
2468
+ var length = moduleNames.length;
2469
+
2470
+ // store the module definition for the last module in the chain
2471
+ var moduleDefinitions = [];
2472
+ moduleDefinitions[length-1] = moduleDefinition;
2473
+
2474
+ // Loop through all the parts of the module definition
2475
+ _.each(moduleNames, function(moduleName, i){
2476
+ var parentModule = module;
2477
+ module = this._getModule(parentModule, moduleName, app, moduleDefinition);
2478
+ this._addModuleDefinition(parentModule, module, moduleDefinitions[i], customArgs);
2479
+ }, this);
2480
+
2481
+ // Return the last module in the definition chain
2482
+ return module;
2483
+ },
2484
+
2485
+ _getModule: function(parentModule, moduleName, app, def, args){
2486
+ var ModuleClass = Marionette.Module;
2487
+ var options = _.extend({}, def);
2488
+ if (def) {
2489
+ ModuleClass = def.moduleClass || ModuleClass;
2490
+ }
2491
+
2492
+ // Get an existing module of this name if we have one
2493
+ var module = parentModule[moduleName];
2494
+
2495
+ if (!module){
2496
+ // Create a new module if we don't have one
2497
+ module = new ModuleClass(moduleName, app, options);
2498
+ parentModule[moduleName] = module;
2499
+ // store the module on the parent
2500
+ parentModule.submodules[moduleName] = module;
2501
+ }
2502
+
2503
+ return module;
2504
+ },
2505
+
2506
+ _addModuleDefinition: function(parentModule, module, def, args){
2507
+ var fn;
2508
+ var startWithParent;
2509
+
2510
+ if (_.isFunction(def)){
2511
+ // if a function is supplied for the module definition
2512
+ fn = def;
2513
+ startWithParent = true;
2514
+
2515
+ } else if (_.isObject(def)){
2516
+ // if an object is supplied
2517
+ fn = def.define;
2518
+ startWithParent = (typeof def.startWithParent !== 'undefined') ? def.startWithParent : true;
2519
+
2520
+ } else {
2521
+ // if nothing is supplied
2522
+ startWithParent = true;
2523
+ }
2524
+
2525
+ // add module definition if needed
2526
+ if (fn){
2527
+ module.addDefinition(fn, args);
2528
+ }
2529
+
2530
+ // `and` the two together, ensuring a single `false` will prevent it
2531
+ // from starting with the parent
2532
+ module.startWithParent = module.startWithParent && startWithParent;
2533
+
2534
+ // setup auto-start if needed
2535
+ if (module.startWithParent && !module.startWithParentIsConfigured){
2536
+
2537
+ // only configure this once
2538
+ module.startWithParentIsConfigured = true;
2539
+
2540
+ // add the module initializer config
2541
+ parentModule.addInitializer(function(options){
2542
+ if (module.startWithParent){
2543
+ module.start(options);
2544
+ }
2545
+ });
2546
+
2547
+ }
2548
+
2549
+ }
2550
+ });
2551
+
2552
+
2553
+
2554
+ return Marionette;
2555
+ })(this, Backbone, _);