js_stack 0.5.5 → 0.5.6

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