backbone-marionette-rails 1.0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 yz
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,29 @@
1
+ # Backbone::Marionette::Rails
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'backbone-marionette-rails'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install backbone-marionette-rails
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
@@ -0,0 +1,10 @@
1
+ require "backbone-marionette-rails/version"
2
+
3
+ module Backbone
4
+ module Marionette
5
+ module Rails
6
+ class Engine < ::Rails::Engine
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,7 @@
1
+ module Backbone
2
+ module Marionette
3
+ module Rails
4
+ VERSION = "1.0.0.1"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,2069 @@
1
+ // Backbone.Marionette, v1.0.0-rc5
2
+ // Copyright (c)2013 Derick Bailey, Muted Solutions, LLC.
3
+ // Distributed under MIT license
4
+ // http://github.com/marionettejs/backbone.marionette
5
+
6
+
7
+ /*!
8
+ * Includes BabySitter
9
+ * https://github.com/marionettejs/backbone.babysitter/
10
+ *
11
+ * Includes Wreqr
12
+ * https://github.com/marionettejs/backbone.wreqr/
13
+ */
14
+
15
+
16
+ // Backbone.BabySitter, v0.0.4
17
+ // Copyright (c)2012 Derick Bailey, Muted Solutions, LLC.
18
+ // Distributed under MIT license
19
+ // http://github.com/marionettejs/backbone.babysitter
20
+ // Backbone.ChildViewContainer
21
+ // ---------------------------
22
+ //
23
+ // Provide a container to store, retrieve and
24
+ // shut down child views.
25
+
26
+ Backbone.ChildViewContainer = (function(Backbone, _){
27
+
28
+ // Container Constructor
29
+ // ---------------------
30
+
31
+ var Container = function(initialViews){
32
+ this._views = {};
33
+ this._indexByModel = {};
34
+ this._indexByCollection = {};
35
+ this._indexByCustom = {};
36
+ this._updateLength();
37
+
38
+ this._addInitialViews(initialViews);
39
+ };
40
+
41
+ // Container Methods
42
+ // -----------------
43
+
44
+ _.extend(Container.prototype, {
45
+
46
+ // Add a view to this container. Stores the view
47
+ // by `cid` and makes it searchable by the model
48
+ // and/or collection of the view. Optionally specify
49
+ // a custom key to store an retrieve the view.
50
+ add: function(view, customIndex){
51
+ var viewCid = view.cid;
52
+
53
+ // store the view
54
+ this._views[viewCid] = view;
55
+
56
+ // index it by model
57
+ if (view.model){
58
+ this._indexByModel[view.model.cid] = viewCid;
59
+ }
60
+
61
+ // index it by collection
62
+ if (view.collection){
63
+ this._indexByCollection[view.collection.cid] = viewCid;
64
+ }
65
+
66
+ // index by custom
67
+ if (customIndex){
68
+ this._indexByCustom[customIndex] = viewCid;
69
+ }
70
+
71
+ this._updateLength();
72
+ },
73
+
74
+ // Find a view by the model that was attached to
75
+ // it. Uses the model's `cid` to find it, and
76
+ // retrieves the view by it's `cid` from the result
77
+ findByModel: function(model){
78
+ var viewCid = this._indexByModel[model.cid];
79
+ return this.findByCid(viewCid);
80
+ },
81
+
82
+ // Find a view by the collection that was attached to
83
+ // it. Uses the collection's `cid` to find it, and
84
+ // retrieves the view by it's `cid` from the result
85
+ findByCollection: function(col){
86
+ var viewCid = this._indexByCollection[col.cid];
87
+ return this.findByCid(viewCid);
88
+ },
89
+
90
+ // Find a view by a custom indexer.
91
+ findByCustom: function(index){
92
+ var viewCid = this._indexByCustom[index];
93
+ return this.findByCid(viewCid);
94
+ },
95
+
96
+ // Find by index. This is not guaranteed to be a
97
+ // stable index.
98
+ findByIndex: function(index){
99
+ return _.values(this._views)[index];
100
+ },
101
+
102
+ // retrieve a view by it's `cid` directly
103
+ findByCid: function(cid){
104
+ return this._views[cid];
105
+ },
106
+
107
+ // Remove a view
108
+ remove: function(view){
109
+ var viewCid = view.cid;
110
+
111
+ // delete model index
112
+ if (view.model){
113
+ delete this._indexByModel[view.model.cid];
114
+ }
115
+
116
+ // delete collection index
117
+ if (view.collection){
118
+ delete this._indexByCollection[view.collection.cid];
119
+ }
120
+
121
+ // delete custom index
122
+ var cust;
123
+
124
+ for (var key in this._indexByCustom){
125
+ if (this._indexByCustom.hasOwnProperty(key)){
126
+ if (this._indexByCustom[key] === viewCid){
127
+ cust = key;
128
+ break;
129
+ }
130
+ }
131
+ }
132
+
133
+ if (cust){
134
+ delete this._indexByCustom[cust];
135
+ }
136
+
137
+ // remove the view from the container
138
+ delete this._views[viewCid];
139
+
140
+ // update the length
141
+ this._updateLength();
142
+ },
143
+
144
+ // Call a method on every view in the container,
145
+ // passing parameters to the call method one at a
146
+ // time, like `function.call`.
147
+ call: function(method, args){
148
+ args = Array.prototype.slice.call(arguments, 1);
149
+ this.apply(method, args);
150
+ },
151
+
152
+ // Apply a method on every view in the container,
153
+ // passing parameters to the call method one at a
154
+ // time, like `function.apply`.
155
+ apply: function(method, args){
156
+ var view;
157
+
158
+ // fix for IE < 9
159
+ args = args || [];
160
+
161
+ _.each(this._views, function(view, key){
162
+ if (_.isFunction(view[method])){
163
+ view[method].apply(view, args);
164
+ }
165
+ });
166
+
167
+ },
168
+
169
+ // Update the `.length` attribute on this container
170
+ _updateLength: function(){
171
+ this.length = _.size(this._views);
172
+ },
173
+
174
+ // set up an initial list of views
175
+ _addInitialViews: function(views){
176
+ if (!views){ return; }
177
+
178
+ var view, i,
179
+ length = views.length;
180
+
181
+ for (i=0; i<length; i++){
182
+ view = views[i];
183
+ this.add(view);
184
+ }
185
+ }
186
+ });
187
+
188
+ // Borrowing this code from Backbone.Collection:
189
+ // http://backbonejs.org/docs/backbone.html#section-106
190
+ //
191
+ // Mix in methods from Underscore, for iteration, and other
192
+ // collection related features.
193
+ var methods = ['forEach', 'each', 'map', 'find', 'detect', 'filter',
194
+ 'select', 'reject', 'every', 'all', 'some', 'any', 'include',
195
+ 'contains', 'invoke', 'toArray', 'first', 'initial', 'rest',
196
+ 'last', 'without', 'isEmpty', 'pluck'];
197
+
198
+ _.each(methods, function(method) {
199
+ Container.prototype[method] = function() {
200
+ var views = _.values(this._views);
201
+ var args = [views].concat(_.toArray(arguments));
202
+ return _[method].apply(_, args);
203
+ };
204
+ });
205
+
206
+ // return the public API
207
+ return Container;
208
+ })(Backbone, _);
209
+
210
+ // Backbone.Wreqr, v0.1.1
211
+ // Copyright (c)2013 Derick Bailey, Muted Solutions, LLC.
212
+ // Distributed under MIT license
213
+ // http://github.com/marionettejs/backbone.wreqr
214
+ Backbone.Wreqr = (function(Backbone, Marionette, _){
215
+ "use strict";
216
+ var Wreqr = {};
217
+
218
+ // Handlers
219
+ // --------
220
+ // A registry of functions to call, given a name
221
+
222
+ Wreqr.Handlers = (function(Backbone, _){
223
+ "use strict";
224
+
225
+ // Constructor
226
+ // -----------
227
+
228
+ var Handlers = function(){
229
+ this._handlers = {};
230
+ };
231
+
232
+ Handlers.extend = Backbone.Model.extend;
233
+
234
+ // Instance Members
235
+ // ----------------
236
+
237
+ _.extend(Handlers.prototype, {
238
+
239
+ // Add a handler for the given name, with an
240
+ // optional context to run the handler within
241
+ addHandler: function(name, handler, context){
242
+ var config = {
243
+ callback: handler,
244
+ context: context
245
+ };
246
+
247
+ this._handlers[name] = config;
248
+ },
249
+
250
+ // Get the currently registered handler for
251
+ // the specified name. Throws an exception if
252
+ // no handler is found.
253
+ getHandler: function(name){
254
+ var config = this._handlers[name];
255
+
256
+ if (!config){
257
+ throw new Error("Handler not found for '" + name + "'");
258
+ }
259
+
260
+ return function(){
261
+ var args = Array.prototype.slice.apply(arguments);
262
+ return config.callback.apply(config.context, args);
263
+ };
264
+ },
265
+
266
+ // Remove a handler for the specified name
267
+ removeHandler: function(name){
268
+ delete this._handlers[name];
269
+ },
270
+
271
+ // Remove all handlers from this registry
272
+ removeAllHandlers: function(){
273
+ this._handlers = {};
274
+ }
275
+ });
276
+
277
+ return Handlers;
278
+ })(Backbone, _);
279
+
280
+ // Wreqr.Commands
281
+ // --------------
282
+ //
283
+ // A simple command pattern implementation. Register a command
284
+ // handler and execute it.
285
+ Wreqr.Commands = (function(Wreqr){
286
+ "use strict";
287
+
288
+ return Wreqr.Handlers.extend({
289
+ execute: function(){
290
+ var name = arguments[0];
291
+ var args = Array.prototype.slice.call(arguments, 1);
292
+
293
+ this.getHandler(name).apply(this, args);
294
+ }
295
+ });
296
+
297
+ })(Wreqr);
298
+
299
+ // Wreqr.RequestResponse
300
+ // ---------------------
301
+ //
302
+ // A simple request/response implementation. Register a
303
+ // request handler, and return a response from it
304
+ Wreqr.RequestResponse = (function(Wreqr){
305
+ "use strict";
306
+
307
+ return Wreqr.Handlers.extend({
308
+ request: function(){
309
+ var name = arguments[0];
310
+ var args = Array.prototype.slice.call(arguments, 1);
311
+
312
+ return this.getHandler(name).apply(this, args);
313
+ }
314
+ });
315
+
316
+ })(Wreqr);
317
+
318
+ // Event Aggregator
319
+ // ----------------
320
+ // A pub-sub object that can be used to decouple various parts
321
+ // of an application through event-driven architecture.
322
+
323
+ Wreqr.EventAggregator = (function(Backbone, _){
324
+ "use strict";
325
+ var EA = function(){};
326
+
327
+ // Copy the `extend` function used by Backbone's classes
328
+ EA.extend = Backbone.Model.extend;
329
+
330
+ // Copy the basic Backbone.Events on to the event aggregator
331
+ _.extend(EA.prototype, Backbone.Events);
332
+
333
+ return EA;
334
+ })(Backbone, _);
335
+
336
+
337
+ return Wreqr;
338
+ })(Backbone, Backbone.Marionette, _);
339
+
340
+ var Marionette = (function(Backbone, _, $){
341
+ "use strict";
342
+
343
+ var Marionette = {};
344
+ Backbone.Marionette = Marionette;
345
+
346
+ // Helpers
347
+ // -------
348
+
349
+ // For slicing `arguments` in functions
350
+ var slice = Array.prototype.slice;
351
+
352
+ // Marionette.extend
353
+ // -----------------
354
+
355
+ // Borrow the Backbone `extend` method so we can use it as needed
356
+ Marionette.extend = Backbone.Model.extend;
357
+
358
+ // Marionette.getOption
359
+ // --------------------
360
+
361
+ // Retrieve an object, function or other value from a target
362
+ // object or it's `options`, with `options` taking precedence.
363
+ Marionette.getOption = function(target, optionName){
364
+ if (!target || !optionName){ return; }
365
+ var value;
366
+
367
+ if (target.options && (optionName in target.options) && (target.options[optionName] !== undefined)){
368
+ value = target.options[optionName];
369
+ } else {
370
+ value = target[optionName];
371
+ }
372
+
373
+ return value;
374
+ };
375
+
376
+ // Mairionette.createObject
377
+ // ------------------------
378
+
379
+ // A wrapper / shim for `Object.create`. Uses native `Object.create`
380
+ // if available, otherwise shims it in place for Marionette to use.
381
+ Marionette.createObject = (function(){
382
+ var createObject;
383
+
384
+ // Define this once, and just replace the .prototype on it as needed,
385
+ // to improve performance in older / less optimized JS engines
386
+ function F() {}
387
+
388
+
389
+ // Check for existing native / shimmed Object.create
390
+ if (typeof Object.create === "function"){
391
+
392
+ // found native/shim, so use it
393
+ createObject = Object.create;
394
+
395
+ } else {
396
+
397
+ // An implementation of the Boodman/Crockford delegation
398
+ // w/ Cornford optimization, as suggested by @unscriptable
399
+ // https://gist.github.com/3959151
400
+
401
+ // native/shim not found, so shim it ourself
402
+ createObject = function (o) {
403
+
404
+ // set the prototype of the function
405
+ // so we will get `o` as the prototype
406
+ // of the new object instance
407
+ F.prototype = o;
408
+
409
+ // create a new object that inherits from
410
+ // the `o` parameter
411
+ var child = new F();
412
+
413
+ // clean up just in case o is really large
414
+ F.prototype = null;
415
+
416
+ // send it back
417
+ return child;
418
+ };
419
+
420
+ }
421
+
422
+ return createObject;
423
+ })();
424
+
425
+ // Trigger an event and a corresponding method name. Examples:
426
+ //
427
+ // `this.triggerMethod("foo")` will trigger the "foo" event and
428
+ // call the "onFoo" method.
429
+ //
430
+ // `this.triggerMethod("foo:bar") will trigger the "foo:bar" event and
431
+ // call the "onFooBar" method.
432
+ Marionette.triggerMethod = function(){
433
+ var args = Array.prototype.slice.apply(arguments);
434
+ var eventName = args[0];
435
+ var segments = eventName.split(":");
436
+ var segment, capLetter, methodName = "on";
437
+
438
+ for (var i = 0; i < segments.length; i++){
439
+ segment = segments[i];
440
+ capLetter = segment.charAt(0).toUpperCase();
441
+ methodName += capLetter + segment.slice(1);
442
+ }
443
+
444
+ this.trigger.apply(this, args);
445
+
446
+ if (_.isFunction(this[methodName])){
447
+ args.shift();
448
+ return this[methodName].apply(this, args);
449
+ }
450
+ };
451
+
452
+ // DOMRefresh
453
+ // ----------
454
+ //
455
+ // Monitor a view's state, and after it has been rendered and shown
456
+ // in the DOM, trigger a "dom:refresh" event every time it is
457
+ // re-rendered.
458
+
459
+ Marionette.MonitorDOMRefresh = (function(){
460
+ // track when the view has been rendered
461
+ function handleShow(view){
462
+ view._isShown = true;
463
+ triggerDOMRefresh(view);
464
+ }
465
+
466
+ // track when the view has been shown in the DOM,
467
+ // using a Marionette.Region (or by other means of triggering "show")
468
+ function handleRender(view){
469
+ view._isRendered = true;
470
+ triggerDOMRefresh(view);
471
+ }
472
+
473
+ // Trigger the "dom:refresh" event and corresponding "onDomRefresh" method
474
+ function triggerDOMRefresh(view){
475
+ if (view._isShown && view._isRendered){
476
+ if (_.isFunction(view.triggerMethod)){
477
+ view.triggerMethod("dom:refresh");
478
+ }
479
+ }
480
+ }
481
+
482
+ // Export public API
483
+ return function(view){
484
+ view.listenTo(view, "show", function(){
485
+ handleShow(view);
486
+ });
487
+
488
+ view.listenTo(view, "render", function(){
489
+ handleRender(view);
490
+ });
491
+ };
492
+ })();
493
+
494
+
495
+ // Marionette.bindEntityEvents & unbindEntityEvents
496
+ // ---------------------------
497
+ //
498
+ // These methods are used to bind/unbind a backbone "entity" (collection/model)
499
+ // to methods on a target object.
500
+ //
501
+ // The first paremter, `target`, must have a `listenTo` method from the
502
+ // EventBinder object.
503
+ //
504
+ // The second parameter is the entity (Backbone.Model or Backbone.Collection)
505
+ // to bind the events from.
506
+ //
507
+ // The third parameter is a hash of { "event:name": "eventHandler" }
508
+ // configuration. Multiple handlers can be separated by a space. A
509
+ // function can be supplied instead of a string handler name.
510
+
511
+ (function(Marionette){
512
+ "use strict";
513
+
514
+ // Bind the event to handlers specified as a string of
515
+ // handler names on the target object
516
+ function bindFromStrings(target, entity, evt, methods){
517
+ var methodNames = methods.split(/\s+/);
518
+
519
+ _.each(methodNames,function(methodName) {
520
+
521
+ var method = target[methodName];
522
+ if(!method) {
523
+ throw new Error("Method '"+ methodName +"' was configured as an event handler, but does not exist.");
524
+ }
525
+
526
+ target.listenTo(entity, evt, method, target);
527
+ });
528
+ }
529
+
530
+ // Bind the event to a supplied callback function
531
+ function bindToFunction(target, entity, evt, method){
532
+ target.listenTo(entity, evt, method, target);
533
+ }
534
+
535
+ // Bind the event to handlers specified as a string of
536
+ // handler names on the target object
537
+ function unbindFromStrings(target, entity, evt, methods){
538
+ var methodNames = methods.split(/\s+/);
539
+
540
+ _.each(methodNames,function(methodName) {
541
+ var method = target[method];
542
+ target.stopListening(entity, evt, method, target);
543
+ });
544
+ }
545
+
546
+ // Bind the event to a supplied callback function
547
+ function unbindToFunction(target, entity, evt, method){
548
+ target.stopListening(entity, evt, method, target);
549
+ }
550
+
551
+
552
+ // generic looping function
553
+ function iterateEvents(target, entity, bindings, functionCallback, stringCallback){
554
+ if (!entity || !bindings) { return; }
555
+
556
+ // allow the bindings to be a function
557
+ if (_.isFunction(bindings)){
558
+ bindings = bindings.call(target);
559
+ }
560
+
561
+ // iterate the bindings and bind them
562
+ _.each(bindings, function(methods, evt){
563
+
564
+ // allow for a function as the handler,
565
+ // or a list of event names as a string
566
+ if (_.isFunction(methods)){
567
+ functionCallback(target, entity, evt, methods);
568
+ } else {
569
+ stringCallback(target, entity, evt, methods);
570
+ }
571
+
572
+ });
573
+ }
574
+
575
+ // Export Public API
576
+ Marionette.bindEntityEvents = function(target, entity, bindings){
577
+ iterateEvents(target, entity, bindings, bindToFunction, bindFromStrings);
578
+ };
579
+
580
+ Marionette.unbindEntityEvents = function(target, entity, bindings){
581
+ iterateEvents(target, entity, bindings, unbindToFunction, unbindFromStrings);
582
+ };
583
+
584
+ })(Marionette);
585
+
586
+
587
+ // Callbacks
588
+ // ---------
589
+
590
+ // A simple way of managing a collection of callbacks
591
+ // and executing them at a later point in time, using jQuery's
592
+ // `Deferred` object.
593
+ Marionette.Callbacks = function(){
594
+ this._deferred = $.Deferred();
595
+ this._callbacks = [];
596
+ };
597
+
598
+ _.extend(Marionette.Callbacks.prototype, {
599
+
600
+ // Add a callback to be executed. Callbacks added here are
601
+ // guaranteed to execute, even if they are added after the
602
+ // `run` method is called.
603
+ add: function(callback, contextOverride){
604
+ this._callbacks.push({cb: callback, ctx: contextOverride});
605
+
606
+ this._deferred.done(function(context, options){
607
+ if (contextOverride){ context = contextOverride; }
608
+ callback.call(context, options);
609
+ });
610
+ },
611
+
612
+ // Run all registered callbacks with the context specified.
613
+ // Additional callbacks can be added after this has been run
614
+ // and they will still be executed.
615
+ run: function(options, context){
616
+ this._deferred.resolve(context, options);
617
+ },
618
+
619
+ // Resets the list of callbacks to be run, allowing the same list
620
+ // to be run multiple times - whenever the `run` method is called.
621
+ reset: function(){
622
+ var that = this;
623
+ var callbacks = this._callbacks;
624
+ this._deferred = $.Deferred();
625
+ this._callbacks = [];
626
+ _.each(callbacks, function(cb){
627
+ that.add(cb.cb, cb.ctx);
628
+ });
629
+ }
630
+ });
631
+
632
+
633
+ // Marionette Controller
634
+ // ---------------------
635
+ //
636
+ // A multi-purpose object to use as a controller for
637
+ // modules and routers, and as a mediator for workflow
638
+ // and coordination of other objects, views, and more.
639
+ Marionette.Controller = function(options){
640
+ this.triggerMethod = Marionette.triggerMethod;
641
+ this.options = options || {};
642
+
643
+ if (_.isFunction(this.initialize)){
644
+ this.initialize(this.options);
645
+ }
646
+ };
647
+
648
+ Marionette.Controller.extend = Marionette.extend;
649
+
650
+ // Controller Methods
651
+ // --------------
652
+
653
+ // Ensure it can trigger events with Backbone.Events
654
+ _.extend(Marionette.Controller.prototype, Backbone.Events, {
655
+ close: function(){
656
+ this.stopListening();
657
+ this.triggerMethod("close");
658
+ this.unbind();
659
+ }
660
+ });
661
+
662
+ // Region
663
+ // ------
664
+ //
665
+ // Manage the visual regions of your composite application. See
666
+ // http://lostechies.com/derickbailey/2011/12/12/composite-js-apps-regions-and-region-managers/
667
+
668
+ Marionette.Region = function(options){
669
+ this.options = options || {};
670
+
671
+ this.el = Marionette.getOption(this, "el");
672
+
673
+ if (!this.el){
674
+ var err = new Error("An 'el' must be specified for a region.");
675
+ err.name = "NoElError";
676
+ throw err;
677
+ }
678
+
679
+ if (this.initialize){
680
+ var args = Array.prototype.slice.apply(arguments);
681
+ this.initialize.apply(this, args);
682
+ }
683
+ };
684
+
685
+
686
+ // Region Type methods
687
+ // -------------------
688
+
689
+ _.extend(Marionette.Region, {
690
+
691
+ // Build an instance of a region by passing in a configuration object
692
+ // and a default region type to use if none is specified in the config.
693
+ //
694
+ // The config object should either be a string as a jQuery DOM selector,
695
+ // a Region type directly, or an object literal that specifies both
696
+ // a selector and regionType:
697
+ //
698
+ // ```js
699
+ // {
700
+ // selector: "#foo",
701
+ // regionType: MyCustomRegion
702
+ // }
703
+ // ```
704
+ //
705
+ buildRegion: function(regionConfig, defaultRegionType){
706
+ var regionIsString = (typeof regionConfig === "string");
707
+ var regionSelectorIsString = (typeof regionConfig.selector === "string");
708
+ var regionTypeIsUndefined = (typeof regionConfig.regionType === "undefined");
709
+ var regionIsType = (typeof regionConfig === "function");
710
+
711
+ if (!regionIsType && !regionIsString && !regionSelectorIsString) {
712
+ throw new Error("Region must be specified as a Region type, a selector string or an object with selector property");
713
+ }
714
+
715
+ var selector, RegionType;
716
+
717
+ // get the selector for the region
718
+
719
+ if (regionIsString) {
720
+ selector = regionConfig;
721
+ }
722
+
723
+ if (regionConfig.selector) {
724
+ selector = regionConfig.selector;
725
+ }
726
+
727
+ // get the type for the region
728
+
729
+ if (regionIsType){
730
+ RegionType = regionConfig;
731
+ }
732
+
733
+ if (!regionIsType && regionTypeIsUndefined) {
734
+ RegionType = defaultRegionType;
735
+ }
736
+
737
+ if (regionConfig.regionType) {
738
+ RegionType = regionConfig.regionType;
739
+ }
740
+
741
+ // build the region instance
742
+
743
+ var regionManager = new RegionType({
744
+ el: selector
745
+ });
746
+
747
+ return regionManager;
748
+ }
749
+
750
+ });
751
+
752
+ // Region Instance Methods
753
+ // -----------------------
754
+
755
+ _.extend(Marionette.Region.prototype, Backbone.Events, {
756
+
757
+ // Displays a backbone view instance inside of the region.
758
+ // Handles calling the `render` method for you. Reads content
759
+ // directly from the `el` attribute. Also calls an optional
760
+ // `onShow` and `close` method on your view, just after showing
761
+ // or just before closing the view, respectively.
762
+ show: function(view){
763
+
764
+ this.ensureEl();
765
+ this.close();
766
+
767
+ view.render();
768
+ this.open(view);
769
+
770
+ Marionette.triggerMethod.call(view, "show");
771
+ Marionette.triggerMethod.call(this, "show", view);
772
+
773
+ this.currentView = view;
774
+ },
775
+
776
+ ensureEl: function(){
777
+ if (!this.$el || this.$el.length === 0){
778
+ this.$el = this.getEl(this.el);
779
+ }
780
+ },
781
+
782
+ // Override this method to change how the region finds the
783
+ // DOM element that it manages. Return a jQuery selector object.
784
+ getEl: function(selector){
785
+ return $(selector);
786
+ },
787
+
788
+ // Override this method to change how the new view is
789
+ // appended to the `$el` that the region is managing
790
+ open: function(view){
791
+ this.$el.empty().append(view.el);
792
+ },
793
+
794
+ // Close the current view, if there is one. If there is no
795
+ // current view, it does nothing and returns immediately.
796
+ close: function(){
797
+ var view = this.currentView;
798
+ if (!view || view.isClosed){ return; }
799
+
800
+ if (view.close) { view.close(); }
801
+ Marionette.triggerMethod.call(this, "close");
802
+
803
+ delete this.currentView;
804
+ },
805
+
806
+ // Attach an existing view to the region. This
807
+ // will not call `render` or `onShow` for the new view,
808
+ // and will not replace the current HTML for the `el`
809
+ // of the region.
810
+ attachView: function(view){
811
+ this.currentView = view;
812
+ },
813
+
814
+ // Reset the region by closing any existing view and
815
+ // clearing out the cached `$el`. The next time a view
816
+ // is shown via this region, the region will re-query the
817
+ // DOM for the region's `el`.
818
+ reset: function(){
819
+ this.close();
820
+ delete this.$el;
821
+ }
822
+ });
823
+
824
+ // Copy the `extend` function used by Backbone's classes
825
+ Marionette.Region.extend = Marionette.extend;
826
+
827
+
828
+ // Template Cache
829
+ // --------------
830
+
831
+ // Manage templates stored in `<script>` blocks,
832
+ // caching them for faster access.
833
+ Marionette.TemplateCache = function(templateId){
834
+ this.templateId = templateId;
835
+ };
836
+
837
+ // TemplateCache object-level methods. Manage the template
838
+ // caches from these method calls instead of creating
839
+ // your own TemplateCache instances
840
+ _.extend(Marionette.TemplateCache, {
841
+ templateCaches: {},
842
+
843
+ // Get the specified template by id. Either
844
+ // retrieves the cached version, or loads it
845
+ // from the DOM.
846
+ get: function(templateId){
847
+ var that = this;
848
+ var cachedTemplate = this.templateCaches[templateId];
849
+
850
+ if (!cachedTemplate){
851
+ cachedTemplate = new Marionette.TemplateCache(templateId);
852
+ this.templateCaches[templateId] = cachedTemplate;
853
+ }
854
+
855
+ return cachedTemplate.load();
856
+ },
857
+
858
+ // Clear templates from the cache. If no arguments
859
+ // are specified, clears all templates:
860
+ // `clear()`
861
+ //
862
+ // If arguments are specified, clears each of the
863
+ // specified templates from the cache:
864
+ // `clear("#t1", "#t2", "...")`
865
+ clear: function(){
866
+ var i;
867
+ var args = Array.prototype.slice.apply(arguments);
868
+ var length = args.length;
869
+
870
+ if (length > 0){
871
+ for(i=0; i<length; i++){
872
+ delete this.templateCaches[args[i]];
873
+ }
874
+ } else {
875
+ this.templateCaches = {};
876
+ }
877
+ }
878
+ });
879
+
880
+ // TemplateCache instance methods, allowing each
881
+ // template cache object to manage it's own state
882
+ // and know whether or not it has been loaded
883
+ _.extend(Marionette.TemplateCache.prototype, {
884
+
885
+ // Internal method to load the template
886
+ load: function(){
887
+ var that = this;
888
+
889
+ // Guard clause to prevent loading this template more than once
890
+ if (this.compiledTemplate){
891
+ return this.compiledTemplate;
892
+ }
893
+
894
+ // Load the template and compile it
895
+ var template = this.loadTemplate(this.templateId);
896
+ this.compiledTemplate = this.compileTemplate(template);
897
+
898
+ return this.compiledTemplate;
899
+ },
900
+
901
+ // Load a template from the DOM, by default. Override
902
+ // this method to provide your own template retrieval
903
+ // For asynchronous loading with AMD/RequireJS, consider
904
+ // using a template-loader plugin as described here:
905
+ // https://github.com/marionettejs/backbone.marionette/wiki/Using-marionette-with-requirejs
906
+ loadTemplate: function(templateId){
907
+ var template = $(templateId).html();
908
+
909
+ if (!template || template.length === 0){
910
+ var msg = "Could not find template: '" + templateId + "'";
911
+ var err = new Error(msg);
912
+ err.name = "NoTemplateError";
913
+ throw err;
914
+ }
915
+
916
+ return template;
917
+ },
918
+
919
+ // Pre-compile the template before caching it. Override
920
+ // this method if you do not need to pre-compile a template
921
+ // (JST / RequireJS for example) or if you want to change
922
+ // the template engine used (Handebars, etc).
923
+ compileTemplate: function(rawTemplate){
924
+ return _.template(rawTemplate);
925
+ }
926
+ });
927
+
928
+
929
+ // Renderer
930
+ // --------
931
+
932
+ // Render a template with data by passing in the template
933
+ // selector and the data to render.
934
+ Marionette.Renderer = {
935
+
936
+ // Render a template with data. The `template` parameter is
937
+ // passed to the `TemplateCache` object to retrieve the
938
+ // template function. Override this method to provide your own
939
+ // custom rendering and template handling for all of Marionette.
940
+ render: function(template, data){
941
+ var templateFunc = typeof template === 'function' ? template : Marionette.TemplateCache.get(template);
942
+ var html = templateFunc(data);
943
+ return html;
944
+ }
945
+ };
946
+
947
+
948
+
949
+ // Marionette.View
950
+ // ---------------
951
+
952
+ // The core view type that other Marionette views extend from.
953
+ Marionette.View = Backbone.View.extend({
954
+
955
+ constructor: function(){
956
+ _.bindAll(this, "render");
957
+
958
+ var args = Array.prototype.slice.apply(arguments);
959
+ Backbone.View.prototype.constructor.apply(this, args);
960
+
961
+ Marionette.MonitorDOMRefresh(this);
962
+ this.listenTo(this, "show", this.onShowCalled, this);
963
+ },
964
+
965
+ // import the "triggerMethod" to trigger events with corresponding
966
+ // methods if the method exists
967
+ triggerMethod: Marionette.triggerMethod,
968
+
969
+ // Get the template for this view
970
+ // instance. You can set a `template` attribute in the view
971
+ // definition or pass a `template: "whatever"` parameter in
972
+ // to the constructor options.
973
+ getTemplate: function(){
974
+ return Marionette.getOption(this, "template");
975
+ },
976
+
977
+ // Mix in template helper methods. Looks for a
978
+ // `templateHelpers` attribute, which can either be an
979
+ // object literal, or a function that returns an object
980
+ // literal. All methods and attributes from this object
981
+ // are copies to the object passed in.
982
+ mixinTemplateHelpers: function(target){
983
+ target = target || {};
984
+ var templateHelpers = this.templateHelpers;
985
+ if (_.isFunction(templateHelpers)){
986
+ templateHelpers = templateHelpers.call(this);
987
+ }
988
+ return _.extend(target, templateHelpers);
989
+ },
990
+
991
+ // Configure `triggers` to forward DOM events to view
992
+ // events. `triggers: {"click .foo": "do:foo"}`
993
+ configureTriggers: function(){
994
+ if (!this.triggers) { return; }
995
+
996
+ var that = this;
997
+ var triggerEvents = {};
998
+
999
+ // Allow `triggers` to be configured as a function
1000
+ var triggers = _.result(this, "triggers");
1001
+
1002
+ // Configure the triggers, prevent default
1003
+ // action and stop propagation of DOM events
1004
+ _.each(triggers, function(value, key){
1005
+
1006
+ // build the event handler function for the DOM event
1007
+ triggerEvents[key] = function(e){
1008
+
1009
+ // stop the event in it's tracks
1010
+ if (e && e.preventDefault){ e.preventDefault(); }
1011
+ if (e && e.stopPropagation){ e.stopPropagation(); }
1012
+
1013
+ // buil the args for the event
1014
+ var args = {
1015
+ view: this,
1016
+ model: this.model,
1017
+ collection: this.collection
1018
+ };
1019
+
1020
+ // trigger the event
1021
+ that.triggerMethod(value, args);
1022
+ };
1023
+
1024
+ });
1025
+
1026
+ return triggerEvents;
1027
+ },
1028
+
1029
+ // Overriding Backbone.View's delegateEvents to handle
1030
+ // the `triggers`, `modelEvents`, and `collectionEvents` configuration
1031
+ delegateEvents: function(events){
1032
+ this._delegateDOMEvents(events);
1033
+ Marionette.bindEntityEvents(this, this.model, Marionette.getOption(this, "modelEvents"));
1034
+ Marionette.bindEntityEvents(this, this.collection, Marionette.getOption(this, "collectionEvents"));
1035
+ },
1036
+
1037
+ // internal method to delegate DOM events and triggers
1038
+ _delegateDOMEvents: function(events){
1039
+ events = events || this.events;
1040
+ if (_.isFunction(events)){ events = events.call(this); }
1041
+
1042
+ var combinedEvents = {};
1043
+ var triggers = this.configureTriggers();
1044
+ _.extend(combinedEvents, events, triggers);
1045
+
1046
+ Backbone.View.prototype.delegateEvents.call(this, combinedEvents);
1047
+ },
1048
+
1049
+ // Overriding Backbone.View's undelegateEvents to handle unbinding
1050
+ // the `triggers`, `modelEvents`, and `collectionEvents` config
1051
+ undelegateEvents: function(){
1052
+ var args = Array.prototype.slice.call(arguments);
1053
+ Backbone.View.prototype.undelegateEvents.apply(this, args);
1054
+
1055
+ Marionette.unbindEntityEvents(this, this.model, Marionette.getOption(this, "modelEvents"));
1056
+ Marionette.unbindEntityEvents(this, this.collection, Marionette.getOption(this, "collectionEvents"));
1057
+ },
1058
+
1059
+ // Internal method, handles the `show` event.
1060
+ onShowCalled: function(){},
1061
+
1062
+ // Default `close` implementation, for removing a view from the
1063
+ // DOM and unbinding it. Regions will call this method
1064
+ // for you. You can specify an `onClose` method in your view to
1065
+ // add custom code that is called after the view is closed.
1066
+ close: function(){
1067
+ if (this.isClosed) { return; }
1068
+
1069
+ // allow the close to be stopped by returning `false`
1070
+ // from the `onBeforeClose` method
1071
+ var shouldClose = this.triggerMethod("before:close");
1072
+ if (shouldClose === false){
1073
+ return;
1074
+ }
1075
+
1076
+ // mark as closed before doing the actual close, to
1077
+ // prevent infinite loops within "close" event handlers
1078
+ // that are trying to close other views
1079
+ this.isClosed = true;
1080
+ this.triggerMethod("close");
1081
+
1082
+ this.remove();
1083
+ },
1084
+
1085
+ // This method binds the elements specified in the "ui" hash inside the view's code with
1086
+ // the associated jQuery selectors.
1087
+ bindUIElements: function(){
1088
+ if (!this.ui) { return; }
1089
+
1090
+ var that = this;
1091
+
1092
+ if (!this.uiBindings) {
1093
+ // We want to store the ui hash in uiBindings, since afterwards the values in the ui hash
1094
+ // will be overridden with jQuery selectors.
1095
+ this.uiBindings = _.result(this, "ui");
1096
+ }
1097
+
1098
+ // refreshing the associated selectors since they should point to the newly rendered elements.
1099
+ this.ui = {};
1100
+ _.each(_.keys(this.uiBindings), function(key) {
1101
+ var selector = that.uiBindings[key];
1102
+ that.ui[key] = that.$(selector);
1103
+ });
1104
+ }
1105
+ });
1106
+
1107
+ // Item View
1108
+ // ---------
1109
+
1110
+ // A single item view implementation that contains code for rendering
1111
+ // with underscore.js templates, serializing the view's model or collection,
1112
+ // and calling several methods on extended views, such as `onRender`.
1113
+ Marionette.ItemView = Marionette.View.extend({
1114
+ constructor: function(){
1115
+ var args = Array.prototype.slice.apply(arguments);
1116
+ Marionette.View.prototype.constructor.apply(this, args);
1117
+ },
1118
+
1119
+ // Serialize the model or collection for the view. If a model is
1120
+ // found, `.toJSON()` is called. If a collection is found, `.toJSON()`
1121
+ // is also called, but is used to populate an `items` array in the
1122
+ // resulting data. If both are found, defaults to the model.
1123
+ // You can override the `serializeData` method in your own view
1124
+ // definition, to provide custom serialization for your view's data.
1125
+ serializeData: function(){
1126
+ var data = {};
1127
+
1128
+ if (this.model) {
1129
+ data = this.model.toJSON();
1130
+ }
1131
+ else if (this.collection) {
1132
+ data = { items: this.collection.toJSON() };
1133
+ }
1134
+
1135
+ return data;
1136
+ },
1137
+
1138
+ // Render the view, defaulting to underscore.js templates.
1139
+ // You can override this in your view definition to provide
1140
+ // a very specific rendering for your view. In general, though,
1141
+ // you should override the `Marionette.Renderer` object to
1142
+ // change how Marionette renders views.
1143
+ render: function(){
1144
+ this.isClosed = false;
1145
+
1146
+ this.triggerMethod("before:render", this);
1147
+ this.triggerMethod("item:before:render", this);
1148
+
1149
+ var data = this.serializeData();
1150
+ data = this.mixinTemplateHelpers(data);
1151
+
1152
+ var template = this.getTemplate();
1153
+ var html = Marionette.Renderer.render(template, data);
1154
+ this.$el.html(html);
1155
+ this.bindUIElements();
1156
+
1157
+ this.triggerMethod("render", this);
1158
+ this.triggerMethod("item:rendered", this);
1159
+
1160
+ return this;
1161
+ },
1162
+
1163
+ // Override the default close event to add a few
1164
+ // more events that are triggered.
1165
+ close: function(){
1166
+ if (this.isClosed){ return; }
1167
+
1168
+ this.triggerMethod('item:before:close');
1169
+
1170
+ var args = Array.prototype.slice.apply(arguments);
1171
+ Marionette.View.prototype.close.apply(this, args);
1172
+
1173
+ this.triggerMethod('item:closed');
1174
+ }
1175
+ });
1176
+
1177
+ // Collection View
1178
+ // ---------------
1179
+
1180
+ // A view that iterates over a Backbone.Collection
1181
+ // and renders an individual ItemView for each model.
1182
+ Marionette.CollectionView = Marionette.View.extend({
1183
+ // used as the prefix for item view events
1184
+ // that are forwarded through the collectionview
1185
+ itemViewEventPrefix: "itemview",
1186
+
1187
+ // constructor
1188
+ constructor: function(options){
1189
+ this._initChildViewStorage();
1190
+
1191
+ var args = Array.prototype.slice.apply(arguments);
1192
+ Marionette.View.prototype.constructor.apply(this, args);
1193
+
1194
+ this._initialEvents();
1195
+ },
1196
+
1197
+ // Configured the initial events that the collection view
1198
+ // binds to. Override this method to prevent the initial
1199
+ // events, or to add your own initial events.
1200
+ _initialEvents: function(){
1201
+ if (this.collection){
1202
+ this.listenTo(this.collection, "add", this.addChildView, this);
1203
+ this.listenTo(this.collection, "remove", this.removeItemView, this);
1204
+ this.listenTo(this.collection, "reset", this.render, this);
1205
+ }
1206
+ },
1207
+
1208
+ // Handle a child item added to the collection
1209
+ addChildView: function(item, collection, options){
1210
+ this.closeEmptyView();
1211
+ var ItemView = this.getItemView(item);
1212
+ var index = this.collection.indexOf(item);
1213
+ this.addItemView(item, ItemView, index);
1214
+ },
1215
+
1216
+ // Override from `Marionette.View` to guarantee the `onShow` method
1217
+ // of child views is called.
1218
+ onShowCalled: function(){
1219
+ this.children.each(function(child){
1220
+ Marionette.triggerMethod.call(child, "show");
1221
+ });
1222
+ },
1223
+
1224
+ // Internal method to trigger the before render callbacks
1225
+ // and events
1226
+ triggerBeforeRender: function(){
1227
+ this.triggerMethod("before:render", this);
1228
+ this.triggerMethod("collection:before:render", this);
1229
+ },
1230
+
1231
+ // Internal method to trigger the rendered callbacks and
1232
+ // events
1233
+ triggerRendered: function(){
1234
+ this.triggerMethod("render", this);
1235
+ this.triggerMethod("collection:rendered", this);
1236
+ },
1237
+
1238
+ // Render the collection of items. Override this method to
1239
+ // provide your own implementation of a render function for
1240
+ // the collection view.
1241
+ render: function(){
1242
+ this.isClosed = false;
1243
+
1244
+ this.triggerBeforeRender();
1245
+ this.closeEmptyView();
1246
+ this.closeChildren();
1247
+
1248
+ if (this.collection && this.collection.length > 0) {
1249
+ this.showCollection();
1250
+ } else {
1251
+ this.showEmptyView();
1252
+ }
1253
+
1254
+ this.triggerRendered();
1255
+ return this;
1256
+ },
1257
+
1258
+ // Internal method to loop through each item in the
1259
+ // collection view and show it
1260
+ showCollection: function(){
1261
+ var that = this;
1262
+ var ItemView;
1263
+ this.collection.each(function(item, index){
1264
+ ItemView = that.getItemView(item);
1265
+ that.addItemView(item, ItemView, index);
1266
+ });
1267
+ },
1268
+
1269
+ // Internal method to show an empty view in place of
1270
+ // a collection of item views, when the collection is
1271
+ // empty
1272
+ showEmptyView: function(){
1273
+ var EmptyView = Marionette.getOption(this, "emptyView");
1274
+
1275
+ if (EmptyView && !this._showingEmptyView){
1276
+ this._showingEmptyView = true;
1277
+ var model = new Backbone.Model();
1278
+ this.addItemView(model, EmptyView, 0);
1279
+ }
1280
+ },
1281
+
1282
+ // Internal method to close an existing emptyView instance
1283
+ // if one exists. Called when a collection view has been
1284
+ // rendered empty, and then an item is added to the collection.
1285
+ closeEmptyView: function(){
1286
+ if (this._showingEmptyView){
1287
+ this.closeChildren();
1288
+ delete this._showingEmptyView;
1289
+ }
1290
+ },
1291
+
1292
+ // Retrieve the itemView type, either from `this.options.itemView`
1293
+ // or from the `itemView` in the object definition. The "options"
1294
+ // takes precedence.
1295
+ getItemView: function(item){
1296
+ var itemView = Marionette.getOption(this, "itemView");
1297
+
1298
+ if (!itemView){
1299
+ var err = new Error("An `itemView` must be specified");
1300
+ err.name = "NoItemViewError";
1301
+ throw err;
1302
+ }
1303
+
1304
+ return itemView;
1305
+ },
1306
+
1307
+ // Render the child item's view and add it to the
1308
+ // HTML for the collection view.
1309
+ addItemView: function(item, ItemView, index){
1310
+ var that = this;
1311
+
1312
+ // get the itemViewOptions if any were specified
1313
+ var itemViewOptions = Marionette.getOption(this, "itemViewOptions");
1314
+ if (_.isFunction(itemViewOptions)){
1315
+ itemViewOptions = itemViewOptions.call(this, item);
1316
+ }
1317
+
1318
+ // build the view
1319
+ var view = this.buildItemView(item, ItemView, itemViewOptions);
1320
+
1321
+ // set up the child view event forwarding
1322
+ this.addChildViewEventForwarding(view);
1323
+
1324
+ // this view is about to be added
1325
+ this.triggerMethod("before:item:added", view);
1326
+
1327
+ // Store the child view itself so we can properly
1328
+ // remove and/or close it later
1329
+ this.children.add(view);
1330
+
1331
+ // Render it and show it
1332
+ this.renderItemView(view, index);
1333
+
1334
+ // call the "show" method if the collection view
1335
+ // has already been shown
1336
+ if (this._isShown){
1337
+ Marionette.triggerMethod.call(view, "show");
1338
+ }
1339
+
1340
+ // this view was added
1341
+ this.triggerMethod("after:item:added", view);
1342
+ },
1343
+
1344
+ // Set up the child view event forwarding. Uses an "itemview:"
1345
+ // prefix in front of all forwarded events.
1346
+ addChildViewEventForwarding: function(view){
1347
+ var prefix = Marionette.getOption(this, "itemViewEventPrefix");
1348
+
1349
+ // Forward all child item view events through the parent,
1350
+ // prepending "itemview:" to the event name
1351
+ this.listenTo(view, "all", function(){
1352
+ var args = slice.call(arguments);
1353
+ args[0] = prefix + ":" + args[0];
1354
+ args.splice(1, 0, view);
1355
+
1356
+ Marionette.triggerMethod.apply(this, args);
1357
+ }, this);
1358
+ },
1359
+
1360
+ // render the item view
1361
+ renderItemView: function(view, index) {
1362
+ view.render();
1363
+ this.appendHtml(this, view, index);
1364
+ },
1365
+
1366
+ // Build an `itemView` for every model in the collection.
1367
+ buildItemView: function(item, ItemViewType, itemViewOptions){
1368
+ var options = _.extend({model: item}, itemViewOptions);
1369
+ var view = new ItemViewType(options);
1370
+ return view;
1371
+ },
1372
+
1373
+ // get the child view by item it holds, and remove it
1374
+ removeItemView: function(item){
1375
+ var view = this.children.findByModel(item);
1376
+ this.removeChildView(view);
1377
+ this.checkEmpty();
1378
+ },
1379
+
1380
+ // Remove the child view and close it
1381
+ removeChildView: function(view){
1382
+
1383
+ // shut down the child view properly,
1384
+ // including events that the collection has from it
1385
+ if (view){
1386
+ this.stopListening(view);
1387
+
1388
+ if (view.close){
1389
+ view.close();
1390
+ }
1391
+
1392
+ this.children.remove(view);
1393
+ }
1394
+
1395
+ this.triggerMethod("item:removed", view);
1396
+ },
1397
+
1398
+ // helper to show the empty view if the collection is empty
1399
+ checkEmpty: function() {
1400
+ // check if we're empty now, and if we are, show the
1401
+ // empty view
1402
+ if (!this.collection || this.collection.length === 0){
1403
+ this.showEmptyView();
1404
+ }
1405
+ },
1406
+
1407
+ // Append the HTML to the collection's `el`.
1408
+ // Override this method to do something other
1409
+ // then `.append`.
1410
+ appendHtml: function(collectionView, itemView, index){
1411
+ collectionView.$el.append(itemView.el);
1412
+ },
1413
+
1414
+ // Internal method to set up the `children` object for
1415
+ // storing all of the child views
1416
+ _initChildViewStorage: function(){
1417
+ this.children = new Backbone.ChildViewContainer();
1418
+ },
1419
+
1420
+ // Handle cleanup and other closing needs for
1421
+ // the collection of views.
1422
+ close: function(){
1423
+ if (this.isClosed){ return; }
1424
+
1425
+ this.triggerMethod("collection:before:close");
1426
+ this.closeChildren();
1427
+ this.triggerMethod("collection:closed");
1428
+
1429
+ var args = Array.prototype.slice.apply(arguments);
1430
+ Marionette.View.prototype.close.apply(this, args);
1431
+ },
1432
+
1433
+ // Close the child views that this collection view
1434
+ // is holding on to, if any
1435
+ closeChildren: function(){
1436
+ this.children.each(function(child){
1437
+ this.removeChildView(child);
1438
+ }, this);
1439
+ this.checkEmpty();
1440
+ }
1441
+ });
1442
+
1443
+
1444
+ // Composite View
1445
+ // --------------
1446
+
1447
+ // Used for rendering a branch-leaf, hierarchical structure.
1448
+ // Extends directly from CollectionView and also renders an
1449
+ // an item view as `modelView`, for the top leaf
1450
+ Marionette.CompositeView = Marionette.CollectionView.extend({
1451
+ constructor: function(options){
1452
+ var args = Array.prototype.slice.apply(arguments);
1453
+ Marionette.CollectionView.apply(this, args);
1454
+
1455
+ this.itemView = this.getItemView();
1456
+ },
1457
+
1458
+ // Configured the initial events that the composite view
1459
+ // binds to. Override this method to prevent the initial
1460
+ // events, or to add your own initial events.
1461
+ _initialEvents: function(){
1462
+ if (this.collection){
1463
+ this.listenTo(this.collection, "add", this.addChildView, this);
1464
+ this.listenTo(this.collection, "remove", this.removeItemView, this);
1465
+ this.listenTo(this.collection, "reset", this.renderCollection, this);
1466
+ }
1467
+ },
1468
+
1469
+ // Retrieve the `itemView` to be used when rendering each of
1470
+ // the items in the collection. The default is to return
1471
+ // `this.itemView` or Marionette.CompositeView if no `itemView`
1472
+ // has been defined
1473
+ getItemView: function(item){
1474
+ var itemView = Marionette.getOption(this, "itemView") || this.constructor;
1475
+
1476
+ if (!itemView){
1477
+ var err = new Error("An `itemView` must be specified");
1478
+ err.name = "NoItemViewError";
1479
+ throw err;
1480
+ }
1481
+
1482
+ return itemView;
1483
+ },
1484
+
1485
+ // Serialize the collection for the view.
1486
+ // You can override the `serializeData` method in your own view
1487
+ // definition, to provide custom serialization for your view's data.
1488
+ serializeData: function(){
1489
+ var data = {};
1490
+
1491
+ if (this.model){
1492
+ data = this.model.toJSON();
1493
+ }
1494
+
1495
+ return data;
1496
+ },
1497
+
1498
+ // Renders the model once, and the collection once. Calling
1499
+ // this again will tell the model's view to re-render itself
1500
+ // but the collection will not re-render.
1501
+ render: function(){
1502
+ this.isClosed = false;
1503
+
1504
+ this.resetItemViewContainer();
1505
+
1506
+ var html = this.renderModel();
1507
+ this.$el.html(html);
1508
+
1509
+ // the ui bindings is done here and not at the end of render since they
1510
+ // will not be available until after the model is rendered, but should be
1511
+ // available before the collection is rendered.
1512
+ this.bindUIElements();
1513
+
1514
+ this.triggerMethod("composite:model:rendered");
1515
+
1516
+ this.renderCollection();
1517
+ this.triggerMethod("composite:rendered");
1518
+ return this;
1519
+ },
1520
+
1521
+ // Render the collection for the composite view
1522
+ renderCollection: function(){
1523
+ var args = Array.prototype.slice.apply(arguments);
1524
+ Marionette.CollectionView.prototype.render.apply(this, args);
1525
+
1526
+ this.triggerMethod("composite:collection:rendered");
1527
+ },
1528
+
1529
+ // Render an individual model, if we have one, as
1530
+ // part of a composite view (branch / leaf). For example:
1531
+ // a treeview.
1532
+ renderModel: function(){
1533
+ var data = {};
1534
+ data = this.serializeData();
1535
+ data = this.mixinTemplateHelpers(data);
1536
+
1537
+ var template = this.getTemplate();
1538
+ return Marionette.Renderer.render(template, data);
1539
+ },
1540
+
1541
+ // Appends the `el` of itemView instances to the specified
1542
+ // `itemViewContainer` (a jQuery selector). Override this method to
1543
+ // provide custom logic of how the child item view instances have their
1544
+ // HTML appended to the composite view instance.
1545
+ appendHtml: function(cv, iv){
1546
+ var $container = this.getItemViewContainer(cv);
1547
+ $container.append(iv.el);
1548
+ },
1549
+
1550
+ // Internal method to ensure an `$itemViewContainer` exists, for the
1551
+ // `appendHtml` method to use.
1552
+ getItemViewContainer: function(containerView){
1553
+ if ("$itemViewContainer" in containerView){
1554
+ return containerView.$itemViewContainer;
1555
+ }
1556
+
1557
+ var container;
1558
+ if (containerView.itemViewContainer){
1559
+
1560
+ var selector = _.result(containerView, "itemViewContainer");
1561
+ container = containerView.$(selector);
1562
+ if (container.length <= 0) {
1563
+ var err = new Error("The specified `itemViewContainer` was not found: " + containerView.itemViewContainer);
1564
+ err.name = "ItemViewContainerMissingError";
1565
+ throw err;
1566
+ }
1567
+
1568
+ } else {
1569
+ container = containerView.$el;
1570
+ }
1571
+
1572
+ containerView.$itemViewContainer = container;
1573
+ return container;
1574
+ },
1575
+
1576
+ // Internal method to reset the `$itemViewContainer` on render
1577
+ resetItemViewContainer: function(){
1578
+ if (this.$itemViewContainer){
1579
+ delete this.$itemViewContainer;
1580
+ }
1581
+ }
1582
+ });
1583
+
1584
+
1585
+ // Layout
1586
+ // ------
1587
+
1588
+ // Used for managing application layouts, nested layouts and
1589
+ // multiple regions within an application or sub-application.
1590
+ //
1591
+ // A specialized view type that renders an area of HTML and then
1592
+ // attaches `Region` instances to the specified `regions`.
1593
+ // Used for composite view management and sub-application areas.
1594
+ Marionette.Layout = Marionette.ItemView.extend({
1595
+ regionType: Marionette.Region,
1596
+
1597
+ // Ensure the regions are avialable when the `initialize` method
1598
+ // is called.
1599
+ constructor: function () {
1600
+ this._firstRender = true;
1601
+ this.initializeRegions();
1602
+
1603
+ var args = Array.prototype.slice.apply(arguments);
1604
+ Marionette.ItemView.apply(this, args);
1605
+ },
1606
+
1607
+ // Layout's render will use the existing region objects the
1608
+ // first time it is called. Subsequent calls will close the
1609
+ // views that the regions are showing and then reset the `el`
1610
+ // for the regions to the newly rendered DOM elements.
1611
+ render: function(){
1612
+
1613
+ if (this._firstRender){
1614
+ // if this is the first render, don't do anything to
1615
+ // reset the regions
1616
+ this._firstRender = false;
1617
+ } else {
1618
+ // If this is not the first render call, then we need to
1619
+ // re-initializing the `el` for each region
1620
+ this.closeRegions();
1621
+ this.reInitializeRegions();
1622
+ }
1623
+
1624
+ var args = Array.prototype.slice.apply(arguments);
1625
+ var result = Marionette.ItemView.prototype.render.apply(this, args);
1626
+
1627
+ return result;
1628
+ },
1629
+
1630
+ // Handle closing regions, and then close the view itself.
1631
+ close: function () {
1632
+ if (this.isClosed){ return; }
1633
+
1634
+ this.closeRegions();
1635
+ this.destroyRegions();
1636
+
1637
+ var args = Array.prototype.slice.apply(arguments);
1638
+ Marionette.ItemView.prototype.close.apply(this, args);
1639
+ },
1640
+
1641
+ // Initialize the regions that have been defined in a
1642
+ // `regions` attribute on this layout. The key of the
1643
+ // hash becomes an attribute on the layout object directly.
1644
+ // For example: `regions: { menu: ".menu-container" }`
1645
+ // will product a `layout.menu` object which is a region
1646
+ // that controls the `.menu-container` DOM element.
1647
+ initializeRegions: function () {
1648
+ if (!this.regionManagers){
1649
+ this.regionManagers = {};
1650
+ }
1651
+
1652
+ var that = this;
1653
+ var regions = this.regions || {};
1654
+ _.each(regions, function (region, name) {
1655
+
1656
+ var regionManager = Marionette.Region.buildRegion(region, that.regionType);
1657
+ regionManager.getEl = function(selector){
1658
+ return that.$(selector);
1659
+ };
1660
+
1661
+ that.regionManagers[name] = regionManager;
1662
+ that[name] = regionManager;
1663
+ });
1664
+
1665
+ },
1666
+
1667
+ // Re-initialize all of the regions by updating the `el` that
1668
+ // they point to
1669
+ reInitializeRegions: function(){
1670
+ if (this.regionManagers && _.size(this.regionManagers)===0){
1671
+ this.initializeRegions();
1672
+ } else {
1673
+ _.each(this.regionManagers, function(region){
1674
+ region.reset();
1675
+ });
1676
+ }
1677
+ },
1678
+
1679
+ // Close all of the regions that have been opened by
1680
+ // this layout. This method is called when the layout
1681
+ // itself is closed.
1682
+ closeRegions: function () {
1683
+ var that = this;
1684
+ _.each(this.regionManagers, function (manager, name) {
1685
+ manager.close();
1686
+ });
1687
+ },
1688
+
1689
+ // Destroys all of the regions by removing references
1690
+ // from the Layout
1691
+ destroyRegions: function(){
1692
+ var that = this;
1693
+ _.each(this.regionManagers, function (manager, name) {
1694
+ delete that[name];
1695
+ });
1696
+ this.regionManagers = {};
1697
+ }
1698
+ });
1699
+
1700
+
1701
+
1702
+ // AppRouter
1703
+ // ---------
1704
+
1705
+ // Reduce the boilerplate code of handling route events
1706
+ // and then calling a single method on another object.
1707
+ // Have your routers configured to call the method on
1708
+ // your object, directly.
1709
+ //
1710
+ // Configure an AppRouter with `appRoutes`.
1711
+ //
1712
+ // App routers can only take one `controller` object.
1713
+ // It is recommended that you divide your controller
1714
+ // objects in to smaller peices of related functionality
1715
+ // and have multiple routers / controllers, instead of
1716
+ // just one giant router and controller.
1717
+ //
1718
+ // You can also add standard routes to an AppRouter.
1719
+
1720
+ Marionette.AppRouter = Backbone.Router.extend({
1721
+
1722
+ constructor: function(options){
1723
+ var args = Array.prototype.slice.apply(arguments);
1724
+ Backbone.Router.prototype.constructor.apply(this, args);
1725
+
1726
+ this.options = options;
1727
+
1728
+ if (this.appRoutes){
1729
+ var controller = Marionette.getOption(this, "controller");
1730
+ this.processAppRoutes(controller, this.appRoutes);
1731
+ }
1732
+ },
1733
+
1734
+ // Internal method to process the `appRoutes` for the
1735
+ // router, and turn them in to routes that trigger the
1736
+ // specified method on the specified `controller`.
1737
+ processAppRoutes: function(controller, appRoutes){
1738
+ var method, methodName;
1739
+ var route, routesLength, i;
1740
+ var routes = [];
1741
+ var router = this;
1742
+
1743
+ for(route in appRoutes){
1744
+ if (appRoutes.hasOwnProperty(route)){
1745
+ routes.unshift([route, appRoutes[route]]);
1746
+ }
1747
+ }
1748
+
1749
+ routesLength = routes.length;
1750
+ for (i = 0; i < routesLength; i++){
1751
+ route = routes[i][0];
1752
+ methodName = routes[i][1];
1753
+ method = controller[methodName];
1754
+
1755
+ if (!method){
1756
+ var msg = "Method '" + methodName + "' was not found on the controller";
1757
+ var err = new Error(msg);
1758
+ err.name = "NoMethodError";
1759
+ throw err;
1760
+ }
1761
+
1762
+ method = _.bind(method, controller);
1763
+ router.route(route, methodName, method);
1764
+ }
1765
+ }
1766
+ });
1767
+
1768
+
1769
+ // Application
1770
+ // -----------
1771
+
1772
+ // Contain and manage the composite application as a whole.
1773
+ // Stores and starts up `Region` objects, includes an
1774
+ // event aggregator as `app.vent`
1775
+ Marionette.Application = function(options){
1776
+ this.initCallbacks = new Marionette.Callbacks();
1777
+ this.vent = new Backbone.Wreqr.EventAggregator();
1778
+ this.commands = new Backbone.Wreqr.Commands();
1779
+ this.reqres = new Backbone.Wreqr.RequestResponse();
1780
+ this.submodules = {};
1781
+
1782
+ _.extend(this, options);
1783
+
1784
+ this.triggerMethod = Marionette.triggerMethod;
1785
+ };
1786
+
1787
+ _.extend(Marionette.Application.prototype, Backbone.Events, {
1788
+ // Command execution, facilitated by Backbone.Wreqr.Commands
1789
+ execute: function(){
1790
+ var args = Array.prototype.slice.apply(arguments);
1791
+ this.commands.execute.apply(this.commands, args);
1792
+ },
1793
+
1794
+ // Request/response, facilitated by Backbone.Wreqr.RequestResponse
1795
+ request: function(){
1796
+ var args = Array.prototype.slice.apply(arguments);
1797
+ return this.reqres.request.apply(this.reqres, args);
1798
+ },
1799
+
1800
+ // Add an initializer that is either run at when the `start`
1801
+ // method is called, or run immediately if added after `start`
1802
+ // has already been called.
1803
+ addInitializer: function(initializer){
1804
+ this.initCallbacks.add(initializer);
1805
+ },
1806
+
1807
+ // kick off all of the application's processes.
1808
+ // initializes all of the regions that have been added
1809
+ // to the app, and runs all of the initializer functions
1810
+ start: function(options){
1811
+ this.triggerMethod("initialize:before", options);
1812
+ this.initCallbacks.run(options, this);
1813
+ this.triggerMethod("initialize:after", options);
1814
+
1815
+ this.triggerMethod("start", options);
1816
+ },
1817
+
1818
+ // Add regions to your app.
1819
+ // Accepts a hash of named strings or Region objects
1820
+ // addRegions({something: "#someRegion"})
1821
+ // addRegions{{something: Region.extend({el: "#someRegion"}) });
1822
+ addRegions: function(regions){
1823
+ var that = this;
1824
+ _.each(regions, function (region, name) {
1825
+ var regionManager = Marionette.Region.buildRegion(region, Marionette.Region);
1826
+ that[name] = regionManager;
1827
+ });
1828
+ },
1829
+
1830
+ // Removes a region from your app.
1831
+ // Accepts the regions name
1832
+ // removeRegion('myRegion')
1833
+ removeRegion: function(region) {
1834
+ this[region].close();
1835
+ delete this[region];
1836
+ },
1837
+
1838
+ // Create a module, attached to the application
1839
+ module: function(moduleNames, moduleDefinition){
1840
+ // slice the args, and add this application object as the
1841
+ // first argument of the array
1842
+ var args = slice.call(arguments);
1843
+ args.unshift(this);
1844
+
1845
+ // see the Marionette.Module object for more information
1846
+ return Marionette.Module.create.apply(Marionette.Module, args);
1847
+ }
1848
+ });
1849
+
1850
+ // Copy the `extend` function used by Backbone's classes
1851
+ Marionette.Application.extend = Marionette.extend;
1852
+
1853
+ // Module
1854
+ // ------
1855
+
1856
+ // A simple module system, used to create privacy and encapsulation in
1857
+ // Marionette applications
1858
+ Marionette.Module = function(moduleName, app){
1859
+ this.moduleName = moduleName;
1860
+
1861
+ // store sub-modules
1862
+ this.submodules = {};
1863
+
1864
+ this._setupInitializersAndFinalizers();
1865
+
1866
+ // store the configuration for this module
1867
+ this.app = app;
1868
+ this.startWithParent = true;
1869
+
1870
+ this.triggerMethod = Marionette.triggerMethod;
1871
+ };
1872
+
1873
+ // Extend the Module prototype with events / listenTo, so that the module
1874
+ // can be used as an event aggregator or pub/sub.
1875
+ _.extend(Marionette.Module.prototype, Backbone.Events, {
1876
+
1877
+ // Initializer for a specific module. Initializers are run when the
1878
+ // module's `start` method is called.
1879
+ addInitializer: function(callback){
1880
+ this._initializerCallbacks.add(callback);
1881
+ },
1882
+
1883
+ // Finalizers are run when a module is stopped. They are used to teardown
1884
+ // and finalize any variables, references, events and other code that the
1885
+ // module had set up.
1886
+ addFinalizer: function(callback){
1887
+ this._finalizerCallbacks.add(callback);
1888
+ },
1889
+
1890
+ // Start the module, and run all of it's initializers
1891
+ start: function(options){
1892
+ // Prevent re-starting a module that is already started
1893
+ if (this._isInitialized){ return; }
1894
+
1895
+ // start the sub-modules (depth-first hierarchy)
1896
+ _.each(this.submodules, function(mod){
1897
+ // check to see if we should start the sub-module with this parent
1898
+ var startWithParent = true;
1899
+ startWithParent = mod.startWithParent;
1900
+
1901
+ // start the sub-module
1902
+ if (startWithParent){
1903
+ mod.start(options);
1904
+ }
1905
+ });
1906
+
1907
+ // run the callbacks to "start" the current module
1908
+ this.triggerMethod("before:start", options);
1909
+
1910
+ this._initializerCallbacks.run(options, this);
1911
+ this._isInitialized = true;
1912
+
1913
+ this.triggerMethod("start", options);
1914
+ },
1915
+
1916
+ // Stop this module by running its finalizers and then stop all of
1917
+ // the sub-modules for this module
1918
+ stop: function(){
1919
+ // if we are not initialized, don't bother finalizing
1920
+ if (!this._isInitialized){ return; }
1921
+ this._isInitialized = false;
1922
+
1923
+ Marionette.triggerMethod.call(this, "before:stop");
1924
+
1925
+ // stop the sub-modules; depth-first, to make sure the
1926
+ // sub-modules are stopped / finalized before parents
1927
+ _.each(this.submodules, function(mod){ mod.stop(); });
1928
+
1929
+ // run the finalizers
1930
+ this._finalizerCallbacks.run(undefined,this);
1931
+
1932
+ // reset the initializers and finalizers
1933
+ this._initializerCallbacks.reset();
1934
+ this._finalizerCallbacks.reset();
1935
+
1936
+ Marionette.triggerMethod.call(this, "stop");
1937
+ },
1938
+
1939
+ // Configure the module with a definition function and any custom args
1940
+ // that are to be passed in to the definition function
1941
+ addDefinition: function(moduleDefinition, customArgs){
1942
+ this._runModuleDefinition(moduleDefinition, customArgs);
1943
+ },
1944
+
1945
+ // Internal method: run the module definition function with the correct
1946
+ // arguments
1947
+ _runModuleDefinition: function(definition, customArgs){
1948
+ if (!definition){ return; }
1949
+
1950
+ // build the correct list of arguments for the module definition
1951
+ var args = _.flatten([
1952
+ this,
1953
+ this.app,
1954
+ Backbone,
1955
+ Marionette,
1956
+ $, _,
1957
+ customArgs
1958
+ ]);
1959
+
1960
+ definition.apply(this, args);
1961
+ },
1962
+
1963
+ // Internal method: set up new copies of initializers and finalizers.
1964
+ // Calling this method will wipe out all existing initializers and
1965
+ // finalizers.
1966
+ _setupInitializersAndFinalizers: function(){
1967
+ this._initializerCallbacks = new Marionette.Callbacks();
1968
+ this._finalizerCallbacks = new Marionette.Callbacks();
1969
+ }
1970
+ });
1971
+
1972
+ // Type methods to create modules
1973
+ _.extend(Marionette.Module, {
1974
+
1975
+ // Create a module, hanging off the app parameter as the parent object.
1976
+ create: function(app, moduleNames, moduleDefinition){
1977
+ var that = this;
1978
+ var module = app;
1979
+
1980
+ // get the custom args passed in after the module definition and
1981
+ // get rid of the module name and definition function
1982
+ var customArgs = slice.apply(arguments);
1983
+ customArgs.splice(0, 3);
1984
+
1985
+ // split the module names and get the length
1986
+ moduleNames = moduleNames.split(".");
1987
+ var length = moduleNames.length;
1988
+
1989
+ // store the module definition for the last module in the chain
1990
+ var moduleDefinitions = [];
1991
+ moduleDefinitions[length-1] = moduleDefinition;
1992
+
1993
+ // Loop through all the parts of the module definition
1994
+ _.each(moduleNames, function(moduleName, i){
1995
+ var parentModule = module;
1996
+ module = that._getModule(parentModule, moduleName, app);
1997
+ that._addModuleDefinition(parentModule, module, moduleDefinitions[i], customArgs);
1998
+ });
1999
+
2000
+ // Return the last module in the definition chain
2001
+ return module;
2002
+ },
2003
+
2004
+ _getModule: function(parentModule, moduleName, app, def, args){
2005
+ // Get an existing module of this name if we have one
2006
+ var module = parentModule[moduleName];
2007
+
2008
+ if (!module){
2009
+ // Create a new module if we don't have one
2010
+ module = new Marionette.Module(moduleName, app);
2011
+ parentModule[moduleName] = module;
2012
+ // store the module on the parent
2013
+ parentModule.submodules[moduleName] = module;
2014
+ }
2015
+
2016
+ return module;
2017
+ },
2018
+
2019
+ _addModuleDefinition: function(parentModule, module, def, args){
2020
+ var fn;
2021
+ var startWithParent;
2022
+
2023
+ if (_.isFunction(def)){
2024
+ // if a function is supplied for the module definition
2025
+ fn = def;
2026
+ startWithParent = true;
2027
+
2028
+ } else if (_.isObject(def)){
2029
+ // if an object is supplied
2030
+ fn = def.define;
2031
+ startWithParent = def.startWithParent;
2032
+
2033
+ } else {
2034
+ // if nothing is supplied
2035
+ startWithParent = true;
2036
+ }
2037
+
2038
+ // add module definition if needed
2039
+ if (fn){
2040
+ module.addDefinition(fn, args);
2041
+ }
2042
+
2043
+ // `and` the two together, ensuring a single `false` will prevent it
2044
+ // from starting with the parent
2045
+ var tmp = module.startWithParent;
2046
+ module.startWithParent = module.startWithParent && startWithParent;
2047
+
2048
+ // setup auto-start if needed
2049
+ if (module.startWithParent && !module.startWithParentIsConfigured){
2050
+
2051
+ // only configure this once
2052
+ module.startWithParentIsConfigured = true;
2053
+
2054
+ // add the module initializer config
2055
+ parentModule.addInitializer(function(options){
2056
+ if (module.startWithParent){
2057
+ module.start(options);
2058
+ }
2059
+ });
2060
+
2061
+ }
2062
+
2063
+ }
2064
+ });
2065
+
2066
+
2067
+
2068
+ return Marionette;
2069
+ })(Backbone, _, $ || window.jQuery || window.Zepto || window.ender);