js_stack 0.6.5 → 0.6.6

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