js_stack 1.7.0 → 1.8.0

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