js_stack 0.0.4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2363 @@
1
+ // MarionetteJS (Backbone.Marionette)
2
+ // ----------------------------------
3
+ // v1.1.0
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(){
1206
+ _.bindAll(this, "render");
1207
+
1208
+ var args = Array.prototype.slice.apply(arguments);
1209
+ Backbone.View.prototype.constructor.apply(this, args);
1210
+
1211
+ Marionette.MonitorDOMRefresh(this);
1212
+ this.listenTo(this, "show", this.onShowCalled, this);
1213
+ },
1214
+
1215
+ // import the "triggerMethod" to trigger events with corresponding
1216
+ // methods if the method exists
1217
+ triggerMethod: Marionette.triggerMethod,
1218
+
1219
+ // Get the template for this view
1220
+ // instance. You can set a `template` attribute in the view
1221
+ // definition or pass a `template: "whatever"` parameter in
1222
+ // to the constructor options.
1223
+ getTemplate: function(){
1224
+ return Marionette.getOption(this, "template");
1225
+ },
1226
+
1227
+ // Mix in template helper methods. Looks for a
1228
+ // `templateHelpers` attribute, which can either be an
1229
+ // object literal, or a function that returns an object
1230
+ // literal. All methods and attributes from this object
1231
+ // are copies to the object passed in.
1232
+ mixinTemplateHelpers: function(target){
1233
+ target = target || {};
1234
+ var templateHelpers = Marionette.getOption(this, "templateHelpers");
1235
+ if (_.isFunction(templateHelpers)){
1236
+ templateHelpers = templateHelpers.call(this);
1237
+ }
1238
+ return _.extend(target, templateHelpers);
1239
+ },
1240
+
1241
+ // Configure `triggers` to forward DOM events to view
1242
+ // events. `triggers: {"click .foo": "do:foo"}`
1243
+ configureTriggers: function(){
1244
+ if (!this.triggers) { return; }
1245
+
1246
+ var triggerEvents = {};
1247
+
1248
+ // Allow `triggers` to be configured as a function
1249
+ var triggers = _.result(this, "triggers");
1250
+
1251
+ // Configure the triggers, prevent default
1252
+ // action and stop propagation of DOM events
1253
+ _.each(triggers, function(value, key){
1254
+
1255
+ // build the event handler function for the DOM event
1256
+ triggerEvents[key] = function(e){
1257
+
1258
+ // stop the event in its tracks
1259
+ if (e && e.preventDefault){ e.preventDefault(); }
1260
+ if (e && e.stopPropagation){ e.stopPropagation(); }
1261
+
1262
+ // build the args for the event
1263
+ var args = {
1264
+ view: this,
1265
+ model: this.model,
1266
+ collection: this.collection
1267
+ };
1268
+
1269
+ // trigger the event
1270
+ this.triggerMethod(value, args);
1271
+ };
1272
+
1273
+ }, this);
1274
+
1275
+ return triggerEvents;
1276
+ },
1277
+
1278
+ // Overriding Backbone.View's delegateEvents to handle
1279
+ // the `triggers`, `modelEvents`, and `collectionEvents` configuration
1280
+ delegateEvents: function(events){
1281
+ this._delegateDOMEvents(events);
1282
+ Marionette.bindEntityEvents(this, this.model, Marionette.getOption(this, "modelEvents"));
1283
+ Marionette.bindEntityEvents(this, this.collection, Marionette.getOption(this, "collectionEvents"));
1284
+ },
1285
+
1286
+ // internal method to delegate DOM events and triggers
1287
+ _delegateDOMEvents: function(events){
1288
+ events = events || this.events;
1289
+ if (_.isFunction(events)){ events = events.call(this); }
1290
+
1291
+ var combinedEvents = {};
1292
+ var triggers = this.configureTriggers();
1293
+ _.extend(combinedEvents, events, triggers);
1294
+
1295
+ Backbone.View.prototype.delegateEvents.call(this, combinedEvents);
1296
+ },
1297
+
1298
+ // Overriding Backbone.View's undelegateEvents to handle unbinding
1299
+ // the `triggers`, `modelEvents`, and `collectionEvents` config
1300
+ undelegateEvents: function(){
1301
+ var args = Array.prototype.slice.call(arguments);
1302
+ Backbone.View.prototype.undelegateEvents.apply(this, args);
1303
+
1304
+ Marionette.unbindEntityEvents(this, this.model, Marionette.getOption(this, "modelEvents"));
1305
+ Marionette.unbindEntityEvents(this, this.collection, Marionette.getOption(this, "collectionEvents"));
1306
+ },
1307
+
1308
+ // Internal method, handles the `show` event.
1309
+ onShowCalled: function(){},
1310
+
1311
+ // Default `close` implementation, for removing a view from the
1312
+ // DOM and unbinding it. Regions will call this method
1313
+ // for you. You can specify an `onClose` method in your view to
1314
+ // add custom code that is called after the view is closed.
1315
+ close: function(){
1316
+ if (this.isClosed) { return; }
1317
+
1318
+ // allow the close to be stopped by returning `false`
1319
+ // from the `onBeforeClose` method
1320
+ var shouldClose = this.triggerMethod("before:close");
1321
+ if (shouldClose === false){
1322
+ return;
1323
+ }
1324
+
1325
+ // mark as closed before doing the actual close, to
1326
+ // prevent infinite loops within "close" event handlers
1327
+ // that are trying to close other views
1328
+ this.isClosed = true;
1329
+ this.triggerMethod("close");
1330
+
1331
+ // unbind UI elements
1332
+ this.unbindUIElements();
1333
+
1334
+ // remove the view from the DOM
1335
+ this.remove();
1336
+ },
1337
+
1338
+ // This method binds the elements specified in the "ui" hash inside the view's code with
1339
+ // the associated jQuery selectors.
1340
+ bindUIElements: function(){
1341
+ if (!this.ui) { return; }
1342
+
1343
+ // store the ui hash in _uiBindings so they can be reset later
1344
+ // and so re-rendering the view will be able to find the bindings
1345
+ if (!this._uiBindings){
1346
+ this._uiBindings = this.ui;
1347
+ }
1348
+
1349
+ // get the bindings result, as a function or otherwise
1350
+ var bindings = _.result(this, "_uiBindings");
1351
+
1352
+ // empty the ui so we don't have anything to start with
1353
+ this.ui = {};
1354
+
1355
+ // bind each of the selectors
1356
+ _.each(_.keys(bindings), function(key) {
1357
+ var selector = bindings[key];
1358
+ this.ui[key] = this.$(selector);
1359
+ }, this);
1360
+ },
1361
+
1362
+ // This method unbinds the elements specified in the "ui" hash
1363
+ unbindUIElements: function(){
1364
+ if (!this.ui || !this._uiBindings){ return; }
1365
+
1366
+ // delete all of the existing ui bindings
1367
+ _.each(this.ui, function($el, name){
1368
+ delete this.ui[name];
1369
+ }, this);
1370
+
1371
+ // reset the ui element to the original bindings configuration
1372
+ this.ui = this._uiBindings;
1373
+ delete this._uiBindings;
1374
+ }
1375
+ });
1376
+
1377
+ // Item View
1378
+ // ---------
1379
+
1380
+ // A single item view implementation that contains code for rendering
1381
+ // with underscore.js templates, serializing the view's model or collection,
1382
+ // and calling several methods on extended views, such as `onRender`.
1383
+ Marionette.ItemView = Marionette.View.extend({
1384
+
1385
+ // Setting up the inheritance chain which allows changes to
1386
+ // Marionette.View.prototype.constructor which allows overriding
1387
+ constructor: function(){
1388
+ Marionette.View.prototype.constructor.apply(this, slice(arguments));
1389
+ },
1390
+
1391
+ // Serialize the model or collection for the view. If a model is
1392
+ // found, `.toJSON()` is called. If a collection is found, `.toJSON()`
1393
+ // is also called, but is used to populate an `items` array in the
1394
+ // resulting data. If both are found, defaults to the model.
1395
+ // You can override the `serializeData` method in your own view
1396
+ // definition, to provide custom serialization for your view's data.
1397
+ serializeData: function(){
1398
+ var data = {};
1399
+
1400
+ if (this.model) {
1401
+ data = this.model.toJSON();
1402
+ }
1403
+ else if (this.collection) {
1404
+ data = { items: this.collection.toJSON() };
1405
+ }
1406
+
1407
+ return data;
1408
+ },
1409
+
1410
+ // Render the view, defaulting to underscore.js templates.
1411
+ // You can override this in your view definition to provide
1412
+ // a very specific rendering for your view. In general, though,
1413
+ // you should override the `Marionette.Renderer` object to
1414
+ // change how Marionette renders views.
1415
+ render: function(){
1416
+ this.isClosed = false;
1417
+
1418
+ this.triggerMethod("before:render", this);
1419
+ this.triggerMethod("item:before:render", this);
1420
+
1421
+ var data = this.serializeData();
1422
+ data = this.mixinTemplateHelpers(data);
1423
+
1424
+ var template = this.getTemplate();
1425
+ var html = Marionette.Renderer.render(template, data);
1426
+
1427
+ this.$el.html(html);
1428
+ this.bindUIElements();
1429
+
1430
+ this.triggerMethod("render", this);
1431
+ this.triggerMethod("item:rendered", this);
1432
+
1433
+ return this;
1434
+ },
1435
+
1436
+ // Override the default close event to add a few
1437
+ // more events that are triggered.
1438
+ close: function(){
1439
+ if (this.isClosed){ return; }
1440
+
1441
+ this.triggerMethod('item:before:close');
1442
+
1443
+ Marionette.View.prototype.close.apply(this, slice(arguments));
1444
+
1445
+ this.triggerMethod('item:closed');
1446
+ }
1447
+ });
1448
+
1449
+ // Collection View
1450
+ // ---------------
1451
+
1452
+ // A view that iterates over a Backbone.Collection
1453
+ // and renders an individual ItemView for each model.
1454
+ Marionette.CollectionView = Marionette.View.extend({
1455
+ // used as the prefix for item view events
1456
+ // that are forwarded through the collectionview
1457
+ itemViewEventPrefix: "itemview",
1458
+
1459
+ // constructor
1460
+ constructor: function(options){
1461
+ this._initChildViewStorage();
1462
+
1463
+ Marionette.View.prototype.constructor.apply(this, slice(arguments));
1464
+
1465
+ this._initialEvents();
1466
+ },
1467
+
1468
+ // Configured the initial events that the collection view
1469
+ // binds to. Override this method to prevent the initial
1470
+ // events, or to add your own initial events.
1471
+ _initialEvents: function(){
1472
+ if (this.collection){
1473
+ this.listenTo(this.collection, "add", this.addChildView, this);
1474
+ this.listenTo(this.collection, "remove", this.removeItemView, this);
1475
+ this.listenTo(this.collection, "reset", this.render, this);
1476
+ }
1477
+ },
1478
+
1479
+ // Handle a child item added to the collection
1480
+ addChildView: function(item, collection, options){
1481
+ this.closeEmptyView();
1482
+ var ItemView = this.getItemView(item);
1483
+ var index = this.collection.indexOf(item);
1484
+ this.addItemView(item, ItemView, index);
1485
+ },
1486
+
1487
+ // Override from `Marionette.View` to guarantee the `onShow` method
1488
+ // of child views is called.
1489
+ onShowCalled: function(){
1490
+ this.children.each(function(child){
1491
+ Marionette.triggerMethod.call(child, "show");
1492
+ });
1493
+ },
1494
+
1495
+ // Internal method to trigger the before render callbacks
1496
+ // and events
1497
+ triggerBeforeRender: function(){
1498
+ this.triggerMethod("before:render", this);
1499
+ this.triggerMethod("collection:before:render", this);
1500
+ },
1501
+
1502
+ // Internal method to trigger the rendered callbacks and
1503
+ // events
1504
+ triggerRendered: function(){
1505
+ this.triggerMethod("render", this);
1506
+ this.triggerMethod("collection:rendered", this);
1507
+ },
1508
+
1509
+ // Render the collection of items. Override this method to
1510
+ // provide your own implementation of a render function for
1511
+ // the collection view.
1512
+ render: function(){
1513
+ this.isClosed = false;
1514
+ this.triggerBeforeRender();
1515
+ this._renderChildren();
1516
+ this.triggerRendered();
1517
+ return this;
1518
+ },
1519
+
1520
+ // Internal method. Separated so that CompositeView can have
1521
+ // more control over events being triggered, around the rendering
1522
+ // process
1523
+ _renderChildren: function(){
1524
+ this.closeEmptyView();
1525
+ this.closeChildren();
1526
+
1527
+ if (this.collection && this.collection.length > 0) {
1528
+ this.showCollection();
1529
+ } else {
1530
+ this.showEmptyView();
1531
+ }
1532
+ },
1533
+
1534
+ // Internal method to loop through each item in the
1535
+ // collection view and show it
1536
+ showCollection: function(){
1537
+ var ItemView;
1538
+ this.collection.each(function(item, index){
1539
+ ItemView = this.getItemView(item);
1540
+ this.addItemView(item, ItemView, index);
1541
+ }, this);
1542
+ },
1543
+
1544
+ // Internal method to show an empty view in place of
1545
+ // a collection of item views, when the collection is
1546
+ // empty
1547
+ showEmptyView: function(){
1548
+ var EmptyView = Marionette.getOption(this, "emptyView");
1549
+
1550
+ if (EmptyView && !this._showingEmptyView){
1551
+ this._showingEmptyView = true;
1552
+ var model = new Backbone.Model();
1553
+ this.addItemView(model, EmptyView, 0);
1554
+ }
1555
+ },
1556
+
1557
+ // Internal method to close an existing emptyView instance
1558
+ // if one exists. Called when a collection view has been
1559
+ // rendered empty, and then an item is added to the collection.
1560
+ closeEmptyView: function(){
1561
+ if (this._showingEmptyView){
1562
+ this.closeChildren();
1563
+ delete this._showingEmptyView;
1564
+ }
1565
+ },
1566
+
1567
+ // Retrieve the itemView type, either from `this.options.itemView`
1568
+ // or from the `itemView` in the object definition. The "options"
1569
+ // takes precedence.
1570
+ getItemView: function(item){
1571
+ var itemView = Marionette.getOption(this, "itemView");
1572
+
1573
+ if (!itemView){
1574
+ throwError("An `itemView` must be specified", "NoItemViewError");
1575
+ }
1576
+
1577
+ return itemView;
1578
+ },
1579
+
1580
+ // Render the child item's view and add it to the
1581
+ // HTML for the collection view.
1582
+ addItemView: function(item, ItemView, index){
1583
+ // get the itemViewOptions if any were specified
1584
+ var itemViewOptions = Marionette.getOption(this, "itemViewOptions");
1585
+ if (_.isFunction(itemViewOptions)){
1586
+ itemViewOptions = itemViewOptions.call(this, item, index);
1587
+ }
1588
+
1589
+ // build the view
1590
+ var view = this.buildItemView(item, ItemView, itemViewOptions);
1591
+
1592
+ // set up the child view event forwarding
1593
+ this.addChildViewEventForwarding(view);
1594
+
1595
+ // this view is about to be added
1596
+ this.triggerMethod("before:item:added", view);
1597
+
1598
+ // Store the child view itself so we can properly
1599
+ // remove and/or close it later
1600
+ this.children.add(view);
1601
+
1602
+ // Render it and show it
1603
+ this.renderItemView(view, index);
1604
+
1605
+ // call the "show" method if the collection view
1606
+ // has already been shown
1607
+ if (this._isShown){
1608
+ Marionette.triggerMethod.call(view, "show");
1609
+ }
1610
+
1611
+ // this view was added
1612
+ this.triggerMethod("after:item:added", view);
1613
+ },
1614
+
1615
+ // Set up the child view event forwarding. Uses an "itemview:"
1616
+ // prefix in front of all forwarded events.
1617
+ addChildViewEventForwarding: function(view){
1618
+ var prefix = Marionette.getOption(this, "itemViewEventPrefix");
1619
+
1620
+ // Forward all child item view events through the parent,
1621
+ // prepending "itemview:" to the event name
1622
+ this.listenTo(view, "all", function(){
1623
+ var args = slice(arguments);
1624
+ args[0] = prefix + ":" + args[0];
1625
+ args.splice(1, 0, view);
1626
+
1627
+ Marionette.triggerMethod.apply(this, args);
1628
+ }, this);
1629
+ },
1630
+
1631
+ // render the item view
1632
+ renderItemView: function(view, index) {
1633
+ view.render();
1634
+ this.appendHtml(this, view, index);
1635
+ },
1636
+
1637
+ // Build an `itemView` for every model in the collection.
1638
+ buildItemView: function(item, ItemViewType, itemViewOptions){
1639
+ var options = _.extend({model: item}, itemViewOptions);
1640
+ return new ItemViewType(options);
1641
+ },
1642
+
1643
+ // get the child view by item it holds, and remove it
1644
+ removeItemView: function(item){
1645
+ var view = this.children.findByModel(item);
1646
+ this.removeChildView(view);
1647
+ this.checkEmpty();
1648
+ },
1649
+
1650
+ // Remove the child view and close it
1651
+ removeChildView: function(view){
1652
+
1653
+ // shut down the child view properly,
1654
+ // including events that the collection has from it
1655
+ if (view){
1656
+ this.stopListening(view);
1657
+
1658
+ // call 'close' or 'remove', depending on which is found
1659
+ if (view.close) { view.close(); }
1660
+ else if (view.remove) { view.remove(); }
1661
+
1662
+ this.children.remove(view);
1663
+ }
1664
+
1665
+ this.triggerMethod("item:removed", view);
1666
+ },
1667
+
1668
+ // helper to show the empty view if the collection is empty
1669
+ checkEmpty: function() {
1670
+ // check if we're empty now, and if we are, show the
1671
+ // empty view
1672
+ if (!this.collection || this.collection.length === 0){
1673
+ this.showEmptyView();
1674
+ }
1675
+ },
1676
+
1677
+ // Append the HTML to the collection's `el`.
1678
+ // Override this method to do something other
1679
+ // then `.append`.
1680
+ appendHtml: function(collectionView, itemView, index){
1681
+ collectionView.$el.append(itemView.el);
1682
+ },
1683
+
1684
+ // Internal method to set up the `children` object for
1685
+ // storing all of the child views
1686
+ _initChildViewStorage: function(){
1687
+ this.children = new Backbone.ChildViewContainer();
1688
+ },
1689
+
1690
+ // Handle cleanup and other closing needs for
1691
+ // the collection of views.
1692
+ close: function(){
1693
+ if (this.isClosed){ return; }
1694
+
1695
+ this.triggerMethod("collection:before:close");
1696
+ this.closeChildren();
1697
+ this.triggerMethod("collection:closed");
1698
+
1699
+ Marionette.View.prototype.close.apply(this, slice(arguments));
1700
+ },
1701
+
1702
+ // Close the child views that this collection view
1703
+ // is holding on to, if any
1704
+ closeChildren: function(){
1705
+ this.children.each(function(child){
1706
+ this.removeChildView(child);
1707
+ }, this);
1708
+ this.checkEmpty();
1709
+ }
1710
+ });
1711
+
1712
+
1713
+ // Composite View
1714
+ // --------------
1715
+
1716
+ // Used for rendering a branch-leaf, hierarchical structure.
1717
+ // Extends directly from CollectionView and also renders an
1718
+ // an item view as `modelView`, for the top leaf
1719
+ Marionette.CompositeView = Marionette.CollectionView.extend({
1720
+
1721
+ // Setting up the inheritance chain which allows changes to
1722
+ // Marionette.CollectionView.prototype.constructor which allows overriding
1723
+ constructor: function(){
1724
+ Marionette.CollectionView.prototype.constructor.apply(this, slice(arguments));
1725
+ },
1726
+
1727
+ // Configured the initial events that the composite view
1728
+ // binds to. Override this method to prevent the initial
1729
+ // events, or to add your own initial events.
1730
+ _initialEvents: function(){
1731
+ if (this.collection){
1732
+ this.listenTo(this.collection, "add", this.addChildView, this);
1733
+ this.listenTo(this.collection, "remove", this.removeItemView, this);
1734
+ this.listenTo(this.collection, "reset", this._renderChildren, this);
1735
+ }
1736
+ },
1737
+
1738
+ // Retrieve the `itemView` to be used when rendering each of
1739
+ // the items in the collection. The default is to return
1740
+ // `this.itemView` or Marionette.CompositeView if no `itemView`
1741
+ // has been defined
1742
+ getItemView: function(item){
1743
+ var itemView = Marionette.getOption(this, "itemView") || this.constructor;
1744
+
1745
+ if (!itemView){
1746
+ throwError("An `itemView` must be specified", "NoItemViewError");
1747
+ }
1748
+
1749
+ return itemView;
1750
+ },
1751
+
1752
+ // Serialize the collection for the view.
1753
+ // You can override the `serializeData` method in your own view
1754
+ // definition, to provide custom serialization for your view's data.
1755
+ serializeData: function(){
1756
+ var data = {};
1757
+
1758
+ if (this.model){
1759
+ data = this.model.toJSON();
1760
+ }
1761
+
1762
+ return data;
1763
+ },
1764
+
1765
+ // Renders the model once, and the collection once. Calling
1766
+ // this again will tell the model's view to re-render itself
1767
+ // but the collection will not re-render.
1768
+ render: function(){
1769
+ this.isRendered = true;
1770
+ this.isClosed = false;
1771
+ this.resetItemViewContainer();
1772
+
1773
+ this.triggerBeforeRender();
1774
+ var html = this.renderModel();
1775
+ this.$el.html(html);
1776
+ // the ui bindings is done here and not at the end of render since they
1777
+ // will not be available until after the model is rendered, but should be
1778
+ // available before the collection is rendered.
1779
+ this.bindUIElements();
1780
+ this.triggerMethod("composite:model:rendered");
1781
+
1782
+ this._renderChildren();
1783
+
1784
+ this.triggerMethod("composite:rendered");
1785
+ this.triggerRendered();
1786
+ return this;
1787
+ },
1788
+
1789
+ _renderChildren: function(){
1790
+ if (this.isRendered){
1791
+ Marionette.CollectionView.prototype._renderChildren.call(this);
1792
+ this.triggerMethod("composite:collection:rendered");
1793
+ }
1794
+ },
1795
+
1796
+ // Render an individual model, if we have one, as
1797
+ // part of a composite view (branch / leaf). For example:
1798
+ // a treeview.
1799
+ renderModel: function(){
1800
+ var data = {};
1801
+ data = this.serializeData();
1802
+ data = this.mixinTemplateHelpers(data);
1803
+
1804
+ var template = this.getTemplate();
1805
+ return Marionette.Renderer.render(template, data);
1806
+ },
1807
+
1808
+ // Appends the `el` of itemView instances to the specified
1809
+ // `itemViewContainer` (a jQuery selector). Override this method to
1810
+ // provide custom logic of how the child item view instances have their
1811
+ // HTML appended to the composite view instance.
1812
+ appendHtml: function(cv, iv, index){
1813
+ var $container = this.getItemViewContainer(cv);
1814
+ $container.append(iv.el);
1815
+ },
1816
+
1817
+ // Internal method to ensure an `$itemViewContainer` exists, for the
1818
+ // `appendHtml` method to use.
1819
+ getItemViewContainer: function(containerView){
1820
+ if ("$itemViewContainer" in containerView){
1821
+ return containerView.$itemViewContainer;
1822
+ }
1823
+
1824
+ var container;
1825
+ var itemViewContainer = Marionette.getOption(containerView, "itemViewContainer");
1826
+ if (itemViewContainer){
1827
+
1828
+ var selector = _.isFunction(itemViewContainer) ? itemViewContainer() : itemViewContainer;
1829
+ container = containerView.$(selector);
1830
+ if (container.length <= 0) {
1831
+ throwError("The specified `itemViewContainer` was not found: " + containerView.itemViewContainer, "ItemViewContainerMissingError");
1832
+ }
1833
+
1834
+ } else {
1835
+ container = containerView.$el;
1836
+ }
1837
+
1838
+ containerView.$itemViewContainer = container;
1839
+ return container;
1840
+ },
1841
+
1842
+ // Internal method to reset the `$itemViewContainer` on render
1843
+ resetItemViewContainer: function(){
1844
+ if (this.$itemViewContainer){
1845
+ delete this.$itemViewContainer;
1846
+ }
1847
+ }
1848
+ });
1849
+
1850
+
1851
+ // Layout
1852
+ // ------
1853
+
1854
+ // Used for managing application layouts, nested layouts and
1855
+ // multiple regions within an application or sub-application.
1856
+ //
1857
+ // A specialized view type that renders an area of HTML and then
1858
+ // attaches `Region` instances to the specified `regions`.
1859
+ // Used for composite view management and sub-application areas.
1860
+ Marionette.Layout = Marionette.ItemView.extend({
1861
+ regionType: Marionette.Region,
1862
+
1863
+ // Ensure the regions are available when the `initialize` method
1864
+ // is called.
1865
+ constructor: function (options) {
1866
+ options = options || {};
1867
+
1868
+ this._firstRender = true;
1869
+ this._initializeRegions(options);
1870
+
1871
+ Marionette.ItemView.prototype.constructor.call(this, options);
1872
+ },
1873
+
1874
+ // Layout's render will use the existing region objects the
1875
+ // first time it is called. Subsequent calls will close the
1876
+ // views that the regions are showing and then reset the `el`
1877
+ // for the regions to the newly rendered DOM elements.
1878
+ render: function(){
1879
+
1880
+ if (this.isClosed){
1881
+ // a previously closed layout means we need to
1882
+ // completely re-initialize the regions
1883
+ this._initializeRegions();
1884
+ }
1885
+ if (this._firstRender) {
1886
+ // if this is the first render, don't do anything to
1887
+ // reset the regions
1888
+ this._firstRender = false;
1889
+ } else if (!this.isClosed){
1890
+ // If this is not the first render call, then we need to
1891
+ // re-initializing the `el` for each region
1892
+ this._reInitializeRegions();
1893
+ }
1894
+
1895
+ var args = Array.prototype.slice.apply(arguments);
1896
+ var result = Marionette.ItemView.prototype.render.apply(this, args);
1897
+
1898
+ return result;
1899
+ },
1900
+
1901
+ // Handle closing regions, and then close the view itself.
1902
+ close: function () {
1903
+ if (this.isClosed){ return; }
1904
+ this.regionManager.close();
1905
+ var args = Array.prototype.slice.apply(arguments);
1906
+ Marionette.ItemView.prototype.close.apply(this, args);
1907
+ },
1908
+
1909
+ // Add a single region, by name, to the layout
1910
+ addRegion: function(name, definition){
1911
+ var regions = {};
1912
+ regions[name] = definition;
1913
+ return this._buildRegions(regions)[name];
1914
+ },
1915
+
1916
+ // Add multiple regions as a {name: definition, name2: def2} object literal
1917
+ addRegions: function(regions){
1918
+ this.regions = _.extend({}, this.regions, regions);
1919
+ return this._buildRegions(regions);
1920
+ },
1921
+
1922
+ // Remove a single region from the Layout, by name
1923
+ removeRegion: function(name){
1924
+ delete this.regions[name];
1925
+ return this.regionManager.removeRegion(name);
1926
+ },
1927
+
1928
+ // internal method to build regions
1929
+ _buildRegions: function(regions){
1930
+ var that = this;
1931
+
1932
+ var defaults = {
1933
+ regionType: Marionette.getOption(this, "regionType"),
1934
+ parentEl: function(){ return that.$el; }
1935
+ };
1936
+
1937
+ return this.regionManager.addRegions(regions, defaults);
1938
+ },
1939
+
1940
+ // Internal method to initialize the regions that have been defined in a
1941
+ // `regions` attribute on this layout.
1942
+ _initializeRegions: function (options) {
1943
+ var regions;
1944
+ this._initRegionManager();
1945
+
1946
+ if (_.isFunction(this.regions)) {
1947
+ regions = this.regions(options);
1948
+ } else {
1949
+ regions = this.regions || {};
1950
+ }
1951
+
1952
+ this.addRegions(regions);
1953
+ },
1954
+
1955
+ // Internal method to re-initialize all of the regions by updating the `el` that
1956
+ // they point to
1957
+ _reInitializeRegions: function(){
1958
+ this.regionManager.closeRegions();
1959
+ this.regionManager.each(function(region){
1960
+ region.reset();
1961
+ });
1962
+ },
1963
+
1964
+ // Internal method to initialize the region manager
1965
+ // and all regions in it
1966
+ _initRegionManager: function(){
1967
+ this.regionManager = new Marionette.RegionManager();
1968
+
1969
+ this.listenTo(this.regionManager, "region:add", function(name, region){
1970
+ this[name] = region;
1971
+ this.trigger("region:add", name, region);
1972
+ });
1973
+
1974
+ this.listenTo(this.regionManager, "region:remove", function(name, region){
1975
+ delete this[name];
1976
+ this.trigger("region:remove", name, region);
1977
+ });
1978
+ }
1979
+ });
1980
+
1981
+
1982
+ // AppRouter
1983
+ // ---------
1984
+
1985
+ // Reduce the boilerplate code of handling route events
1986
+ // and then calling a single method on another object.
1987
+ // Have your routers configured to call the method on
1988
+ // your object, directly.
1989
+ //
1990
+ // Configure an AppRouter with `appRoutes`.
1991
+ //
1992
+ // App routers can only take one `controller` object.
1993
+ // It is recommended that you divide your controller
1994
+ // objects in to smaller pieces of related functionality
1995
+ // and have multiple routers / controllers, instead of
1996
+ // just one giant router and controller.
1997
+ //
1998
+ // You can also add standard routes to an AppRouter.
1999
+
2000
+ Marionette.AppRouter = Backbone.Router.extend({
2001
+
2002
+ constructor: function(options){
2003
+ Backbone.Router.prototype.constructor.apply(this, slice(arguments));
2004
+
2005
+ this.options = options || {};
2006
+
2007
+ var appRoutes = Marionette.getOption(this, "appRoutes");
2008
+ var controller = this._getController();
2009
+ this.processAppRoutes(controller, appRoutes);
2010
+ },
2011
+
2012
+ // Similar to route method on a Backbone Router but
2013
+ // method is called on the controller
2014
+ appRoute: function(route, methodName) {
2015
+ var controller = this._getController();
2016
+ this._addAppRoute(controller, route, methodName);
2017
+ },
2018
+
2019
+ // Internal method to process the `appRoutes` for the
2020
+ // router, and turn them in to routes that trigger the
2021
+ // specified method on the specified `controller`.
2022
+ processAppRoutes: function(controller, appRoutes) {
2023
+ if (!appRoutes){ return; }
2024
+
2025
+ var routeNames = _.keys(appRoutes).reverse(); // Backbone requires reverted order of routes
2026
+
2027
+ _.each(routeNames, function(route) {
2028
+ this._addAppRoute(controller, route, appRoutes[route]);
2029
+ }, this);
2030
+ },
2031
+
2032
+ _getController: function(){
2033
+ return Marionette.getOption(this, "controller");
2034
+ },
2035
+
2036
+ _addAppRoute: function(controller, route, methodName){
2037
+ var method = controller[methodName];
2038
+
2039
+ if (!method) {
2040
+ throw new Error("Method '" + methodName + "' was not found on the controller");
2041
+ }
2042
+
2043
+ this.route(route, methodName, _.bind(method, controller));
2044
+ }
2045
+ });
2046
+
2047
+
2048
+ // Application
2049
+ // -----------
2050
+
2051
+ // Contain and manage the composite application as a whole.
2052
+ // Stores and starts up `Region` objects, includes an
2053
+ // event aggregator as `app.vent`
2054
+ Marionette.Application = function(options){
2055
+ this._initRegionManager();
2056
+ this._initCallbacks = new Marionette.Callbacks();
2057
+ this.vent = new Backbone.Wreqr.EventAggregator();
2058
+ this.commands = new Backbone.Wreqr.Commands();
2059
+ this.reqres = new Backbone.Wreqr.RequestResponse();
2060
+ this.submodules = {};
2061
+
2062
+ _.extend(this, options);
2063
+
2064
+ this.triggerMethod = Marionette.triggerMethod;
2065
+ };
2066
+
2067
+ _.extend(Marionette.Application.prototype, Backbone.Events, {
2068
+ // Command execution, facilitated by Backbone.Wreqr.Commands
2069
+ execute: function(){
2070
+ var args = Array.prototype.slice.apply(arguments);
2071
+ this.commands.execute.apply(this.commands, args);
2072
+ },
2073
+
2074
+ // Request/response, facilitated by Backbone.Wreqr.RequestResponse
2075
+ request: function(){
2076
+ var args = Array.prototype.slice.apply(arguments);
2077
+ return this.reqres.request.apply(this.reqres, args);
2078
+ },
2079
+
2080
+ // Add an initializer that is either run at when the `start`
2081
+ // method is called, or run immediately if added after `start`
2082
+ // has already been called.
2083
+ addInitializer: function(initializer){
2084
+ this._initCallbacks.add(initializer);
2085
+ },
2086
+
2087
+ // kick off all of the application's processes.
2088
+ // initializes all of the regions that have been added
2089
+ // to the app, and runs all of the initializer functions
2090
+ start: function(options){
2091
+ this.triggerMethod("initialize:before", options);
2092
+ this._initCallbacks.run(options, this);
2093
+ this.triggerMethod("initialize:after", options);
2094
+
2095
+ this.triggerMethod("start", options);
2096
+ },
2097
+
2098
+ // Add regions to your app.
2099
+ // Accepts a hash of named strings or Region objects
2100
+ // addRegions({something: "#someRegion"})
2101
+ // addRegions({something: Region.extend({el: "#someRegion"}) });
2102
+ addRegions: function(regions){
2103
+ return this._regionManager.addRegions(regions);
2104
+ },
2105
+
2106
+ // Close all regions in the app, without removing them
2107
+ closeRegions: function(){
2108
+ this._regionManager.closeRegions();
2109
+ },
2110
+
2111
+ // Removes a region from your app, by name
2112
+ // Accepts the regions name
2113
+ // removeRegion('myRegion')
2114
+ removeRegion: function(region) {
2115
+ this._regionManager.removeRegion(region);
2116
+ },
2117
+
2118
+ // Provides alternative access to regions
2119
+ // Accepts the region name
2120
+ // getRegion('main')
2121
+ getRegion: function(region) {
2122
+ return this._regionManager.get(region);
2123
+ },
2124
+
2125
+ // Create a module, attached to the application
2126
+ module: function(moduleNames, moduleDefinition){
2127
+ // slice the args, and add this application object as the
2128
+ // first argument of the array
2129
+ var args = slice(arguments);
2130
+ args.unshift(this);
2131
+
2132
+ // see the Marionette.Module object for more information
2133
+ return Marionette.Module.create.apply(Marionette.Module, args);
2134
+ },
2135
+
2136
+ // Internal method to set up the region manager
2137
+ _initRegionManager: function(){
2138
+ this._regionManager = new Marionette.RegionManager();
2139
+
2140
+ this.listenTo(this._regionManager, "region:add", function(name, region){
2141
+ this[name] = region;
2142
+ });
2143
+
2144
+ this.listenTo(this._regionManager, "region:remove", function(name, region){
2145
+ delete this[name];
2146
+ });
2147
+ }
2148
+ });
2149
+
2150
+ // Copy the `extend` function used by Backbone's classes
2151
+ Marionette.Application.extend = Marionette.extend;
2152
+
2153
+ // Module
2154
+ // ------
2155
+
2156
+ // A simple module system, used to create privacy and encapsulation in
2157
+ // Marionette applications
2158
+ Marionette.Module = function(moduleName, app){
2159
+ this.moduleName = moduleName;
2160
+
2161
+ // store sub-modules
2162
+ this.submodules = {};
2163
+
2164
+ this._setupInitializersAndFinalizers();
2165
+
2166
+ // store the configuration for this module
2167
+ this.app = app;
2168
+ this.startWithParent = true;
2169
+
2170
+ this.triggerMethod = Marionette.triggerMethod;
2171
+ };
2172
+
2173
+ // Extend the Module prototype with events / listenTo, so that the module
2174
+ // can be used as an event aggregator or pub/sub.
2175
+ _.extend(Marionette.Module.prototype, Backbone.Events, {
2176
+
2177
+ // Initializer for a specific module. Initializers are run when the
2178
+ // module's `start` method is called.
2179
+ addInitializer: function(callback){
2180
+ this._initializerCallbacks.add(callback);
2181
+ },
2182
+
2183
+ // Finalizers are run when a module is stopped. They are used to teardown
2184
+ // and finalize any variables, references, events and other code that the
2185
+ // module had set up.
2186
+ addFinalizer: function(callback){
2187
+ this._finalizerCallbacks.add(callback);
2188
+ },
2189
+
2190
+ // Start the module, and run all of its initializers
2191
+ start: function(options){
2192
+ // Prevent re-starting a module that is already started
2193
+ if (this._isInitialized){ return; }
2194
+
2195
+ // start the sub-modules (depth-first hierarchy)
2196
+ _.each(this.submodules, function(mod){
2197
+ // check to see if we should start the sub-module with this parent
2198
+ if (mod.startWithParent){
2199
+ mod.start(options);
2200
+ }
2201
+ });
2202
+
2203
+ // run the callbacks to "start" the current module
2204
+ this.triggerMethod("before:start", options);
2205
+
2206
+ this._initializerCallbacks.run(options, this);
2207
+ this._isInitialized = true;
2208
+
2209
+ this.triggerMethod("start", options);
2210
+ },
2211
+
2212
+ // Stop this module by running its finalizers and then stop all of
2213
+ // the sub-modules for this module
2214
+ stop: function(){
2215
+ // if we are not initialized, don't bother finalizing
2216
+ if (!this._isInitialized){ return; }
2217
+ this._isInitialized = false;
2218
+
2219
+ Marionette.triggerMethod.call(this, "before:stop");
2220
+
2221
+ // stop the sub-modules; depth-first, to make sure the
2222
+ // sub-modules are stopped / finalized before parents
2223
+ _.each(this.submodules, function(mod){ mod.stop(); });
2224
+
2225
+ // run the finalizers
2226
+ this._finalizerCallbacks.run(undefined,this);
2227
+
2228
+ // reset the initializers and finalizers
2229
+ this._initializerCallbacks.reset();
2230
+ this._finalizerCallbacks.reset();
2231
+
2232
+ Marionette.triggerMethod.call(this, "stop");
2233
+ },
2234
+
2235
+ // Configure the module with a definition function and any custom args
2236
+ // that are to be passed in to the definition function
2237
+ addDefinition: function(moduleDefinition, customArgs){
2238
+ this._runModuleDefinition(moduleDefinition, customArgs);
2239
+ },
2240
+
2241
+ // Internal method: run the module definition function with the correct
2242
+ // arguments
2243
+ _runModuleDefinition: function(definition, customArgs){
2244
+ if (!definition){ return; }
2245
+
2246
+ // build the correct list of arguments for the module definition
2247
+ var args = _.flatten([
2248
+ this,
2249
+ this.app,
2250
+ Backbone,
2251
+ Marionette,
2252
+ Marionette.$, _,
2253
+ customArgs
2254
+ ]);
2255
+
2256
+ definition.apply(this, args);
2257
+ },
2258
+
2259
+ // Internal method: set up new copies of initializers and finalizers.
2260
+ // Calling this method will wipe out all existing initializers and
2261
+ // finalizers.
2262
+ _setupInitializersAndFinalizers: function(){
2263
+ this._initializerCallbacks = new Marionette.Callbacks();
2264
+ this._finalizerCallbacks = new Marionette.Callbacks();
2265
+ }
2266
+ });
2267
+
2268
+ // Type methods to create modules
2269
+ _.extend(Marionette.Module, {
2270
+
2271
+ // Create a module, hanging off the app parameter as the parent object.
2272
+ create: function(app, moduleNames, moduleDefinition){
2273
+ var module = app;
2274
+
2275
+ // get the custom args passed in after the module definition and
2276
+ // get rid of the module name and definition function
2277
+ var customArgs = slice(arguments);
2278
+ customArgs.splice(0, 3);
2279
+
2280
+ // split the module names and get the length
2281
+ moduleNames = moduleNames.split(".");
2282
+ var length = moduleNames.length;
2283
+
2284
+ // store the module definition for the last module in the chain
2285
+ var moduleDefinitions = [];
2286
+ moduleDefinitions[length-1] = moduleDefinition;
2287
+
2288
+ // Loop through all the parts of the module definition
2289
+ _.each(moduleNames, function(moduleName, i){
2290
+ var parentModule = module;
2291
+ module = this._getModule(parentModule, moduleName, app);
2292
+ this._addModuleDefinition(parentModule, module, moduleDefinitions[i], customArgs);
2293
+ }, this);
2294
+
2295
+ // Return the last module in the definition chain
2296
+ return module;
2297
+ },
2298
+
2299
+ _getModule: function(parentModule, moduleName, app, def, args){
2300
+ // Get an existing module of this name if we have one
2301
+ var module = parentModule[moduleName];
2302
+
2303
+ if (!module){
2304
+ // Create a new module if we don't have one
2305
+ module = new Marionette.Module(moduleName, app);
2306
+ parentModule[moduleName] = module;
2307
+ // store the module on the parent
2308
+ parentModule.submodules[moduleName] = module;
2309
+ }
2310
+
2311
+ return module;
2312
+ },
2313
+
2314
+ _addModuleDefinition: function(parentModule, module, def, args){
2315
+ var fn;
2316
+ var startWithParent;
2317
+
2318
+ if (_.isFunction(def)){
2319
+ // if a function is supplied for the module definition
2320
+ fn = def;
2321
+ startWithParent = true;
2322
+
2323
+ } else if (_.isObject(def)){
2324
+ // if an object is supplied
2325
+ fn = def.define;
2326
+ startWithParent = def.startWithParent;
2327
+
2328
+ } else {
2329
+ // if nothing is supplied
2330
+ startWithParent = true;
2331
+ }
2332
+
2333
+ // add module definition if needed
2334
+ if (fn){
2335
+ module.addDefinition(fn, args);
2336
+ }
2337
+
2338
+ // `and` the two together, ensuring a single `false` will prevent it
2339
+ // from starting with the parent
2340
+ module.startWithParent = module.startWithParent && startWithParent;
2341
+
2342
+ // setup auto-start if needed
2343
+ if (module.startWithParent && !module.startWithParentIsConfigured){
2344
+
2345
+ // only configure this once
2346
+ module.startWithParentIsConfigured = true;
2347
+
2348
+ // add the module initializer config
2349
+ parentModule.addInitializer(function(options){
2350
+ if (module.startWithParent){
2351
+ module.start(options);
2352
+ }
2353
+ });
2354
+
2355
+ }
2356
+
2357
+ }
2358
+ });
2359
+
2360
+
2361
+
2362
+ return Marionette;
2363
+ })(this, Backbone, _);