js_stack 0.5.6 → 0.5.7

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