js_stack 0.5.3 → 0.5.4

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