yano-backbone-rails 2.2.0 → 2.2.1

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