marionette.modal 1.0.0

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