js_stack 0.5.7 → 0.6.0

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