js_stack 0.6.8 → 1.0.0

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