marionette_dust 0.0.1

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