Scrutinizer GitHub App not installed

We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.

Install GitHub App

Completed
Push — master ( 8abdb6...83f266 )
by Pierre
10:15
created

marionette.js ➔ ... ➔ _childViewEventHandler   C

Complexity

Conditions 8
Paths 16

Size

Total Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 8
nc 16
nop 1
dl 0
loc 29
rs 5.3846
c 1
b 0
f 0
1
// MarionetteJS (Backbone.Marionette)
2
// ----------------------------------
3
// v3.3.1
4
//
5
// Copyright (c)2017 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) :
0 ignored issues
show
Bug introduced by
The variable define seems to be never declared. If this is a global, consider adding a /** global: define */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
14
	(global.Marionette = global['Mn'] = factory(global.Backbone,global._,global.Backbone.Radio));
0 ignored issues
show
Coding Style introduced by
['Mn'] could be written in dot notation.

You can rewrite this statement in dot notation:

var obj = { };
obj['foo'] = 'bar'; // Bad
obj.foo = 'bar'; // Good
Loading history...
Bug introduced by
Did you forget to assign or call a function?

This error message can for example pop up if you forget to assign the result of a function call to a variable or pass it to another function:

function someFunction(x) {
    (x > 0) ? callFoo() : callBar();
}

// JSHint expects you to assign the result to a variable:
function someFunction(x) {
    var rs = (x > 0) ? callFoo() : callBar();
}

// If you do not use the result, you could also use if statements in the
// case above.
function someFunction(x) {
    if (x > 0) {
        callFoo();
    } else {
        callBar();
    }
}
Loading history...
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.3.1";
22
23
//Internal utility for creating context style global utils
24
var proxy = function proxy(method) {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable proxy already seems to be declared on line 24. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
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
// Marionette.extend
35
// -----------------
36
37
// Borrow the Backbone `extend` method so we can use it as needed
38
var extend = Backbone.Model.extend;
39
40
/* global console */
41
42
var deprecate = function deprecate(message, test) {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable deprecate already seems to be declared on line 42. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
43
  if (_.isObject(message)) {
44
    message = message.prev + ' is going to be removed in the future. ' + 'Please use ' + message.next + ' instead.' + (message.url ? ' See: ' + message.url : '');
45
  }
46
47
  if (!Marionette.DEV_MODE) {
48
    return;
49
  }
50
51
  if ((test === undefined || !test) && !deprecate._cache[message]) {
52
    deprecate._warn('Deprecation warning: ' + message);
53
    deprecate._cache[message] = true;
54
  }
55
};
56
57
deprecate._console = typeof console !== 'undefined' ? console : {};
58
deprecate._warn = function () {
59
  var warn = deprecate._console.warn || deprecate._console.log || _.noop;
60
  return warn.apply(deprecate._console, arguments);
61
};
62
deprecate._cache = {};
63
64
// Marionette.isNodeAttached
65
// -------------------------
66
67
// Determine if `el` is a child of the document
68
var isNodeAttached = function isNodeAttached(el) {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable isNodeAttached already seems to be declared on line 68. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
69
  return document.documentElement.contains(el && el.parentNode);
70
};
71
72
// Merge `keys` from `options` onto `this`
73
var mergeOptions = function mergeOptions(options, keys) {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable mergeOptions already seems to be declared on line 73. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
74
  var _this = this;
75
76
  if (!options) {
77
    return;
78
  }
79
80
  _.each(keys, function (key) {
81
    var option = options[key];
82
    if (option !== undefined) {
83
      _this[key] = option;
84
    }
85
  });
86
};
87
88
// Marionette.getOption
89
// --------------------
90
91
// Retrieve an object, function or other value from the
92
// object or its `options`, with `options` taking precedence.
93
var getOption = function getOption(optionName) {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable getOption already seems to be declared on line 93. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
94
  if (!optionName) {
95
    return;
96
  }
97
  if (this.options && this.options[optionName] !== undefined) {
98
    return this.options[optionName];
99
  } else {
100
    return this[optionName];
101
  }
102
};
103
104
// Marionette.normalizeMethods
105
// ----------------------
106
107
// Pass in a mapping of events => functions or function names
108
// and return a mapping of events => functions
109
var normalizeMethods = function normalizeMethods(hash) {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable normalizeMethods already seems to be declared on line 109. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
110
  var _this = this;
111
112
  return _.reduce(hash, function (normalizedHash, method, name) {
113
    if (!_.isFunction(method)) {
114
      method = _this[method];
115
    }
116
    if (method) {
117
      normalizedHash[name] = method;
118
    }
119
    return normalizedHash;
120
  }, {});
121
};
122
123
// Trigger Method
124
// --------------
125
126
// split the event name on the ":"
127
var splitter = /(^|:)(\w)/gi;
128
129
// take the event section ("section1:section2:section3")
130
// and turn it in to uppercase name onSection1Section2Section3
131
function getEventName(match, prefix, eventName) {
132
  return eventName.toUpperCase();
133
}
134
135
var getOnMethodName = _.memoize(function (event) {
136
  return 'on' + event.replace(splitter, getEventName);
137
});
138
139
// Trigger an event and/or a corresponding method name. Examples:
140
//
141
// `this.triggerMethod("foo")` will trigger the "foo" event and
142
// call the "onFoo" method.
143
//
144
// `this.triggerMethod("foo:bar")` will trigger the "foo:bar" event and
145
// call the "onFooBar" method.
146
function triggerMethod$1(event) {
147
  for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
148
    args[_key - 1] = arguments[_key];
149
  }
150
151
  // get the method name from the event name
152
  var methodName = getOnMethodName(event);
153
  var method = getOption.call(this, methodName);
0 ignored issues
show
Bug introduced by
Possible strict violation.
Loading history...
154
  var result = void 0;
0 ignored issues
show
Coding Style introduced by
Consider using undefined instead of void(0). It is equivalent and more straightforward to read.
Loading history...
155
156
  // call the onMethodName if it exists
157
  if (_.isFunction(method)) {
158
    // pass all args, except the event name
159
    result = method.apply(this, args);
0 ignored issues
show
Bug introduced by
Possible strict violation.
Loading history...
160
  }
161
162
  // trigger the event
163
  this.trigger.apply(this, arguments);
0 ignored issues
show
Bug introduced by
Possible strict violation.
Loading history...
164
165
  return result;
166
}
167
168
// triggerMethodOn invokes triggerMethod on a specific context
169
//
170
// e.g. `Marionette.triggerMethodOn(view, 'show')`
171
// will trigger a "show" event or invoke onShow the view.
172
function triggerMethodOn(context) {
173
  for (var _len2 = arguments.length, args = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
174
    args[_key2 - 1] = arguments[_key2];
175
  }
176
177
  if (_.isFunction(context.triggerMethod)) {
178
    return context.triggerMethod.apply(context, args);
179
  }
180
181
  return triggerMethod$1.apply(context, args);
182
}
183
184
// DOM Refresh
185
// -----------
186
187
// Trigger method on children unless a pure Backbone.View
188
function triggerMethodChildren(view, event, shouldTrigger) {
189
  if (!view._getImmediateChildren) {
190
    return;
191
  }
192
  _.each(view._getImmediateChildren(), function (child) {
193
    if (!shouldTrigger(child)) {
194
      return;
195
    }
196
    triggerMethodOn(child, event, child);
197
  });
198
}
199
200
function shouldTriggerAttach(view) {
201
  return !view._isAttached;
202
}
203
204
function shouldAttach(view) {
205
  if (!shouldTriggerAttach(view)) {
206
    return false;
207
  }
208
  view._isAttached = true;
209
  return true;
210
}
211
212
function shouldTriggerDetach(view) {
213
  return view._isAttached;
214
}
215
216
function shouldDetach(view) {
217
  if (!shouldTriggerDetach(view)) {
218
    return false;
219
  }
220
  view._isAttached = false;
221
  return true;
222
}
223
224
function triggerDOMRefresh(view) {
225
  if (view._isAttached && view._isRendered) {
226
    triggerMethodOn(view, 'dom:refresh', view);
227
  }
228
}
229
230
function triggerDOMRemove(view) {
231
  if (view._isAttached && view._isRendered) {
232
    triggerMethodOn(view, 'dom:remove', view);
233
  }
234
}
235
236
function handleBeforeAttach() {
237
  triggerMethodChildren(this, 'before:attach', shouldTriggerAttach);
0 ignored issues
show
Bug introduced by
Possible strict violation.
Loading history...
238
}
239
240
function handleAttach() {
241
  triggerMethodChildren(this, 'attach', shouldAttach);
0 ignored issues
show
Bug introduced by
Possible strict violation.
Loading history...
242
  triggerDOMRefresh(this);
0 ignored issues
show
Bug introduced by
Possible strict violation.
Loading history...
243
}
244
245
function handleBeforeDetach() {
246
  triggerMethodChildren(this, 'before:detach', shouldTriggerDetach);
0 ignored issues
show
Bug introduced by
Possible strict violation.
Loading history...
247
  triggerDOMRemove(this);
0 ignored issues
show
Bug introduced by
Possible strict violation.
Loading history...
248
}
249
250
function handleDetach() {
251
  triggerMethodChildren(this, 'detach', shouldDetach);
0 ignored issues
show
Bug introduced by
Possible strict violation.
Loading history...
252
}
253
254
function handleBeforeRender() {
255
  triggerDOMRemove(this);
0 ignored issues
show
Bug introduced by
Possible strict violation.
Loading history...
256
}
257
258
function handleRender() {
259
  triggerDOMRefresh(this);
0 ignored issues
show
Bug introduced by
Possible strict violation.
Loading history...
260
}
261
262
// Monitor a view's state, propagating attach/detach events to children and firing dom:refresh
263
// whenever a rendered view is attached or an attached view is rendered.
264
function monitorViewEvents(view) {
265
  if (view._areViewEventsMonitored) {
266
    return;
267
  }
268
269
  view._areViewEventsMonitored = true;
270
271
  view.on({
272
    'before:attach': handleBeforeAttach,
273
    'attach': handleAttach,
274
    'before:detach': handleBeforeDetach,
275
    'detach': handleDetach,
276
    'before:render': handleBeforeRender,
277
    'render': handleRender
278
  });
279
}
280
281
// Error
282
// -----
283
284
var errorProps = ['description', 'fileName', 'lineNumber', 'name', 'message', 'number'];
285
286
var MarionetteError = extend.call(Error, {
287
  urlRoot: 'http://marionettejs.com/docs/v' + version + '/',
288
289
  constructor: function constructor(message, options) {
290
    if (_.isObject(message)) {
291
      options = message;
292
      message = options.message;
293
    } else if (!options) {
294
      options = {};
295
    }
296
297
    var error = Error.call(this, message);
298
    _.extend(this, _.pick(error, errorProps), _.pick(options, errorProps));
299
300
    this.captureStackTrace();
301
302
    if (options.url) {
303
      this.url = this.urlRoot + options.url;
304
    }
305
  },
306
  captureStackTrace: function captureStackTrace() {
307
    if (Error.captureStackTrace) {
308
      Error.captureStackTrace(this, MarionetteError);
309
    }
310
  },
311
  toString: function toString() {
312
    return this.name + ': ' + this.message + (this.url ? ' See: ' + this.url : '');
313
  }
314
});
315
316
MarionetteError.extend = extend;
317
318
// Bind Entity Events & Unbind Entity Events
319
// -----------------------------------------
320
//
321
// These methods are used to bind/unbind a backbone "entity" (e.g. collection/model)
322
// to methods on a target object.
323
//
324
// The first parameter, `target`, must have the Backbone.Events module mixed in.
325
//
326
// The second parameter is the `entity` (Backbone.Model, Backbone.Collection or
327
// any object that has Backbone.Events mixed in) to bind the events from.
328
//
329
// The third parameter is a hash of { "event:name": "eventHandler" }
330
// configuration. Multiple handlers can be separated by a space. A
331
// function can be supplied instead of a string handler name.
332
333
// Bind/unbind the event to handlers specified as a string of
334
// handler names on the target object
335
function bindFromStrings(target, entity, evt, methods, actionName) {
336
  var methodNames = methods.split(/\s+/);
337
338
  _.each(methodNames, function (methodName) {
339
    var method = target[methodName];
340
    if (!method) {
341
      throw new MarionetteError('Method "' + methodName + '" was configured as an event handler, but does not exist.');
342
    }
343
344
    target[actionName](entity, evt, method);
345
  });
346
}
347
348
// generic looping function
349
function iterateEvents(target, entity, bindings, actionName) {
350
  if (!entity || !bindings) {
351
    return;
352
  }
353
354
  // type-check bindings
355
  if (!_.isObject(bindings)) {
356
    throw new MarionetteError({
357
      message: 'Bindings must be an object.',
358
      url: 'marionette.functions.html#marionettebindevents'
359
    });
360
  }
361
362
  // iterate the bindings and bind/unbind them
363
  _.each(bindings, function (method, evt) {
364
365
    // allow for a list of method names as a string
366
    if (_.isString(method)) {
367
      bindFromStrings(target, entity, evt, method, actionName);
368
      return;
369
    }
370
371
    target[actionName](entity, evt, method);
372
  });
373
}
374
375
function bindEvents(entity, bindings) {
376
  iterateEvents(this, entity, bindings, 'listenTo');
0 ignored issues
show
Bug introduced by
Possible strict violation.
Loading history...
377
  return this;
0 ignored issues
show
Bug introduced by
Possible strict violation.
Loading history...
378
}
379
380
function unbindEvents(entity, bindings) {
381
  iterateEvents(this, entity, bindings, 'stopListening');
0 ignored issues
show
Bug introduced by
Possible strict violation.
Loading history...
382
  return this;
0 ignored issues
show
Bug introduced by
Possible strict violation.
Loading history...
383
}
384
385
// Bind/Unbind Radio Requests
386
// -----------------------------------------
387
//
388
// These methods are used to bind/unbind a backbone.radio request
389
// to methods on a target object.
390
//
391
// The first parameter, `target`, will set the context of the reply method
392
//
393
// The second parameter is the `Radio.channel` to bind the reply to.
394
//
395
// The third parameter is a hash of { "request:name": "replyHandler" }
396
// configuration. A function can be supplied instead of a string handler name.
397
398
function iterateReplies(target, channel, bindings, actionName) {
399
  if (!channel || !bindings) {
400
    return;
401
  }
402
403
  // type-check bindings
404
  if (!_.isObject(bindings)) {
405
    throw new MarionetteError({
406
      message: 'Bindings must be an object.',
407
      url: 'marionette.functions.html#marionettebindrequests'
408
    });
409
  }
410
411
  var normalizedRadioRequests = normalizeMethods.call(target, bindings);
412
413
  channel[actionName](normalizedRadioRequests, target);
414
}
415
416
function bindRequests(channel, bindings) {
417
  iterateReplies(this, channel, bindings, 'reply');
0 ignored issues
show
Bug introduced by
Possible strict violation.
Loading history...
418
  return this;
0 ignored issues
show
Bug introduced by
Possible strict violation.
Loading history...
419
}
420
421
function unbindRequests(channel, bindings) {
422
  iterateReplies(this, channel, bindings, 'stopReplying');
0 ignored issues
show
Bug introduced by
Possible strict violation.
Loading history...
423
  return this;
0 ignored issues
show
Bug introduced by
Possible strict violation.
Loading history...
424
}
425
426
// Internal utility for setting options consistently across Mn
427
var setOptions = function setOptions() {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable setOptions already seems to be declared on line 427. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
428
  for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
429
    args[_key] = arguments[_key];
430
  }
431
432
  this.options = _.extend.apply(_, [{}, _.result(this, 'options')].concat(args));
433
};
434
435
var CommonMixin = {
436
437
  // Imports the "normalizeMethods" to transform hashes of
438
  // events=>function references/names to a hash of events=>function references
439
  normalizeMethods: normalizeMethods,
440
441
  _setOptions: setOptions,
442
443
  // A handy way to merge passed-in options onto the instance
444
  mergeOptions: mergeOptions,
445
446
  // Enable getting options from this or this.options by name.
447
  getOption: getOption,
448
449
  // Enable binding view's events from another entity.
450
  bindEvents: bindEvents,
451
452
  // Enable unbinding view's events from another entity.
453
  unbindEvents: unbindEvents
454
};
455
456
// MixinOptions
457
// - channelName
458
// - radioEvents
459
// - radioRequests
460
461
var RadioMixin = {
462
  _initRadio: function _initRadio() {
463
    var channelName = _.result(this, 'channelName');
464
465
    if (!channelName) {
466
      return;
467
    }
468
469
    /* istanbul ignore next */
470
    if (!Radio) {
471
      throw new MarionetteError({
472
        name: 'BackboneRadioMissing',
473
        message: 'The dependency "backbone.radio" is missing.'
474
      });
475
    }
476
477
    var channel = this._channel = Radio.channel(channelName);
478
479
    var radioEvents = _.result(this, 'radioEvents');
480
    this.bindEvents(channel, radioEvents);
481
482
    var radioRequests = _.result(this, 'radioRequests');
483
    this.bindRequests(channel, radioRequests);
484
485
    this.on('destroy', this._destroyRadio);
486
  },
487
  _destroyRadio: function _destroyRadio() {
488
    this._channel.stopReplying(null, null, this);
489
  },
490
  getChannel: function getChannel() {
491
    return this._channel;
492
  },
493
494
495
  // Proxy `bindEvents`
496
  bindEvents: bindEvents,
497
498
  // Proxy `unbindEvents`
499
  unbindEvents: unbindEvents,
500
501
  // Proxy `bindRequests`
502
  bindRequests: bindRequests,
503
504
  // Proxy `unbindRequests`
505
  unbindRequests: unbindRequests
506
507
};
508
509
// Object
510
// ------
511
512
var ClassOptions = ['channelName', 'radioEvents', 'radioRequests'];
513
514
// A Base Class that other Classes should descend from.
515
// Object borrows many conventions and utilities from Backbone.
516
var MarionetteObject = function MarionetteObject(options) {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable MarionetteObject already seems to be declared on line 516. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
517
  this._setOptions(options);
518
  this.mergeOptions(options, ClassOptions);
519
  this.cid = _.uniqueId(this.cidPrefix);
520
  this._initRadio();
521
  this.initialize.apply(this, arguments);
522
};
523
524
MarionetteObject.extend = extend;
525
526
// Object Methods
527
// --------------
528
529
// Ensure it can trigger events with Backbone.Events
530
_.extend(MarionetteObject.prototype, Backbone.Events, CommonMixin, RadioMixin, {
531
  cidPrefix: 'mno',
532
533
  // for parity with Marionette.AbstractView lifecyle
534
  _isDestroyed: false,
535
536
  isDestroyed: function isDestroyed() {
537
    return this._isDestroyed;
538
  },
539
540
541
  //this is a noop method intended to be overridden by classes that extend from this base
542
  initialize: function initialize() {},
543
  destroy: function destroy() {
544
    if (this._isDestroyed) {
545
      return this;
546
    }
547
548
    for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
549
      args[_key] = arguments[_key];
550
    }
551
552
    this.triggerMethod.apply(this, ['before:destroy', this].concat(args));
553
554
    this._isDestroyed = true;
555
    this.triggerMethod.apply(this, ['destroy', this].concat(args));
556
    this.stopListening();
557
558
    return this;
559
  },
560
561
562
  triggerMethod: triggerMethod$1
563
});
564
565
// DomMixin
566
//  ---------
567
568
var DomMixin = {
569
  createBuffer: function createBuffer() {
570
    return document.createDocumentFragment();
571
  },
572
  appendChildren: function appendChildren(el, children) {
573
    Backbone.$(el).append(children);
574
  },
575
  beforeEl: function beforeEl(el, sibling) {
576
    Backbone.$(el).before(sibling);
577
  },
578
  replaceEl: function replaceEl(newEl, oldEl) {
579
    if (newEl === oldEl) {
580
      return;
581
    }
582
583
    var parent = oldEl.parentNode;
584
585
    if (!parent) {
586
      return;
587
    }
588
589
    parent.replaceChild(newEl, oldEl);
590
  },
591
  detachContents: function detachContents(el) {
592
    Backbone.$(el).contents().detach();
593
  },
594
  setInnerContent: function setInnerContent(el, html) {
595
    Backbone.$(el).html(html);
596
  },
597
  detachEl: function detachEl(el) {
598
    Backbone.$(el).detach();
599
  },
600
  removeEl: function removeEl(el) {
601
    Backbone.$(el).remove();
602
  },
603
  findEls: function findEls(selector, context) {
604
    return Backbone.$(selector, context);
605
  }
606
};
607
608
// Template Cache
609
// --------------
610
611
// Manage templates stored in `<script>` blocks,
612
// caching them for faster access.
613
var TemplateCache = function TemplateCache(templateId) {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable TemplateCache already seems to be declared on line 613. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
614
  this.templateId = templateId;
615
};
616
617
// TemplateCache object-level methods. Manage the template
618
// caches from these method calls instead of creating
619
// your own TemplateCache instances
620
_.extend(TemplateCache, {
621
  templateCaches: {},
622
623
  // Get the specified template by id. Either
624
  // retrieves the cached version, or loads it
625
  // from the DOM.
626
  get: function get(templateId, options) {
627
    var cachedTemplate = this.templateCaches[templateId];
628
629
    if (!cachedTemplate) {
630
      cachedTemplate = new TemplateCache(templateId);
631
      this.templateCaches[templateId] = cachedTemplate;
632
    }
633
634
    return cachedTemplate.load(options);
635
  },
636
637
638
  // Clear templates from the cache. If no arguments
639
  // are specified, clears all templates:
640
  // `clear()`
641
  //
642
  // If arguments are specified, clears each of the
643
  // specified templates from the cache:
644
  // `clear("#t1", "#t2", "...")`
645
  clear: function clear() {
646
    var i = void 0;
0 ignored issues
show
Unused Code introduced by
The assignment to variable i seems to be never used. Consider removing it.
Loading history...
Coding Style introduced by
Consider using undefined instead of void(0). It is equivalent and more straightforward to read.
Loading history...
647
648
    for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
649
      args[_key] = arguments[_key];
650
    }
651
652
    var length = args.length;
653
654
    if (length > 0) {
655
      for (i = 0; i < length; i++) {
656
        delete this.templateCaches[args[i]];
657
      }
658
    } else {
659
      this.templateCaches = {};
660
    }
661
  }
662
});
663
664
// TemplateCache instance methods, allowing each
665
// template cache object to manage its own state
666
// and know whether or not it has been loaded
667
_.extend(TemplateCache.prototype, DomMixin, {
668
669
  // Internal method to load the template
670
  load: function load(options) {
671
    // Guard clause to prevent loading this template more than once
672
    if (this.compiledTemplate) {
673
      return this.compiledTemplate;
674
    }
675
676
    // Load the template and compile it
677
    var template = this.loadTemplate(this.templateId, options);
678
    this.compiledTemplate = this.compileTemplate(template, options);
679
680
    return this.compiledTemplate;
681
  },
682
683
684
  // Load a template from the DOM, by default. Override
685
  // this method to provide your own template retrieval
686
  // For asynchronous loading with AMD/RequireJS, consider
687
  // using a template-loader plugin as described here:
688
  // https://github.com/marionettejs/backbone.marionette/wiki/Using-marionette-with-requirejs
689
  loadTemplate: function loadTemplate(templateId, options) {
0 ignored issues
show
Unused Code introduced by
The parameter options is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
690
    var $template = this.findEls(templateId);
691
692
    if (!$template.length) {
693
      throw new MarionetteError({
694
        name: 'NoTemplateError',
695
        message: 'Could not find template: "' + templateId + '"'
696
      });
697
    }
698
    return $template.html();
699
  },
700
701
702
  // Pre-compile the template before caching it. Override
703
  // this method if you do not need to pre-compile a template
704
  // (JST / RequireJS for example) or if you want to change
705
  // the template engine used (Handebars, etc).
706
  compileTemplate: function compileTemplate(rawTemplate, options) {
707
    return _.template(rawTemplate, options);
708
  }
709
});
710
711
// Implementation of the invoke method (http://underscorejs.org/#invoke) with support for
712
// lodash v3, v4, and underscore.js
713
var _invoke = _.invokeMap || _.invoke;
714
715
// MixinOptions
716
// - behaviors
717
718
// Takes care of getting the behavior class
719
// given options and a key.
720
// If a user passes in options.behaviorClass
721
// default to using that.
722
// If a user passes in a Behavior Class directly, use that
723
// Otherwise delegate the lookup to the users `behaviorsLookup` implementation.
724
function getBehaviorClass(options, key) {
725
  if (options.behaviorClass) {
726
    return options.behaviorClass;
727
    //treat functions as a Behavior constructor
728
  } else if (_.isFunction(options)) {
729
    return options;
730
  }
731
732
  // behaviorsLookup can be either a flat object or a method
733
  if (_.isFunction(Marionette.Behaviors.behaviorsLookup)) {
734
    return Marionette.Behaviors.behaviorsLookup(options, key)[key];
735
  }
736
737
  return Marionette.Behaviors.behaviorsLookup[key];
738
}
739
740
// Iterate over the behaviors object, for each behavior
741
// instantiate it and get its grouped behaviors.
742
// This accepts a list of behaviors in either an object or array form
743
function parseBehaviors(view, behaviors) {
744
  return _.chain(behaviors).map(function (options, key) {
745
    var BehaviorClass = getBehaviorClass(options, key);
746
    //if we're passed a class directly instead of an object
747
    var _options = options === BehaviorClass ? {} : options;
748
    var behavior = new BehaviorClass(_options, view);
749
    var nestedBehaviors = parseBehaviors(view, _.result(behavior, 'behaviors'));
750
751
    return [behavior].concat(nestedBehaviors);
752
  }).flatten().value();
753
}
754
755
var BehaviorsMixin = {
756
  _initBehaviors: function _initBehaviors() {
757
    this._behaviors = this._getBehaviors();
758
  },
759
  _getBehaviors: function _getBehaviors() {
760
    var behaviors = _.result(this, 'behaviors');
761
762
    // Behaviors defined on a view can be a flat object literal
763
    // or it can be a function that returns an object.
764
    return _.isObject(behaviors) ? parseBehaviors(this, behaviors) : {};
765
  },
766
  _getBehaviorTriggers: function _getBehaviorTriggers() {
767
    var triggers = _invoke(this._behaviors, 'getTriggers');
768
    return _.reduce(triggers, function (memo, _triggers) {
769
      return _.extend(memo, _triggers);
770
    }, {});
771
  },
772
  _getBehaviorEvents: function _getBehaviorEvents() {
773
    var events = _invoke(this._behaviors, 'getEvents');
774
    return _.reduce(events, function (memo, _events) {
775
      return _.extend(memo, _events);
776
    }, {});
777
  },
778
779
780
  // proxy behavior $el to the view's $el.
781
  _proxyBehaviorViewProperties: function _proxyBehaviorViewProperties() {
782
    _invoke(this._behaviors, 'proxyViewProperties');
783
  },
784
785
786
  // delegate modelEvents and collectionEvents
787
  _delegateBehaviorEntityEvents: function _delegateBehaviorEntityEvents() {
788
    _invoke(this._behaviors, 'delegateEntityEvents');
789
  },
790
791
792
  // undelegate modelEvents and collectionEvents
793
  _undelegateBehaviorEntityEvents: function _undelegateBehaviorEntityEvents() {
794
    _invoke(this._behaviors, 'undelegateEntityEvents');
795
  },
796
  _destroyBehaviors: function _destroyBehaviors() {
797
    for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
798
      args[_key] = arguments[_key];
799
    }
800
801
    // Call destroy on each behavior after
802
    // destroying the view.
803
    // This unbinds event listeners
804
    // that behaviors have registered for.
805
    _invoke.apply(undefined, [this._behaviors, 'destroy'].concat(args));
806
  },
807
808
809
  // Remove a behavior
810
  _removeBehavior: function _removeBehavior(behavior) {
811
    // Don't worry about the clean up if the view is destroyed
812
    if (this._isDestroyed) {
813
      return;
814
    }
815
    this._behaviors = _.without(this._behaviors, behavior);
816
  },
817
  _bindBehaviorUIElements: function _bindBehaviorUIElements() {
818
    _invoke(this._behaviors, 'bindUIElements');
819
  },
820
  _unbindBehaviorUIElements: function _unbindBehaviorUIElements() {
821
    _invoke(this._behaviors, 'unbindUIElements');
822
  },
823
  _triggerEventOnBehaviors: function _triggerEventOnBehaviors() {
824
    var behaviors = this._behaviors;
825
    // Use good ol' for as this is a very hot function
826
    for (var i = 0, length = behaviors && behaviors.length; i < length; i++) {
827
      triggerMethod$1.apply(behaviors[i], arguments);
828
    }
829
  }
830
};
831
832
// MixinOptions
833
// - collectionEvents
834
// - modelEvents
835
836
var DelegateEntityEventsMixin = {
837
  // Handle `modelEvents`, and `collectionEvents` configuration
838
  _delegateEntityEvents: function _delegateEntityEvents(model, collection) {
839
    this._undelegateEntityEvents(model, collection);
840
841
    var modelEvents = _.result(this, 'modelEvents');
842
    bindEvents.call(this, model, modelEvents);
843
844
    var collectionEvents = _.result(this, 'collectionEvents');
845
    bindEvents.call(this, collection, collectionEvents);
846
  },
847
  _undelegateEntityEvents: function _undelegateEntityEvents(model, collection) {
848
    var modelEvents = _.result(this, 'modelEvents');
849
    unbindEvents.call(this, model, modelEvents);
850
851
    var collectionEvents = _.result(this, 'collectionEvents');
852
    unbindEvents.call(this, collection, collectionEvents);
853
  }
854
};
855
856
// Borrow event splitter from Backbone
857
var delegateEventSplitter = /^(\S+)\s*(.*)$/;
858
859
function uniqueName(eventName, selector) {
860
  return [eventName + _.uniqueId('.evt'), selector].join(' ');
861
}
862
863
// Set event name to be namespaced using a unique index
864
// to generate a non colliding event namespace
865
// http://api.jquery.com/event.namespace/
866
var getUniqueEventName = function getUniqueEventName(eventName) {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable getUniqueEventName already seems to be declared on line 866. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
867
  var match = eventName.match(delegateEventSplitter);
868
  return uniqueName(match[1], match[2]);
869
};
870
871
// Add Feature flags here
872
// e.g. 'class' => false
873
var FEATURES = {
874
  childViewEventPrefix: true,
875
  triggersStopPropagation: true,
876
  triggersPreventDefault: true
877
};
878
879
function isEnabled(name) {
880
  return !!FEATURES[name];
881
}
882
883
function setEnabled(name, state) {
884
  return FEATURES[name] = state;
0 ignored issues
show
Bug introduced by
Did you mean to return a conditional instead of an assignment?

This error is thrown if you assign to a variable in your return statement:

function someFunction(x) {
    return x = 1;
}

// Instead you maybe ment

function someFunction(x) {
    return x === 1;
}
Loading history...
885
}
886
887
// Internal method to create an event handler for a given `triggerDef` like
888
// 'click:foo'
889
function buildViewTrigger(view, triggerDef) {
890
  if (_.isString(triggerDef)) {
891
    triggerDef = { event: triggerDef };
892
  }
893
894
  var eventName = triggerDef.event;
895
896
  var shouldPreventDefault = !!triggerDef.preventDefault;
897
898
  if (isEnabled('triggersPreventDefault')) {
899
    shouldPreventDefault = triggerDef.preventDefault !== false;
900
  }
901
902
  var shouldStopPropagation = !!triggerDef.stopPropagation;
903
904
  if (isEnabled('triggersStopPropagation')) {
905
    shouldStopPropagation = triggerDef.stopPropagation !== false;
906
  }
907
908
  return function (event) {
909
    if (shouldPreventDefault) {
910
      event.preventDefault();
911
    }
912
913
    if (shouldStopPropagation) {
914
      event.stopPropagation();
915
    }
916
917
    view.triggerMethod(eventName, view, event);
918
  };
919
}
920
921
var TriggersMixin = {
922
923
  // Configure `triggers` to forward DOM events to view
924
  // events. `triggers: {"click .foo": "do:foo"}`
925
  _getViewTriggers: function _getViewTriggers(view, triggers) {
926
    // Configure the triggers, prevent default
927
    // action and stop propagation of DOM events
928
    return _.reduce(triggers, function (events, value, key) {
929
      key = getUniqueEventName(key);
930
      events[key] = buildViewTrigger(view, value);
931
      return events;
932
    }, {});
933
  }
934
};
935
936
// allows for the use of the @ui. syntax within
937
// a given key for triggers and events
938
// swaps the @ui with the associated selector.
939
// Returns a new, non-mutated, parsed events hash.
940
var _normalizeUIKeys = function _normalizeUIKeys(hash, ui) {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable _normalizeUIKeys already seems to be declared on line 940. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
941
  return _.reduce(hash, function (memo, val, key) {
942
    var normalizedKey = _normalizeUIString(key, ui);
943
    memo[normalizedKey] = val;
944
    return memo;
945
  }, {});
946
};
947
948
// utility method for parsing @ui. syntax strings
949
// into associated selector
950
var _normalizeUIString = function _normalizeUIString(uiString, ui) {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable _normalizeUIString already seems to be declared on line 950. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
951
  return uiString.replace(/@ui\.[a-zA-Z-_$0-9]*/g, function (r) {
952
    return ui[r.slice(4)];
953
  });
954
};
955
956
// allows for the use of the @ui. syntax within
957
// a given value for regions
958
// swaps the @ui with the associated selector
959
var _normalizeUIValues = function _normalizeUIValues(hash, ui, properties) {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable _normalizeUIValues already seems to be declared on line 959. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
960
  _.each(hash, function (val, key) {
961
    if (_.isString(val)) {
962
      hash[key] = _normalizeUIString(val, ui);
963
    } else if (_.isObject(val) && _.isArray(properties)) {
964
      _.extend(val, _normalizeUIValues(_.pick(val, properties), ui));
965
      /* Value is an object, and we got an array of embedded property names to normalize. */
966
      _.each(properties, function (property) {
967
        var propertyVal = val[property];
968
        if (_.isString(propertyVal)) {
969
          val[property] = _normalizeUIString(propertyVal, ui);
970
        }
971
      });
972
    }
973
  });
974
  return hash;
975
};
976
977
var UIMixin = {
978
979
  // normalize the keys of passed hash with the views `ui` selectors.
980
  // `{"@ui.foo": "bar"}`
981
  normalizeUIKeys: function normalizeUIKeys(hash) {
982
    var uiBindings = this._getUIBindings();
983
    return _normalizeUIKeys(hash, uiBindings);
984
  },
985
986
987
  // normalize the passed string with the views `ui` selectors.
988
  // `"@ui.bar"`
989
  normalizeUIString: function normalizeUIString(uiString) {
990
    var uiBindings = this._getUIBindings();
991
    return _normalizeUIString(uiString, uiBindings);
992
  },
993
994
995
  // normalize the values of passed hash with the views `ui` selectors.
996
  // `{foo: "@ui.bar"}`
997
  normalizeUIValues: function normalizeUIValues(hash, properties) {
998
    var uiBindings = this._getUIBindings();
999
    return _normalizeUIValues(hash, uiBindings, properties);
1000
  },
1001
  _getUIBindings: function _getUIBindings() {
1002
    var uiBindings = _.result(this, '_uiBindings');
1003
    var ui = _.result(this, 'ui');
1004
    return uiBindings || ui;
1005
  },
1006
1007
1008
  // This method binds the elements specified in the "ui" hash inside the view's code with
1009
  // the associated jQuery selectors.
1010
  _bindUIElements: function _bindUIElements() {
1011
    var _this = this;
1012
1013
    if (!this.ui) {
1014
      return;
1015
    }
1016
1017
    // store the ui hash in _uiBindings so they can be reset later
1018
    // and so re-rendering the view will be able to find the bindings
1019
    if (!this._uiBindings) {
1020
      this._uiBindings = this.ui;
1021
    }
1022
1023
    // get the bindings result, as a function or otherwise
1024
    var bindings = _.result(this, '_uiBindings');
1025
1026
    // empty the ui so we don't have anything to start with
1027
    this._ui = {};
1028
1029
    // bind each of the selectors
1030
    _.each(bindings, function (selector, key) {
1031
      _this._ui[key] = _this.$(selector);
1032
    });
1033
1034
    this.ui = this._ui;
1035
  },
1036
  _unbindUIElements: function _unbindUIElements() {
1037
    var _this2 = this;
1038
1039
    if (!this.ui || !this._uiBindings) {
1040
      return;
1041
    }
1042
1043
    // delete all of the existing ui bindings
1044
    _.each(this.ui, function ($el, name) {
1045
      delete _this2.ui[name];
1046
    });
1047
1048
    // reset the ui element to the original bindings configuration
1049
    this.ui = this._uiBindings;
1050
    delete this._uiBindings;
1051
    delete this._ui;
1052
  },
1053
  _getUI: function _getUI(name) {
1054
    return this._ui[name];
1055
  }
1056
};
1057
1058
// ViewMixin
1059
//  ---------
1060
1061
// MixinOptions
1062
// - behaviors
1063
// - childViewEventPrefix
1064
// - childViewEvents
1065
// - childViewTriggers
1066
// - collectionEvents
1067
// - modelEvents
1068
// - triggers
1069
// - ui
1070
1071
1072
var ViewMixin = {
1073
  supportsRenderLifecycle: true,
1074
  supportsDestroyLifecycle: true,
1075
1076
  _isDestroyed: false,
1077
1078
  isDestroyed: function isDestroyed() {
1079
    return !!this._isDestroyed;
1080
  },
1081
1082
1083
  _isRendered: false,
1084
1085
  isRendered: function isRendered() {
1086
    return !!this._isRendered;
1087
  },
1088
1089
1090
  _isAttached: false,
1091
1092
  isAttached: function isAttached() {
1093
    return !!this._isAttached;
1094
  },
1095
1096
1097
  // Overriding Backbone.View's `delegateEvents` to handle
1098
  // `events` and `triggers`
1099
  delegateEvents: function delegateEvents(eventsArg) {
1100
1101
    this._proxyBehaviorViewProperties();
1102
    this._buildEventProxies();
1103
1104
    var viewEvents = this._getEvents(eventsArg);
1105
1106
    if (typeof eventsArg === 'undefined') {
1107
      this.events = viewEvents;
1108
    }
1109
1110
    var combinedEvents = _.extend({}, this._getBehaviorEvents(), viewEvents, this._getBehaviorTriggers(), this.getTriggers());
1111
1112
    Backbone.View.prototype.delegateEvents.call(this, combinedEvents);
1113
1114
    return this;
1115
  },
1116
  _getEvents: function _getEvents(eventsArg) {
1117
    var events = eventsArg || this.events;
1118
1119
    if (_.isFunction(events)) {
1120
      return this.normalizeUIKeys(events.call(this));
1121
    }
1122
1123
    return this.normalizeUIKeys(events);
1124
  },
1125
1126
1127
  // Configure `triggers` to forward DOM events to view
1128
  // events. `triggers: {"click .foo": "do:foo"}`
1129
  getTriggers: function getTriggers() {
1130
    if (!this.triggers) {
1131
      return;
1132
    }
1133
1134
    // Allow `triggers` to be configured as a function
1135
    var triggers = this.normalizeUIKeys(_.result(this, 'triggers'));
1136
1137
    // Configure the triggers, prevent default
1138
    // action and stop propagation of DOM events
1139
    return this._getViewTriggers(this, triggers);
1140
  },
1141
1142
1143
  // Handle `modelEvents`, and `collectionEvents` configuration
1144
  delegateEntityEvents: function delegateEntityEvents() {
1145
    this._delegateEntityEvents(this.model, this.collection);
1146
1147
    // bind each behaviors model and collection events
1148
    this._delegateBehaviorEntityEvents();
1149
1150
    return this;
1151
  },
1152
1153
1154
  // Handle unbinding `modelEvents`, and `collectionEvents` configuration
1155
  undelegateEntityEvents: function undelegateEntityEvents() {
1156
    this._undelegateEntityEvents(this.model, this.collection);
1157
1158
    // unbind each behaviors model and collection events
1159
    this._undelegateBehaviorEntityEvents();
1160
1161
    return this;
1162
  },
1163
1164
1165
  // Handle destroying the view and its children.
1166
  destroy: function destroy() {
1167
    if (this._isDestroyed) {
1168
      return this;
1169
    }
1170
    var shouldTriggerDetach = !!this._isAttached;
1171
1172
    for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
1173
      args[_key] = arguments[_key];
1174
    }
1175
1176
    this.triggerMethod.apply(this, ['before:destroy', this].concat(args));
1177
    if (shouldTriggerDetach) {
1178
      this.triggerMethod('before:detach', this);
1179
    }
1180
1181
    // unbind UI elements
1182
    this.unbindUIElements();
1183
1184
    // remove the view from the DOM
1185
    this.removeEl(this.el);
1186
1187
    if (shouldTriggerDetach) {
1188
      this._isAttached = false;
1189
      this.triggerMethod('detach', this);
1190
    }
1191
1192
    // remove children after the remove to prevent extra paints
1193
    this._removeChildren();
1194
1195
    this._isDestroyed = true;
1196
    this._isRendered = false;
1197
1198
    // Destroy behaviors after _isDestroyed flag
1199
    this._destroyBehaviors.apply(this, args);
1200
1201
    this.triggerMethod.apply(this, ['destroy', this].concat(args));
1202
1203
    this.stopListening();
1204
1205
    return this;
1206
  },
1207
  bindUIElements: function bindUIElements() {
1208
    this._bindUIElements();
1209
    this._bindBehaviorUIElements();
1210
1211
    return this;
1212
  },
1213
1214
1215
  // This method unbinds the elements specified in the "ui" hash
1216
  unbindUIElements: function unbindUIElements() {
1217
    this._unbindUIElements();
1218
    this._unbindBehaviorUIElements();
1219
1220
    return this;
1221
  },
1222
  getUI: function getUI(name) {
1223
    return this._getUI(name);
1224
  },
1225
1226
1227
  // used as the prefix for child view events
1228
  // that are forwarded through the layoutview
1229
  childViewEventPrefix: function childViewEventPrefix() {
1230
    return isEnabled('childViewEventPrefix') ? 'childview' : false;
1231
  },
1232
1233
1234
  // import the `triggerMethod` to trigger events with corresponding
1235
  // methods if the method exists
1236
  triggerMethod: function triggerMethod() {
1237
    var ret = triggerMethod$1.apply(this, arguments);
1238
1239
    this._triggerEventOnBehaviors.apply(this, arguments);
1240
1241
    return ret;
1242
  },
1243
1244
1245
  // Cache `childViewEvents` and `childViewTriggers`
1246
  _buildEventProxies: function _buildEventProxies() {
1247
    this._childViewEvents = _.result(this, 'childViewEvents');
1248
    this._childViewTriggers = _.result(this, 'childViewTriggers');
1249
  },
1250
  _proxyChildViewEvents: function _proxyChildViewEvents(view) {
1251
    this.listenTo(view, 'all', this._childViewEventHandler);
1252
  },
1253
  _childViewEventHandler: function _childViewEventHandler(eventName) {
1254
    var childViewEvents = this.normalizeMethods(this._childViewEvents);
1255
1256
    // call collectionView childViewEvent if defined
1257
1258
    for (var _len2 = arguments.length, args = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
1259
      args[_key2 - 1] = arguments[_key2];
1260
    }
1261
1262
    if (typeof childViewEvents !== 'undefined' && _.isFunction(childViewEvents[eventName])) {
1263
      childViewEvents[eventName].apply(this, args);
1264
    }
1265
1266
    // use the parent view's proxyEvent handlers
1267
    var childViewTriggers = this._childViewTriggers;
1268
1269
    // Call the event with the proxy name on the parent layout
1270
    if (childViewTriggers && _.isString(childViewTriggers[eventName])) {
1271
      this.triggerMethod.apply(this, [childViewTriggers[eventName]].concat(args));
1272
    }
1273
1274
    var prefix = _.result(this, 'childViewEventPrefix');
1275
1276
    if (prefix !== false) {
1277
      var childEventName = prefix + ':' + eventName;
1278
1279
      this.triggerMethod.apply(this, [childEventName].concat(args));
1280
    }
1281
  }
1282
};
1283
1284
_.extend(ViewMixin, DomMixin, BehaviorsMixin, CommonMixin, DelegateEntityEventsMixin, TriggersMixin, UIMixin);
1285
1286
function destroyBackboneView(view) {
1287
  if (!view.supportsDestroyLifecycle) {
1288
    triggerMethodOn(view, 'before:destroy', view);
1289
  }
1290
1291
  var shouldTriggerDetach = !!view._isAttached;
1292
1293
  if (shouldTriggerDetach) {
1294
    triggerMethodOn(view, 'before:detach', view);
1295
  }
1296
1297
  view.remove();
1298
1299
  if (shouldTriggerDetach) {
1300
    view._isAttached = false;
1301
    triggerMethodOn(view, 'detach', view);
1302
  }
1303
1304
  view._isDestroyed = true;
1305
1306
  if (!view.supportsDestroyLifecycle) {
1307
    triggerMethodOn(view, 'destroy', view);
1308
  }
1309
}
1310
1311
// Region
1312
// ------
1313
1314
var ClassOptions$2 = ['allowMissingEl', 'parentEl', 'replaceElement'];
1315
1316
var Region = MarionetteObject.extend({
1317
  cidPrefix: 'mnr',
1318
  replaceElement: false,
1319
  _isReplaced: false,
1320
  _isSwappingView: false,
1321
1322
  constructor: function constructor(options) {
1323
    this._setOptions(options);
1324
1325
    this.mergeOptions(options, ClassOptions$2);
1326
1327
    // getOption necessary because options.el may be passed as undefined
1328
    this._initEl = this.el = this.getOption('el');
1329
1330
    // Handle when this.el is passed in as a $ wrapped element.
1331
    this.el = this.el instanceof Backbone.$ ? this.el[0] : this.el;
1332
1333
    if (!this.el) {
1334
      throw new MarionetteError({
1335
        name: 'NoElError',
1336
        message: 'An "el" must be specified for a region.'
1337
      });
1338
    }
1339
1340
    this.$el = this.getEl(this.el);
1341
    MarionetteObject.call(this, options);
1342
  },
1343
1344
1345
  // Displays a backbone view instance inside of the region. Handles calling the `render`
1346
  // method for you. Reads content directly from the `el` attribute. The `preventDestroy`
1347
  // option can be used to prevent a view from the old view being destroyed on show.
1348
  show: function show(view, options) {
1349
    if (!this._ensureElement(options)) {
1350
      return;
1351
    }
1352
1353
    view = this._getView(view, options);
1354
1355
    if (view === this.currentView) {
1356
      return this;
1357
    }
1358
1359
    this._isSwappingView = !!this.currentView;
1360
1361
    this.triggerMethod('before:show', this, view, options);
1362
1363
    // Assume an attached view is already in the region for pre-existing DOM
1364
    if (!view._isAttached) {
1365
      this.empty(options);
1366
    }
1367
1368
    this._setupChildView(view);
1369
1370
    this._renderView(view);
1371
1372
    this._attachView(view, options);
1373
1374
    this.currentView = view;
1375
1376
    this.triggerMethod('show', this, view, options);
1377
1378
    this._isSwappingView = false;
1379
1380
    return this;
1381
  },
1382
  _setupChildView: function _setupChildView(view) {
1383
    monitorViewEvents(view);
1384
1385
    this._proxyChildViewEvents(view);
1386
1387
    // We need to listen for if a view is destroyed in a way other than through the region.
1388
    // If this happens we need to remove the reference to the currentView since once a view
1389
    // has been destroyed we can not reuse it.
1390
    view.on('destroy', this._empty, this);
1391
  },
1392
  _proxyChildViewEvents: function _proxyChildViewEvents(view) {
1393
    var parentView = this._parentView;
1394
1395
    if (!parentView) {
1396
      return;
1397
    }
1398
1399
    parentView._proxyChildViewEvents(view);
1400
  },
1401
  _renderView: function _renderView(view) {
1402
    if (view._isRendered) {
1403
      return;
1404
    }
1405
1406
    if (!view.supportsRenderLifecycle) {
1407
      triggerMethodOn(view, 'before:render', view);
1408
    }
1409
1410
    view.render();
1411
1412
    if (!view.supportsRenderLifecycle) {
1413
      view._isRendered = true;
1414
      triggerMethodOn(view, 'render', view);
1415
    }
1416
  },
1417
  _attachView: function _attachView(view) {
1418
    var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
1419
1420
    var shouldTriggerAttach = !view._isAttached && isNodeAttached(this.el);
1421
    var shouldReplaceEl = typeof options.replaceElement === 'undefined' ? !!_.result(this, 'replaceElement') : !!options.replaceElement;
1422
1423
    if (shouldTriggerAttach) {
1424
      triggerMethodOn(view, 'before:attach', view);
1425
    }
1426
1427
    if (shouldReplaceEl) {
1428
      this._replaceEl(view);
1429
    } else {
1430
      this.attachHtml(view);
1431
    }
1432
1433
    if (shouldTriggerAttach) {
1434
      view._isAttached = true;
1435
      triggerMethodOn(view, 'attach', view);
1436
    }
1437
  },
1438
  _ensureElement: function _ensureElement() {
1439
    var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
1440
1441
    if (!_.isObject(this.el)) {
1442
      this.$el = this.getEl(this.el);
1443
      this.el = this.$el[0];
1444
    }
1445
1446
    if (!this.$el || this.$el.length === 0) {
1447
      var allowMissingEl = typeof options.allowMissingEl === 'undefined' ? !!_.result(this, 'allowMissingEl') : !!options.allowMissingEl;
1448
1449
      if (allowMissingEl) {
1450
        return false;
1451
      } else {
1452
        throw new MarionetteError('An "el" must exist in DOM for this region ' + this.cid);
1453
      }
1454
    }
1455
    return true;
1456
  },
1457
  _getView: function _getView(view) {
1458
    if (!view) {
1459
      throw new MarionetteError({
1460
        name: 'ViewNotValid',
1461
        message: 'The view passed is undefined and therefore invalid. You must pass a view instance to show.'
1462
      });
1463
    }
1464
1465
    if (view._isDestroyed) {
1466
      throw new MarionetteError({
1467
        name: 'ViewDestroyedError',
1468
        message: 'View (cid: "' + view.cid + '") has already been destroyed and cannot be used.'
1469
      });
1470
    }
1471
1472
    if (view instanceof Backbone.View) {
1473
      return view;
1474
    }
1475
1476
    var viewOptions = this._getViewOptions(view);
1477
1478
    return new View(viewOptions);
1479
  },
1480
1481
1482
  // This allows for a template or a static string to be
1483
  // used as a template
1484
  _getViewOptions: function _getViewOptions(viewOptions) {
1485
    if (_.isFunction(viewOptions)) {
1486
      return { template: viewOptions };
1487
    }
1488
1489
    if (_.isObject(viewOptions)) {
1490
      return viewOptions;
1491
    }
1492
1493
    var template = function template() {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable template already seems to be declared on line 1493. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
1494
      return viewOptions;
1495
    };
1496
1497
    return { template: template };
1498
  },
1499
1500
1501
  // Override this method to change how the region finds the DOM element that it manages. Return
1502
  // a jQuery selector object scoped to a provided parent el or the document if none exists.
1503
  getEl: function getEl(el) {
1504
    return this.findEls(el, _.result(this, 'parentEl'));
1505
  },
1506
  _replaceEl: function _replaceEl(view) {
1507
    // always restore the el to ensure the regions el is present before replacing
1508
    this._restoreEl();
1509
1510
    view.on('before:destroy', this._restoreEl, this);
1511
1512
    this.replaceEl(view.el, this.el);
1513
1514
    this._isReplaced = true;
1515
  },
1516
1517
1518
  // Restore the region's element in the DOM.
1519
  _restoreEl: function _restoreEl() {
1520
    // There is nothing to replace
1521
    if (!this._isReplaced) {
1522
      return;
1523
    }
1524
1525
    var view = this.currentView;
1526
1527
    if (!view) {
1528
      return;
1529
    }
1530
1531
    this._detachView(view);
1532
1533
    this._isReplaced = false;
1534
  },
1535
1536
1537
  // Check to see if the region's el was replaced.
1538
  isReplaced: function isReplaced() {
1539
    return !!this._isReplaced;
1540
  },
1541
1542
1543
  // Check to see if a view is being swapped by another
1544
  isSwappingView: function isSwappingView() {
1545
    return !!this._isSwappingView;
1546
  },
1547
1548
1549
  // Override this method to change how the new view is appended to the `$el` that the
1550
  // region is managing
1551
  attachHtml: function attachHtml(view) {
1552
    this.appendChildren(this.el, view.el);
1553
  },
1554
1555
1556
  // Destroy the current view, if there is one. If there is no current view, it does
1557
  // nothing and returns immediately.
1558
  empty: function empty() {
1559
    var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : { allowMissingEl: true };
1560
1561
    var view = this.currentView;
1562
1563
    // If there is no view in the region we should only detach current html
1564
    if (!view) {
1565
      if (this._ensureElement(options)) {
1566
        this.detachHtml();
1567
      }
1568
      return this;
1569
    }
1570
1571
    var shouldDestroy = !options.preventDestroy;
1572
1573
    if (!shouldDestroy) {
1574
      deprecate('The preventDestroy option is deprecated. Use Region#detachView');
1575
    }
1576
1577
    this._empty(view, shouldDestroy);
1578
    return this;
1579
  },
1580
  _empty: function _empty(view, shouldDestroy) {
1581
    view.off('destroy', this._empty, this);
1582
    this.triggerMethod('before:empty', this, view);
1583
1584
    this._restoreEl();
1585
1586
    delete this.currentView;
1587
1588
    if (!view._isDestroyed) {
1589
      if (shouldDestroy) {
1590
        this.removeView(view);
1591
      } else {
1592
        this._detachView(view);
1593
      }
1594
      this._stopChildViewEvents(view);
1595
    }
1596
1597
    this.triggerMethod('empty', this, view);
1598
  },
1599
  _stopChildViewEvents: function _stopChildViewEvents(view) {
1600
    var parentView = this._parentView;
1601
1602
    if (!parentView) {
1603
      return;
1604
    }
1605
1606
    this._parentView.stopListening(view);
1607
  },
1608
  destroyView: function destroyView(view) {
1609
    if (view._isDestroyed) {
1610
      return view;
1611
    }
1612
1613
    if (view.destroy) {
1614
      view.destroy();
1615
    } else {
1616
      destroyBackboneView(view);
1617
    }
1618
    return view;
1619
  },
1620
  removeView: function removeView(view) {
1621
    this.destroyView(view);
1622
  },
1623
1624
1625
  // Empties the Region without destroying the view
1626
  // Returns the detached view
1627
  detachView: function detachView() {
1628
    var view = this.currentView;
1629
1630
    if (!view) {
1631
      return;
1632
    }
1633
1634
    this._empty(view);
1635
1636
    return view;
1637
  },
1638
  _detachView: function _detachView(view) {
1639
    var shouldTriggerDetach = !!view._isAttached;
1640
    var shouldRestoreEl = this._isReplaced;
1641
    if (shouldTriggerDetach) {
1642
      triggerMethodOn(view, 'before:detach', view);
1643
    }
1644
1645
    if (shouldRestoreEl) {
1646
      this.replaceEl(this.el, view.el);
1647
    } else {
1648
      this.detachHtml();
1649
    }
1650
1651
    if (shouldTriggerDetach) {
1652
      view._isAttached = false;
1653
      triggerMethodOn(view, 'detach', view);
1654
    }
1655
  },
1656
1657
1658
  // Override this method to change how the region detaches current content
1659
  detachHtml: function detachHtml() {
1660
    this.detachContents(this.el);
1661
  },
1662
1663
1664
  // Checks whether a view is currently present within the region. Returns `true` if there is
1665
  // and `false` if no view is present.
1666
  hasView: function hasView() {
1667
    return !!this.currentView;
1668
  },
1669
1670
1671
  // Reset the region by destroying any existing view and clearing out the cached `$el`.
1672
  // The next time a view is shown via this region, the region will re-query the DOM for
1673
  // the region's `el`.
1674
  reset: function reset(options) {
1675
    this.empty(options);
1676
1677
    if (this.$el) {
1678
      this.el = this._initEl;
1679
    }
1680
1681
    delete this.$el;
1682
    return this;
1683
  },
1684
  destroy: function destroy(options) {
1685
    if (this._isDestroyed) {
1686
      return this;
1687
    }
1688
1689
    this.reset(options);
1690
1691
    if (this._name) {
1692
      this._parentView._removeReferences(this._name);
1693
    }
1694
    delete this._parentView;
1695
    delete this._name;
1696
1697
    return MarionetteObject.prototype.destroy.apply(this, arguments);
1698
  }
1699
});
1700
1701
_.extend(Region.prototype, DomMixin);
1702
1703
// return the region instance from the definition
1704
var buildRegion = function (definition, defaults) {
1705
  if (definition instanceof Region) {
1706
    return definition;
1707
  }
1708
1709
  return buildRegionFromDefinition(definition, defaults);
1710
};
1711
1712
function buildRegionFromDefinition(definition, defaults) {
1713
  var opts = _.extend({}, defaults);
1714
1715
  if (_.isString(definition)) {
1716
    _.extend(opts, { el: definition });
1717
1718
    return buildRegionFromObject(opts);
1719
  }
1720
1721
  if (_.isFunction(definition)) {
1722
    _.extend(opts, { regionClass: definition });
1723
1724
    return buildRegionFromObject(opts);
1725
  }
1726
1727
  if (_.isObject(definition)) {
1728
    if (definition.selector) {
1729
      deprecate('The selector option on a Region definition object is deprecated. Use el to pass a selector string');
1730
    }
1731
1732
    _.extend(opts, { el: definition.selector }, definition);
1733
1734
    return buildRegionFromObject(opts);
1735
  }
1736
1737
  throw new MarionetteError({
1738
    message: 'Improper region configuration type.',
1739
    url: 'marionette.region.html#region-configuration-types'
1740
  });
1741
}
1742
1743
function buildRegionFromObject(definition) {
1744
  var RegionClass = definition.regionClass;
1745
1746
  var options = _.omit(definition, 'regionClass');
1747
1748
  return new RegionClass(options);
1749
}
1750
1751
// MixinOptions
1752
// - regions
1753
// - regionClass
1754
1755
var RegionsMixin = {
1756
  regionClass: Region,
1757
1758
  // Internal method to initialize the regions that have been defined in a
1759
  // `regions` attribute on this View.
1760
  _initRegions: function _initRegions() {
1761
1762
    // init regions hash
1763
    this.regions = this.regions || {};
1764
    this._regions = {};
1765
1766
    this.addRegions(_.result(this, 'regions'));
1767
  },
1768
1769
1770
  // Internal method to re-initialize all of the regions by updating
1771
  // the `el` that they point to
1772
  _reInitRegions: function _reInitRegions() {
1773
    _invoke(this._regions, 'reset');
1774
  },
1775
1776
1777
  // Add a single region, by name, to the View
1778
  addRegion: function addRegion(name, definition) {
1779
    var regions = {};
1780
    regions[name] = definition;
1781
    return this.addRegions(regions)[name];
1782
  },
1783
1784
1785
  // Add multiple regions as a {name: definition, name2: def2} object literal
1786
  addRegions: function addRegions(regions) {
1787
    // If there's nothing to add, stop here.
1788
    if (_.isEmpty(regions)) {
1789
      return;
1790
    }
1791
1792
    // Normalize region selectors hash to allow
1793
    // a user to use the @ui. syntax.
1794
    regions = this.normalizeUIValues(regions, ['selector', 'el']);
1795
1796
    // Add the regions definitions to the regions property
1797
    this.regions = _.extend({}, this.regions, regions);
1798
1799
    return this._addRegions(regions);
1800
  },
1801
1802
1803
  // internal method to build and add regions
1804
  _addRegions: function _addRegions(regionDefinitions) {
1805
    var _this = this;
1806
1807
    var defaults = {
1808
      regionClass: this.regionClass,
1809
      parentEl: _.partial(_.result, this, 'el')
1810
    };
1811
1812
    return _.reduce(regionDefinitions, function (regions, definition, name) {
1813
      regions[name] = buildRegion(definition, defaults);
1814
      _this._addRegion(regions[name], name);
1815
      return regions;
1816
    }, {});
1817
  },
1818
  _addRegion: function _addRegion(region, name) {
1819
    this.triggerMethod('before:add:region', this, name, region);
1820
1821
    region._parentView = this;
1822
    region._name = name;
1823
1824
    this._regions[name] = region;
1825
1826
    this.triggerMethod('add:region', this, name, region);
1827
  },
1828
1829
1830
  // Remove a single region from the View, by name
1831
  removeRegion: function removeRegion(name) {
1832
    var region = this._regions[name];
1833
1834
    this._removeRegion(region, name);
1835
1836
    return region;
1837
  },
1838
1839
1840
  // Remove all regions from the View
1841
  removeRegions: function removeRegions() {
1842
    var regions = this._getRegions();
1843
1844
    _.each(this._regions, _.bind(this._removeRegion, this));
1845
1846
    return regions;
1847
  },
1848
  _removeRegion: function _removeRegion(region, name) {
1849
    this.triggerMethod('before:remove:region', this, name, region);
1850
1851
    region.destroy();
1852
1853
    this.triggerMethod('remove:region', this, name, region);
1854
  },
1855
1856
1857
  // Called in a region's destroy
1858
  _removeReferences: function _removeReferences(name) {
1859
    delete this.regions[name];
1860
    delete this._regions[name];
1861
  },
1862
1863
1864
  // Empty all regions in the region manager, but
1865
  // leave them attached
1866
  emptyRegions: function emptyRegions() {
1867
    var regions = this.getRegions();
1868
    _invoke(regions, 'empty');
1869
    return regions;
1870
  },
1871
1872
1873
  // Checks to see if view contains region
1874
  // Accepts the region name
1875
  // hasRegion('main')
1876
  hasRegion: function hasRegion(name) {
1877
    return !!this.getRegion(name);
1878
  },
1879
1880
1881
  // Provides access to regions
1882
  // Accepts the region name
1883
  // getRegion('main')
1884
  getRegion: function getRegion(name) {
1885
    if (!this._isRendered) {
1886
      this.render();
1887
    }
1888
    return this._regions[name];
1889
  },
1890
1891
1892
  // Get all regions
1893
  _getRegions: function _getRegions() {
1894
    return _.clone(this._regions);
1895
  },
1896
  getRegions: function getRegions() {
1897
    if (!this._isRendered) {
1898
      this.render();
1899
    }
1900
    return this._getRegions();
1901
  },
1902
  showChildView: function showChildView(name, view) {
1903
    var region = this.getRegion(name);
1904
1905
    for (var _len = arguments.length, args = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
1906
      args[_key - 2] = arguments[_key];
1907
    }
1908
1909
    return region.show.apply(region, [view].concat(args));
1910
  },
1911
  detachChildView: function detachChildView(name) {
1912
    return this.getRegion(name).detachView();
1913
  },
1914
  getChildView: function getChildView(name) {
1915
    return this.getRegion(name).currentView;
1916
  }
1917
};
1918
1919
// Renderer
1920
// --------
1921
1922
// Render a template with data by passing in the template
1923
// selector and the data to render.
1924
var Renderer = {
1925
1926
  // Render a template with data. The `template` parameter is
1927
  // passed to the `TemplateCache` object to retrieve the
1928
  // template function. Override this method to provide your own
1929
  // custom rendering and template handling for all of Marionette.
1930
  render: function render(template, data) {
1931
    if (!template) {
1932
      throw new MarionetteError({
1933
        name: 'TemplateNotFoundError',
1934
        message: 'Cannot render the template since its false, null or undefined.'
1935
      });
1936
    }
1937
1938
    var templateFunc = _.isFunction(template) ? template : TemplateCache.get(template);
1939
1940
    return templateFunc(data);
1941
  }
1942
};
1943
1944
// View
1945
// ---------
1946
1947
var ClassOptions$1 = ['behaviors', 'childViewEventPrefix', 'childViewEvents', 'childViewTriggers', 'collectionEvents', 'events', 'modelEvents', 'regionClass', 'regions', 'template', 'templateContext', 'triggers', 'ui'];
1948
1949
// The standard view. Includes view events, automatic rendering
1950
// of Underscore templates, nested views, and more.
1951
var View = Backbone.View.extend({
1952
  constructor: function constructor(options) {
1953
    this.render = _.bind(this.render, this);
1954
1955
    this._setOptions(options);
1956
1957
    this.mergeOptions(options, ClassOptions$1);
1958
1959
    monitorViewEvents(this);
1960
1961
    this._initBehaviors();
1962
    this._initRegions();
1963
1964
    var args = Array.prototype.slice.call(arguments);
1965
    args[0] = this.options;
1966
    Backbone.View.prototype.constructor.apply(this, args);
1967
1968
    this.delegateEntityEvents();
1969
1970
    this._triggerEventOnBehaviors('initialize', this);
1971
  },
1972
1973
1974
  // Serialize the view's model *or* collection, if
1975
  // it exists, for the template
1976
  serializeData: function serializeData() {
1977
    if (!this.model && !this.collection) {
1978
      return {};
1979
    }
1980
1981
    // If we have a model, we serialize that
1982
    if (this.model) {
1983
      return this.serializeModel();
1984
    }
1985
1986
    // Otherwise, we serialize the collection,
1987
    // making it available under the `items` property
1988
    return {
1989
      items: this.serializeCollection()
1990
    };
1991
  },
1992
1993
1994
  // Prepares the special `model` property of a view
1995
  // for being displayed in the template. By default
1996
  // we simply clone the attributes. Override this if
1997
  // you need a custom transformation for your view's model
1998
  serializeModel: function serializeModel() {
1999
    if (!this.model) {
2000
      return {};
2001
    }
2002
    return _.clone(this.model.attributes);
2003
  },
2004
2005
2006
  // Serialize a collection by cloning each of
2007
  // its model's attributes
2008
  serializeCollection: function serializeCollection() {
2009
    if (!this.collection) {
2010
      return {};
2011
    }
2012
    return this.collection.map(function (model) {
2013
      return _.clone(model.attributes);
2014
    });
2015
  },
2016
2017
2018
  // Overriding Backbone.View's `setElement` to handle
2019
  // if an el was previously defined. If so, the view might be
2020
  // rendered or attached on setElement.
2021
  setElement: function setElement() {
2022
    var hasEl = !!this.el;
2023
2024
    Backbone.View.prototype.setElement.apply(this, arguments);
2025
2026
    if (hasEl) {
2027
      this._isRendered = !!this.$el.length;
2028
      this._isAttached = isNodeAttached(this.el);
2029
    }
2030
2031
    if (this._isRendered) {
2032
      this.bindUIElements();
2033
    }
2034
2035
    return this;
2036
  },
2037
2038
2039
  // Render the view, defaulting to underscore.js templates.
2040
  // You can override this in your view definition to provide
2041
  // a very specific rendering for your view. In general, though,
2042
  // you should override the `Marionette.Renderer` object to
2043
  // change how Marionette renders views.
2044
  // Subsequent renders after the first will re-render all nested
2045
  // views.
2046
  render: function render() {
2047
    if (this._isDestroyed) {
2048
      return this;
2049
    }
2050
2051
    this.triggerMethod('before:render', this);
2052
2053
    // If this is not the first render call, then we need to
2054
    // re-initialize the `el` for each region
2055
    if (this._isRendered) {
2056
      this._reInitRegions();
2057
    }
2058
2059
    this._renderTemplate();
2060
    this.bindUIElements();
2061
2062
    this._isRendered = true;
2063
    this.triggerMethod('render', this);
2064
2065
    return this;
2066
  },
2067
2068
2069
  // Internal method to render the template with the serialized data
2070
  // and template context via the `Marionette.Renderer` object.
2071
  _renderTemplate: function _renderTemplate() {
2072
    var template = this.getTemplate();
2073
2074
    // Allow template-less views
2075
    if (template === false) {
2076
      deprecate('template:false is deprecated.  Use _.noop.');
2077
      return;
2078
    }
2079
2080
    // Add in entity data and template context
2081
    var data = this.mixinTemplateContext(this.serializeData());
2082
2083
    // Render and add to el
2084
    var html = this._renderHtml(template, data);
2085
    this.attachElContent(html);
2086
  },
2087
2088
2089
  // Renders the data into the template
2090
  _renderHtml: function _renderHtml(template, data) {
2091
    return Renderer.render(template, data, this);
2092
  },
2093
2094
2095
  // Get the template for this view
2096
  // instance. You can set a `template` attribute in the view
2097
  // definition or pass a `template: "whatever"` parameter in
2098
  // to the constructor options.
2099
  getTemplate: function getTemplate() {
2100
    return this.template;
2101
  },
2102
2103
2104
  // Mix in template context methods. Looks for a
2105
  // `templateContext` attribute, which can either be an
2106
  // object literal, or a function that returns an object
2107
  // literal. All methods and attributes from this object
2108
  // are copies to the object passed in.
2109
  mixinTemplateContext: function mixinTemplateContext() {
2110
    var target = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
2111
2112
    var templateContext = _.result(this, 'templateContext');
2113
    return _.extend(target, templateContext);
2114
  },
2115
2116
2117
  // Attaches the content of a given view.
2118
  // This method can be overridden to optimize rendering,
2119
  // or to render in a non standard way.
2120
  //
2121
  // For example, using `innerHTML` instead of `$el.html`
2122
  //
2123
  // ```js
2124
  // attachElContent(html) {
2125
  //   this.el.innerHTML = html;
2126
  //   return this;
2127
  // }
2128
  // ```
2129
  attachElContent: function attachElContent(html) {
2130
    this.setInnerContent(this.el, html);
2131
2132
    return this;
2133
  },
2134
2135
2136
  // called by ViewMixin destroy
2137
  _removeChildren: function _removeChildren() {
2138
    this.removeRegions();
2139
  },
2140
  _getImmediateChildren: function _getImmediateChildren() {
2141
    return _.chain(this._getRegions()).map('currentView').compact().value();
2142
  }
2143
}, {
2144
  // Sets the renderer for the Marionette.View class
2145
  setRenderer: function setRenderer(renderer) {
2146
    this.prototype._renderHtml = renderer;
2147
  }
2148
});
2149
2150
_.extend(View.prototype, ViewMixin, RegionsMixin);
2151
2152
// Mix in methods from Underscore, for iteration, and other
2153
// collection related features.
2154
// Borrowing this code from Backbone.Collection:
2155
// https://github.com/jashkenas/backbone/blob/1.1.2/backbone.js#L962
2156
2157
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', 'partition'];
2158
2159
var emulateCollection = function emulateCollection(object, listProperty) {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable emulateCollection already seems to be declared on line 2159. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
2160
  _.each(methods, function (method) {
2161
    object[method] = function () {
2162
      var list = _.values(_.result(this, listProperty));
2163
      var args = [list].concat(_.toArray(arguments));
2164
      return _[method].apply(_, args);
2165
    };
2166
  });
2167
};
2168
2169
// Provide a container to store, retrieve and
2170
// shut down child views.
2171
var Container = function Container(views) {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable Container already seems to be declared on line 2171. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
2172
  this._views = {};
2173
  this._indexByModel = {};
2174
  this._indexByCustom = {};
2175
  this._updateLength();
2176
2177
  _.each(views, _.bind(this.add, this));
2178
};
2179
2180
emulateCollection(Container.prototype, '_views');
2181
2182
// Container Methods
2183
// -----------------
2184
2185
_.extend(Container.prototype, {
2186
2187
  // Add a view to this container. Stores the view
2188
  // by `cid` and makes it searchable by the model
2189
  // cid (and model itself). Optionally specify
2190
  // a custom key to store an retrieve the view.
2191
  add: function add(view, customIndex) {
2192
    return this._add(view, customIndex)._updateLength();
2193
  },
2194
2195
2196
  // To be used when avoiding call _updateLength
2197
  // When you are done adding all your new views
2198
  // call _updateLength
2199
  _add: function _add(view, customIndex) {
2200
    var viewCid = view.cid;
2201
2202
    // store the view
2203
    this._views[viewCid] = view;
2204
2205
    // index it by model
2206
    if (view.model) {
2207
      this._indexByModel[view.model.cid] = viewCid;
2208
    }
2209
2210
    // index by custom
2211
    if (customIndex) {
2212
      this._indexByCustom[customIndex] = viewCid;
2213
    }
2214
2215
    return this;
2216
  },
2217
2218
2219
  // Find a view by the model that was attached to
2220
  // it. Uses the model's `cid` to find it.
2221
  findByModel: function findByModel(model) {
2222
    return this.findByModelCid(model.cid);
2223
  },
2224
2225
2226
  // Find a view by the `cid` of the model that was attached to
2227
  // it. Uses the model's `cid` to find the view `cid` and
2228
  // retrieve the view using it.
2229
  findByModelCid: function findByModelCid(modelCid) {
2230
    var viewCid = this._indexByModel[modelCid];
2231
    return this.findByCid(viewCid);
2232
  },
2233
2234
2235
  // Find a view by a custom indexer.
2236
  findByCustom: function findByCustom(index) {
2237
    var viewCid = this._indexByCustom[index];
2238
    return this.findByCid(viewCid);
2239
  },
2240
2241
2242
  // Find by index. This is not guaranteed to be a
2243
  // stable index.
2244
  findByIndex: function findByIndex(index) {
2245
    return _.values(this._views)[index];
2246
  },
2247
2248
2249
  // retrieve a view by its `cid` directly
2250
  findByCid: function findByCid(cid) {
2251
    return this._views[cid];
2252
  },
2253
2254
2255
  // Remove a view
2256
  remove: function remove(view) {
2257
    return this._remove(view)._updateLength();
2258
  },
2259
2260
2261
  // To be used when avoiding call _updateLength
2262
  // When you are done adding all your new views
2263
  // call _updateLength
2264
  _remove: function _remove(view) {
2265
    var viewCid = view.cid;
2266
2267
    // delete model index
2268
    if (view.model) {
2269
      delete this._indexByModel[view.model.cid];
2270
    }
2271
2272
    // delete custom index
2273
    _.some(this._indexByCustom, _.bind(function (cid, key) {
2274
      if (cid === viewCid) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if cid === viewCid is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
2275
        delete this._indexByCustom[key];
2276
        return true;
2277
      }
2278
    }, this));
2279
2280
    // remove the view from the container
2281
    delete this._views[viewCid];
2282
2283
    return this;
2284
  },
2285
2286
2287
  // Update the `.length` attribute on this container
2288
  _updateLength: function _updateLength() {
2289
    this.length = _.size(this._views);
2290
2291
    return this;
2292
  }
2293
});
2294
2295
// Collection View
2296
// ---------------
2297
2298
var ClassOptions$3 = ['behaviors', 'childView', 'childViewEventPrefix', 'childViewEvents', 'childViewOptions', 'childViewTriggers', 'collectionEvents', 'events', 'filter', 'emptyView', 'emptyViewOptions', 'modelEvents', 'reorderOnSort', 'sort', 'triggers', 'ui', 'viewComparator'];
2299
2300
// A view that iterates over a Backbone.Collection
2301
// and renders an individual child view for each model.
2302
var CollectionView = Backbone.View.extend({
2303
2304
  // flag for maintaining the sorted order of the collection
2305
  sort: true,
2306
2307
  // constructor
2308
  // option to pass `{sort: false}` to prevent the `CollectionView` from
2309
  // maintaining the sorted order of the collection.
2310
  // This will fallback onto appending childView's to the end.
2311
  //
2312
  // option to pass `{viewComparator: compFunction()}` to allow the `CollectionView`
2313
  // to use a custom sort order for the collection.
2314
  constructor: function constructor(options) {
2315
    this.render = _.bind(this.render, this);
2316
2317
    this._setOptions(options);
2318
2319
    this.mergeOptions(options, ClassOptions$3);
2320
2321
    monitorViewEvents(this);
2322
2323
    this._initBehaviors();
2324
    this.once('render', this._initialEvents);
2325
    this._initChildViewStorage();
2326
    this._bufferedChildren = [];
2327
2328
    var args = Array.prototype.slice.call(arguments);
2329
    args[0] = this.options;
2330
    Backbone.View.prototype.constructor.apply(this, args);
2331
2332
    this.delegateEntityEvents();
2333
2334
    this._triggerEventOnBehaviors('initialize', this);
2335
  },
2336
2337
2338
  // Instead of inserting elements one by one into the page, it's much more performant to insert
2339
  // elements into a document fragment and then insert that document fragment into the page
2340
  _startBuffering: function _startBuffering() {
2341
    this._isBuffering = true;
2342
  },
2343
  _endBuffering: function _endBuffering() {
2344
    var shouldTriggerAttach = !!this._isAttached;
2345
    var triggerOnChildren = shouldTriggerAttach ? this._getImmediateChildren() : [];
2346
2347
    this._isBuffering = false;
2348
2349
    _.each(triggerOnChildren, function (child) {
2350
      triggerMethodOn(child, 'before:attach', child);
2351
    });
2352
2353
    this.attachBuffer(this, this._createBuffer());
2354
2355
    _.each(triggerOnChildren, function (child) {
2356
      child._isAttached = true;
2357
      triggerMethodOn(child, 'attach', child);
2358
    });
2359
2360
    this._bufferedChildren = [];
2361
  },
2362
  _getImmediateChildren: function _getImmediateChildren() {
2363
    return _.values(this.children._views);
2364
  },
2365
2366
2367
  // Configured the initial events that the collection view binds to.
2368
  _initialEvents: function _initialEvents() {
2369
    if (this.collection) {
2370
      this.listenTo(this.collection, 'add', this._onCollectionAdd);
2371
      this.listenTo(this.collection, 'update', this._onCollectionUpdate);
2372
      this.listenTo(this.collection, 'reset', this.render);
2373
2374
      if (this.sort) {
2375
        this.listenTo(this.collection, 'sort', this._sortViews);
2376
      }
2377
    }
2378
  },
2379
2380
2381
  // Handle a child added to the collection
2382
  _onCollectionAdd: function _onCollectionAdd(child, collection, opts) {
2383
    // `index` is present when adding with `at` since BB 1.2; indexOf fallback for < 1.2
2384
    var index = opts.at !== undefined && (opts.index || collection.indexOf(child));
2385
2386
    // When filtered or when there is no initial index, calculate index.
2387
    if (this.filter || index === false) {
2388
      index = _.indexOf(this._filteredSortedModels(index), child);
2389
    }
2390
2391
    if (this._shouldAddChild(child, index)) {
2392
      this._destroyEmptyView();
2393
      this._addChild(child, index);
2394
    }
2395
  },
2396
2397
2398
  // Handle collection update model removals
2399
  _onCollectionUpdate: function _onCollectionUpdate(collection, options) {
2400
    var changes = options.changes;
2401
    this._removeChildModels(changes.removed);
2402
  },
2403
2404
2405
  // Remove the child views and destroy them.
2406
  // This function also updates the indices of later views
2407
  // in the collection in order to keep the children in sync with the collection.
2408
  // "models" is an array of models and the corresponding views
2409
  // will be removed and destroyed from the CollectionView
2410
  _removeChildModels: function _removeChildModels(models) {
2411
    // Used to determine where to update the remaining
2412
    // sibling view indices after these views are removed.
2413
    var removedViews = this._getRemovedViews(models);
2414
2415
    if (!removedViews.length) {
2416
      return;
2417
    }
2418
2419
    this.children._updateLength();
2420
2421
    // decrement the index of views after this one
2422
    this._updateIndices(removedViews, false);
2423
2424
    if (this.isEmpty()) {
2425
      this._showEmptyView();
2426
    }
2427
  },
2428
2429
2430
  // Returns the views that will be used for re-indexing
2431
  // through CollectionView#_updateIndices.
2432
  _getRemovedViews: function _getRemovedViews(models) {
2433
    var _this = this;
2434
2435
    // Returning a view means something was removed.
2436
    return _.reduce(models, function (removingViews, model) {
2437
      var view = model && _this.children.findByModel(model);
2438
2439
      if (!view || view._isDestroyed) {
2440
        return removingViews;
2441
      }
2442
2443
      _this._removeChildView(view);
2444
2445
      removingViews.push(view);
2446
2447
      return removingViews;
2448
    }, []);
2449
  },
2450
  _removeChildView: function _removeChildView(view) {
2451
    this.triggerMethod('before:remove:child', this, view);
2452
2453
    this.children._remove(view);
2454
    if (view.destroy) {
2455
      view.destroy();
2456
    } else {
2457
      destroyBackboneView(view);
2458
    }
2459
2460
    this.stopListening(view);
2461
    this.triggerMethod('remove:child', this, view);
2462
  },
2463
2464
2465
  // Overriding Backbone.View's `setElement` to handle
2466
  // if an el was previously defined. If so, the view might be
2467
  // attached on setElement.
2468
  setElement: function setElement() {
2469
    var hasEl = !!this.el;
2470
2471
    Backbone.View.prototype.setElement.apply(this, arguments);
2472
2473
    if (hasEl) {
2474
      this._isAttached = isNodeAttached(this.el);
2475
    }
2476
2477
    return this;
2478
  },
2479
2480
2481
  // Render children views. Override this method to provide your own implementation of a
2482
  // render function for the collection view.
2483
  render: function render() {
2484
    if (this._isDestroyed) {
2485
      return this;
2486
    }
2487
    this.triggerMethod('before:render', this);
2488
    this._renderChildren();
2489
    this._isRendered = true;
2490
    this.triggerMethod('render', this);
2491
    return this;
2492
  },
2493
2494
2495
  // An efficient rendering used for filtering. Instead of modifying the whole DOM for the
2496
  // collection view, we are only adding or removing the related childrenViews.
2497
  setFilter: function setFilter(filter) {
2498
    var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
2499
        preventRender = _ref.preventRender;
2500
2501
    var canBeRendered = this._isRendered && !this._isDestroyed;
2502
    var filterChanged = this.filter !== filter;
2503
    var shouldRender = canBeRendered && filterChanged && !preventRender;
2504
2505
    if (shouldRender) {
2506
      var previousModels = this._filteredSortedModels();
2507
      this.filter = filter;
2508
      var models = this._filteredSortedModels();
2509
      this._applyModelDeltas(models, previousModels);
2510
    } else {
2511
      this.filter = filter;
2512
    }
2513
2514
    return this;
2515
  },
2516
2517
2518
  // `removeFilter` is actually an alias for removing filters.
2519
  removeFilter: function removeFilter(options) {
2520
    return this.setFilter(null, options);
2521
  },
2522
2523
2524
  // Calculate and apply difference by cid between `models` and `previousModels`.
2525
  _applyModelDeltas: function _applyModelDeltas(models, previousModels) {
2526
    var _this2 = this;
2527
2528
    var currentIds = {};
2529
    _.each(models, function (model, index) {
2530
      var addedChildNotExists = !_this2.children.findByModel(model);
2531
      if (addedChildNotExists) {
2532
        _this2._onCollectionAdd(model, _this2.collection, { at: index });
2533
      }
2534
      currentIds[model.cid] = true;
2535
    });
2536
2537
    var removeModels = _.filter(previousModels, function (prevModel) {
2538
      return !currentIds[prevModel.cid] && _this2.children.findByModel(prevModel);
2539
    });
2540
2541
    this._removeChildModels(removeModels);
2542
  },
2543
2544
2545
  // Reorder DOM after sorting. When your element's rendering do not use their index,
2546
  // you can pass reorderOnSort: true to only reorder the DOM after a sort instead of
2547
  // rendering all the collectionView.
2548
  reorder: function reorder() {
2549
    var children = this.children;
2550
    var models = this._filteredSortedModels();
2551
2552
    if (!models.length && this._showingEmptyView) {
2553
      return this;
2554
    }
2555
2556
    var anyModelsAdded = _.some(models, function (model) {
2557
      return !children.findByModel(model);
2558
    });
2559
2560
    // If there are any new models added due to filtering we need to add child views,
2561
    // so render as normal.
2562
    if (anyModelsAdded) {
2563
      this.render();
2564
    } else {
2565
2566
      var filteredOutModels = [];
2567
2568
      // Get the DOM nodes in the same order as the models and
2569
      // find the model that were children before but aren't in this new order.
2570
      var elsToReorder = children.reduce(function (viewEls, view) {
2571
        var index = _.indexOf(models, view.model);
2572
2573
        if (index === -1) {
2574
          filteredOutModels.push(view.model);
2575
          return viewEls;
2576
        }
2577
2578
        view._index = index;
2579
2580
        viewEls[index] = view.el;
2581
2582
        return viewEls;
2583
      }, new Array(models.length));
2584
2585
      this.triggerMethod('before:reorder', this);
2586
2587
      // Since append moves elements that are already in the DOM, appending the elements
2588
      // will effectively reorder them.
2589
      this._appendReorderedChildren(elsToReorder);
2590
2591
      // remove any views that have been filtered out
2592
      this._removeChildModels(filteredOutModels);
2593
2594
      this.triggerMethod('reorder', this);
2595
    }
2596
    return this;
2597
  },
2598
2599
2600
  // Render view after sorting. Override this method to change how the view renders
2601
  // after a `sort` on the collection.
2602
  resortView: function resortView() {
2603
    if (this.reorderOnSort) {
2604
      this.reorder();
2605
    } else {
2606
      this._renderChildren();
2607
    }
2608
    return this;
2609
  },
2610
2611
2612
  // Internal method. This checks for any changes in the order of the collection.
2613
  // If the index of any view doesn't match, it will render.
2614
  _sortViews: function _sortViews() {
2615
    var _this3 = this;
2616
2617
    var models = this._filteredSortedModels();
2618
2619
    // check for any changes in sort order of views
2620
    var orderChanged = _.find(models, function (item, index) {
2621
      var view = _this3.children.findByModel(item);
2622
      return !view || view._index !== index;
2623
    });
2624
2625
    if (orderChanged) {
2626
      this.resortView();
2627
    }
2628
  },
2629
2630
2631
  // Internal reference to what index a `emptyView` is.
2632
  _emptyViewIndex: -1,
2633
2634
  // Internal method. Separated so that CompositeView can append to the childViewContainer
2635
  // if necessary
2636
  _appendReorderedChildren: function _appendReorderedChildren(children) {
2637
    this.appendChildren(this.el, children);
2638
  },
2639
2640
2641
  // Internal method. Separated so that CompositeView can have more control over events
2642
  // being triggered, around the rendering process
2643
  _renderChildren: function _renderChildren() {
2644
    if (this._isRendered) {
2645
      this._destroyEmptyView();
2646
      this._destroyChildren();
2647
    }
2648
2649
    var models = this._filteredSortedModels();
2650
    if (this.isEmpty({ processedModels: models })) {
2651
      this._showEmptyView();
2652
    } else {
2653
      this.triggerMethod('before:render:children', this);
2654
      this._startBuffering();
2655
      this._showCollection(models);
2656
      this._endBuffering();
2657
      this.triggerMethod('render:children', this);
2658
    }
2659
  },
2660
  _createView: function _createView(model, index) {
2661
    var ChildView = this._getChildView(model);
2662
    var childViewOptions = this._getChildViewOptions(model, index);
2663
    var view = this.buildChildView(model, ChildView, childViewOptions);
2664
    return view;
2665
  },
2666
  _setupChildView: function _setupChildView(view, index) {
2667
    monitorViewEvents(view);
2668
2669
    // set up the child view event forwarding
2670
    this._proxyChildViewEvents(view);
2671
2672
    if (this.sort) {
2673
      view._index = index;
2674
    }
2675
  },
2676
2677
2678
  // Internal method to loop through collection and show each child view.
2679
  _showCollection: function _showCollection(models) {
2680
    _.each(models, _.bind(this._addChild, this));
2681
    this.children._updateLength();
2682
  },
2683
2684
2685
  // Allow the collection to be sorted by a custom view comparator
2686
  _filteredSortedModels: function _filteredSortedModels(addedAt) {
2687
    if (!this.collection || !this.collection.length) {
2688
      return [];
2689
    }
2690
2691
    var viewComparator = this.getViewComparator();
2692
    var models = this.collection.models;
2693
    addedAt = Math.min(Math.max(addedAt, 0), models.length - 1);
2694
2695
    if (viewComparator) {
2696
      var addedModel = void 0;
0 ignored issues
show
Coding Style introduced by
Consider using undefined instead of void(0). It is equivalent and more straightforward to read.
Loading history...
2697
      // Preserve `at` location, even for a sorted view
2698
      if (addedAt) {
2699
        addedModel = models[addedAt];
2700
        models = models.slice(0, addedAt).concat(models.slice(addedAt + 1));
2701
      }
2702
      models = this._sortModelsBy(models, viewComparator);
2703
      if (addedModel) {
2704
        models.splice(addedAt, 0, addedModel);
2705
      }
2706
    }
2707
2708
    // Filter after sorting in case the filter uses the index
2709
    models = this._filterModels(models);
2710
2711
    return models;
2712
  },
2713
  getViewComparator: function getViewComparator() {
2714
    return this.viewComparator;
2715
  },
2716
2717
2718
  // Filter an array of models, if a filter exists
2719
  _filterModels: function _filterModels(models) {
2720
    var _this4 = this;
2721
2722
    if (this.filter) {
2723
      models = _.filter(models, function (model, index) {
2724
        return _this4._shouldAddChild(model, index);
2725
      });
2726
    }
2727
    return models;
2728
  },
2729
  _sortModelsBy: function _sortModelsBy(models, comparator) {
2730
    if (typeof comparator === 'string') {
2731
      return _.sortBy(models, function (model) {
2732
        return model.get(comparator);
2733
      });
2734
    } else if (comparator.length === 1) {
2735
      return _.sortBy(models, _.bind(comparator, this));
2736
    } else {
2737
      return _.clone(models).sort(_.bind(comparator, this));
2738
    }
2739
  },
2740
2741
2742
  // Internal method to show an empty view in place of a collection of child views,
2743
  // when the collection is empty
2744
  _showEmptyView: function _showEmptyView() {
2745
    var EmptyView = this._getEmptyView();
2746
2747
    if (EmptyView && !this._showingEmptyView) {
2748
      this._showingEmptyView = true;
2749
2750
      var model = new Backbone.Model();
2751
      var emptyViewOptions = this.emptyViewOptions || this.childViewOptions;
2752
      if (_.isFunction(emptyViewOptions)) {
2753
        emptyViewOptions = emptyViewOptions.call(this, model, this._emptyViewIndex);
2754
      }
2755
2756
      var view = this.buildChildView(model, EmptyView, emptyViewOptions);
2757
2758
      this.triggerMethod('before:render:empty', this, view);
2759
      this.addChildView(view, 0);
2760
      this.triggerMethod('render:empty', this, view);
2761
    }
2762
  },
2763
2764
2765
  // Internal method to destroy an existing emptyView instance if one exists. Called when
2766
  // a collection view has been rendered empty, and then a child is added to the collection.
2767
  _destroyEmptyView: function _destroyEmptyView() {
2768
    if (this._showingEmptyView) {
2769
      this.triggerMethod('before:remove:empty', this);
2770
2771
      this._destroyChildren();
2772
      delete this._showingEmptyView;
2773
2774
      this.triggerMethod('remove:empty', this);
2775
    }
2776
  },
2777
2778
2779
  // Retrieve the empty view class
2780
  _getEmptyView: function _getEmptyView() {
2781
    var emptyView = this.emptyView;
2782
2783
    if (!emptyView) {
2784
      return;
2785
    }
2786
2787
    return this._getView(emptyView);
2788
  },
2789
2790
2791
  // Retrieve the `childView` class
2792
  // The `childView` property can be either a view class or a function that
2793
  // returns a view class. If it is a function, it will receive the model that
2794
  // will be passed to the view instance (created from the returned view class)
2795
  _getChildView: function _getChildView(child) {
2796
    var childView = this.childView;
2797
2798
    if (!childView) {
2799
      throw new MarionetteError({
2800
        name: 'NoChildViewError',
2801
        message: 'A "childView" must be specified'
2802
      });
2803
    }
2804
2805
    childView = this._getView(childView, child);
2806
2807
    if (!childView) {
2808
      throw new MarionetteError({
2809
        name: 'InvalidChildViewError',
2810
        message: '"childView" must be a view class or a function that returns a view class'
2811
      });
2812
    }
2813
2814
    return childView;
2815
  },
2816
2817
2818
  // First check if the `view` is a view class (the common case)
2819
  // Then check if it's a function (which we assume that returns a view class)
2820
  _getView: function _getView(view, child) {
2821
    if (view.prototype instanceof Backbone.View || view === Backbone.View) {
2822
      return view;
2823
    } else if (_.isFunction(view)) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if _.isFunction(view) is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
2824
      return view.call(this, child);
2825
    }
2826
  },
2827
2828
2829
  // Internal method for building and adding a child view
2830
  _addChild: function _addChild(child, index) {
2831
    var view = this._createView(child, index);
2832
    this.addChildView(view, index);
2833
2834
    return view;
2835
  },
2836
  _getChildViewOptions: function _getChildViewOptions(child, index) {
2837
    if (_.isFunction(this.childViewOptions)) {
2838
      return this.childViewOptions(child, index);
2839
    }
2840
2841
    return this.childViewOptions;
2842
  },
2843
2844
2845
  // Render the child's view and add it to the HTML for the collection view at a given index.
2846
  // This will also update the indices of later views in the collection in order to keep the
2847
  // children in sync with the collection.
2848
  addChildView: function addChildView(view, index) {
2849
    this.triggerMethod('before:add:child', this, view);
2850
    this._setupChildView(view, index);
2851
2852
    // Store the child view itself so we can properly remove and/or destroy it later
2853
    if (this._isBuffering) {
2854
      // Add to children, but don't update children's length.
2855
      this.children._add(view);
2856
    } else {
2857
      // increment indices of views after this one
2858
      this._updateIndices(view, true);
2859
      this.children.add(view);
2860
    }
2861
2862
    this._renderView(view);
2863
2864
    this._attachView(view, index);
2865
2866
    this.triggerMethod('add:child', this, view);
2867
2868
    return view;
2869
  },
2870
2871
2872
  // Internal method. This decrements or increments the indices of views after the added/removed
2873
  // view to keep in sync with the collection.
2874
  _updateIndices: function _updateIndices(views, increment) {
2875
    if (!this.sort) {
2876
      return;
2877
    }
2878
2879
    if (!increment) {
2880
      _.each(_.sortBy(this.children._views, '_index'), function (view, index) {
2881
        view._index = index;
2882
      });
2883
2884
      return;
2885
    }
2886
2887
    var view = _.isArray(views) ? _.max(views, '_index') : views;
2888
2889
    if (_.isObject(view)) {
2890
      // update the indexes of views after this one
2891
      this.children.each(function (laterView) {
2892
        if (laterView._index >= view._index) {
2893
          laterView._index += 1;
2894
        }
2895
      });
2896
    }
2897
  },
2898
  _renderView: function _renderView(view) {
2899
    if (view._isRendered) {
2900
      return;
2901
    }
2902
2903
    if (!view.supportsRenderLifecycle) {
2904
      triggerMethodOn(view, 'before:render', view);
2905
    }
2906
2907
    view.render();
2908
2909
    if (!view.supportsRenderLifecycle) {
2910
      view._isRendered = true;
2911
      triggerMethodOn(view, 'render', view);
2912
    }
2913
  },
2914
  _attachView: function _attachView(view, index) {
2915
    // Only trigger attach if already attached and not buffering,
2916
    // otherwise _endBuffering() or Region#show() handles this.
2917
    var shouldTriggerAttach = !view._isAttached && !this._isBuffering && this._isAttached;
2918
2919
    if (shouldTriggerAttach) {
2920
      triggerMethodOn(view, 'before:attach', view);
2921
    }
2922
2923
    this.attachHtml(this, view, index);
2924
2925
    if (shouldTriggerAttach) {
2926
      view._isAttached = true;
2927
      triggerMethodOn(view, 'attach', view);
2928
    }
2929
  },
2930
2931
2932
  // Build a `childView` for a model in the collection.
2933
  buildChildView: function buildChildView(child, ChildViewClass, childViewOptions) {
2934
    var options = _.extend({ model: child }, childViewOptions);
2935
    return new ChildViewClass(options);
2936
  },
2937
2938
2939
  // Remove the child view and destroy it. This function also updates the indices of later views
2940
  // in the collection in order to keep the children in sync with the collection.
2941
  removeChildView: function removeChildView(view) {
2942
    if (!view || view._isDestroyed) {
2943
      return view;
2944
    }
2945
2946
    this._removeChildView(view);
2947
    this.children._updateLength();
2948
    // decrement the index of views after this one
2949
    this._updateIndices(view, false);
2950
    return view;
2951
  },
2952
2953
2954
  // check if the collection is empty or optionally whether an array of pre-processed models is empty
2955
  isEmpty: function isEmpty(options) {
2956
    var models = void 0;
0 ignored issues
show
Coding Style introduced by
Consider using undefined instead of void(0). It is equivalent and more straightforward to read.
Loading history...
2957
    if (_.result(options, 'processedModels')) {
2958
      models = options.processedModels;
2959
    } else {
2960
      models = this.collection ? this.collection.models : [];
2961
      models = this._filterModels(models);
2962
    }
2963
    return models.length === 0;
2964
  },
2965
2966
2967
  // You might need to override this if you've overridden attachHtml
2968
  attachBuffer: function attachBuffer(collectionView, buffer) {
2969
    this.appendChildren(collectionView.el, buffer);
2970
  },
2971
2972
2973
  // Create a fragment buffer from the currently buffered children
2974
  _createBuffer: function _createBuffer() {
2975
    var _this5 = this;
2976
2977
    var elBuffer = this.createBuffer();
2978
    _.each(this._bufferedChildren, function (b) {
2979
      _this5.appendChildren(elBuffer, b.el);
2980
    });
2981
    return elBuffer;
2982
  },
2983
2984
2985
  // Append the HTML to the collection's `el`. Override this method to do something other
2986
  // than `.append`.
2987
  attachHtml: function attachHtml(collectionView, childView, index) {
2988
    if (collectionView._isBuffering) {
2989
      // buffering happens on reset events and initial renders
2990
      // in order to reduce the number of inserts into the
2991
      // document, which are expensive.
2992
      collectionView._bufferedChildren.splice(index, 0, childView);
2993
    } else {
2994
      // If we've already rendered the main collection, append
2995
      // the new child into the correct order if we need to. Otherwise
2996
      // append to the end.
2997
      if (!collectionView._insertBefore(childView, index)) {
2998
        collectionView._insertAfter(childView);
2999
      }
3000
    }
3001
  },
3002
3003
3004
  // Internal method. Check whether we need to insert the view into the correct position.
3005
  _insertBefore: function _insertBefore(childView, index) {
3006
    var currentView = void 0;
0 ignored issues
show
Coding Style introduced by
Consider using undefined instead of void(0). It is equivalent and more straightforward to read.
Loading history...
3007
    var findPosition = this.sort && index < this.children.length - 1;
3008
    if (findPosition) {
3009
      // Find the view after this one
3010
      currentView = this.children.find(function (view) {
3011
        return view._index === index + 1;
3012
      });
3013
    }
3014
3015
    if (currentView) {
3016
      this.beforeEl(currentView.el, childView.el);
3017
      return true;
3018
    }
3019
3020
    return false;
3021
  },
3022
3023
3024
  // Internal method. Append a view to the end of the $el
3025
  _insertAfter: function _insertAfter(childView) {
3026
    this.appendChildren(this.el, childView.el);
3027
  },
3028
3029
3030
  // Internal method to set up the `children` object for storing all of the child views
3031
  _initChildViewStorage: function _initChildViewStorage() {
3032
    this.children = new Container();
3033
  },
3034
3035
3036
  // called by ViewMixin destroy
3037
  _removeChildren: function _removeChildren() {
3038
    this._destroyChildren();
3039
  },
3040
3041
3042
  // Destroy the child views that this collection view is holding on to, if any
3043
  _destroyChildren: function _destroyChildren(options) {
0 ignored issues
show
Unused Code introduced by
The parameter options is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
3044
    if (!this.children.length) {
3045
      return;
3046
    }
3047
3048
    this.triggerMethod('before:destroy:children', this);
3049
    this.children.each(_.bind(this._removeChildView, this));
3050
    this.children._updateLength();
3051
    this.triggerMethod('destroy:children', this);
3052
  },
3053
3054
3055
  // Return true if the given child should be shown. Return false otherwise.
3056
  // The filter will be passed (child, index, collection), where
3057
  //  'child' is the given model
3058
  //  'index' is the index of that model in the collection
3059
  //  'collection' is the collection referenced by this CollectionView
3060
  _shouldAddChild: function _shouldAddChild(child, index) {
3061
    var filter = this.filter;
3062
    return !_.isFunction(filter) || filter.call(this, child, index, this.collection);
3063
  }
3064
});
3065
3066
_.extend(CollectionView.prototype, ViewMixin);
3067
3068
// Provide a container to store, retrieve and
3069
// shut down child views.
3070
var Container$1 = function Container$1() {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable Container$1 already seems to be declared on line 3070. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
3071
  this._init();
3072
};
3073
3074
emulateCollection(Container$1.prototype, '_views');
3075
3076
function stringComparator(comparator, view) {
3077
  return view.model && view.model.get(comparator);
3078
}
3079
3080
// Container Methods
3081
// -----------------
3082
3083
_.extend(Container$1.prototype, {
3084
3085
  // Initializes an empty container
3086
  _init: function _init() {
3087
    this._views = [];
3088
    this._viewsByCid = {};
3089
    this._indexByModel = {};
3090
    this._updateLength();
3091
  },
3092
3093
3094
  // Add a view to this container. Stores the view
3095
  // by `cid` and makes it searchable by the model
3096
  // cid (and model itself). Additionally it stores
3097
  // the view by index in the _views array
3098
  _add: function _add(view) {
3099
    var index = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this._views.length;
3100
3101
    var viewCid = view.cid;
3102
3103
    // store the view
3104
    this._viewsByCid[viewCid] = view;
3105
3106
    // index it by model
3107
    if (view.model) {
3108
      this._indexByModel[view.model.cid] = viewCid;
3109
    }
3110
3111
    // add to end by default
3112
    this._views.splice(index, 0, view);
3113
3114
    this._updateLength();
3115
  },
3116
3117
3118
  // Sort (mutate) and return the array of the child views.
3119
  _sort: function _sort(comparator) {
3120
    if (typeof comparator === 'string') {
3121
      comparator = _.partial(stringComparator, comparator);
3122
      return this._sortBy(comparator);
3123
    }
3124
3125
    if (comparator.length === 1) {
3126
      return this._sortBy(comparator);
3127
    }
3128
3129
    return this._views.sort(comparator);
3130
  },
3131
3132
3133
  // Makes `_.sortBy` mutate the array to match `this._views.sort`
3134
  _sortBy: function _sortBy(comparator) {
3135
    var sortedViews = _.sortBy(this._views, comparator);
3136
3137
    this._set(sortedViews);
3138
3139
    return sortedViews;
3140
  },
3141
3142
3143
  // Replace array contents without overwriting the reference.
3144
  _set: function _set(views) {
3145
    this._views.length = 0;
3146
3147
    this._views.push.apply(this._views, views.slice(0));
3148
3149
    this._updateLength();
3150
  },
3151
3152
3153
  // Find a view by the model that was attached to it.
3154
  // Uses the model's `cid` to find it.
3155
  findByModel: function findByModel(model) {
3156
    return this.findByModelCid(model.cid);
3157
  },
3158
3159
3160
  // Find a view by the `cid` of the model that was attached to it.
3161
  // Uses the model's `cid` to find the view `cid` and
3162
  // retrieve the view using it.
3163
  findByModelCid: function findByModelCid(modelCid) {
3164
    var viewCid = this._indexByModel[modelCid];
3165
    return this.findByCid(viewCid);
3166
  },
3167
3168
3169
  // Find a view by index.
3170
  findByIndex: function findByIndex(index) {
3171
    return this._views[index];
3172
  },
3173
3174
3175
  // Find the index of a view instance
3176
  findIndexByView: function findIndexByView(view) {
3177
    return this._views.indexOf(view);
3178
  },
3179
3180
3181
  // Retrieve a view by its `cid` directly
3182
  findByCid: function findByCid(cid) {
3183
    return this._viewsByCid[cid];
3184
  },
3185
3186
3187
  // Remove a view and clean up index references.
3188
  _remove: function _remove(view) {
3189
    if (!this._viewsByCid[view.cid]) {
3190
      return;
3191
    }
3192
3193
    // delete model index
3194
    if (view.model) {
3195
      delete this._indexByModel[view.model.cid];
3196
    }
3197
3198
    // remove the view from the container
3199
    delete this._viewsByCid[view.cid];
3200
3201
    var index = this.findIndexByView(view);
3202
    this._views.splice(index, 1);
3203
3204
    this._updateLength();
3205
  },
3206
3207
3208
  // Update the `.length` attribute on this container
3209
  _updateLength: function _updateLength() {
3210
    this.length = this._views.length;
3211
  }
3212
});
3213
3214
// Next Collection View
3215
// ---------------
3216
3217
var ClassOptions$4 = ['behaviors', 'childView', 'childViewEventPrefix', 'childViewEvents', 'childViewOptions', 'childViewTriggers', 'collectionEvents', 'emptyView', 'emptyViewOptions', 'events', 'modelEvents', 'sortWithCollection', 'triggers', 'ui', 'viewComparator', 'viewFilter'];
3218
3219
// A view that iterates over a Backbone.Collection
3220
// and renders an individual child view for each model.
3221
var CollectionView$2 = Backbone.View.extend({
3222
  // flag for maintaining the sorted order of the collection
3223
  sortWithCollection: true,
3224
3225
  // constructor
3226
  constructor: function constructor(options) {
3227
    this._setOptions(options);
3228
3229
    this.mergeOptions(options, ClassOptions$4);
3230
3231
    monitorViewEvents(this);
3232
3233
    this.once('render', this._initialEvents);
3234
3235
    // This children container isn't really used by a render, but it provides
3236
    // the ability to check `this.children.length` prior to rendering
3237
    // It also allows for cases where only addChildView is used
3238
    this._initChildViewStorage();
3239
    this._initBehaviors();
3240
3241
    var args = Array.prototype.slice.call(arguments);
3242
    args[0] = this.options;
3243
    Backbone.View.prototype.constructor.apply(this, args);
3244
3245
    this._initEmptyRegion();
3246
3247
    this.delegateEntityEvents();
3248
3249
    this._triggerEventOnBehaviors('initialize', this);
3250
  },
3251
3252
3253
  // Internal method to set up the `children` object for storing all of the child views
3254
  _initChildViewStorage: function _initChildViewStorage() {
3255
    this.children = new Container$1();
3256
  },
3257
3258
3259
  // Create an region to show the emptyView
3260
  _initEmptyRegion: function _initEmptyRegion() {
3261
    this.emptyRegion = new Region({ el: this.el });
3262
3263
    this.emptyRegion._parentView = this;
3264
  },
3265
3266
3267
  // Configured the initial events that the collection view binds to.
3268
  _initialEvents: function _initialEvents() {
3269
    this.listenTo(this.collection, {
3270
      'sort': this._onCollectionSort,
3271
      'reset': this._onCollectionReset,
3272
      'update': this._onCollectionUpdate
3273
    });
3274
  },
3275
3276
3277
  // Internal method. This checks for any changes in the order of the collection.
3278
  // If the index of any view doesn't match, it will re-sort.
3279
  _onCollectionSort: function _onCollectionSort() {
3280
    var _this = this;
3281
3282
    if (!this.sortWithCollection) {
3283
      return;
3284
    }
3285
3286
    // If the data is changing we will handle the sort later
3287
    if (this.collection.length !== this.children.length) {
3288
      return;
3289
    }
3290
3291
    // Additional check if the data is changing
3292
    var hasAddedModel = this.collection.some(function (model) {
3293
      return !_this.children.findByModel(model);
3294
    });
3295
3296
    if (hasAddedModel) {
3297
      return;
3298
    }
3299
3300
    // If the only thing happening here is sorting, sort.
3301
    this.sort();
3302
  },
3303
  _onCollectionReset: function _onCollectionReset() {
3304
    this.render();
3305
  },
3306
3307
3308
  // Handle collection update model additions and  removals
3309
  _onCollectionUpdate: function _onCollectionUpdate(collection, options) {
3310
    var changes = options.changes;
3311
3312
    // Remove first since it'll be a shorter array lookup.
3313
    var removedViews = this._removeChildModels(changes.removed);
3314
3315
    this._addChildModels(changes.added);
3316
3317
    this._detachChildren(removedViews);
3318
3319
    this._showChildren();
3320
3321
    // Destroy removed child views after all of the render is complete
3322
    this._removeChildViews(removedViews);
3323
  },
3324
  _removeChildModels: function _removeChildModels(models) {
3325
    return _.map(models, _.bind(this._removeChildModel, this));
3326
  },
3327
  _removeChildModel: function _removeChildModel(model) {
3328
    var view = this.children.findByModel(model);
3329
3330
    this._removeChild(view);
3331
3332
    return view;
3333
  },
3334
  _removeChild: function _removeChild(view) {
3335
    this.triggerMethod('before:remove:child', this, view);
3336
3337
    this.children._remove(view);
3338
3339
    this.triggerMethod('remove:child', this, view);
3340
  },
3341
3342
3343
  // Added views are returned for consistency with _removeChildModels
3344
  _addChildModels: function _addChildModels(models) {
3345
    return _.map(models, _.bind(this._addChildModel, this));
3346
  },
3347
  _addChildModel: function _addChildModel(model) {
3348
    var view = this._createChildView(model);
3349
3350
    this._addChild(view);
3351
3352
    return view;
3353
  },
3354
  _createChildView: function _createChildView(model) {
3355
    var ChildView = this._getChildView(model);
3356
    var childViewOptions = this._getChildViewOptions(model);
3357
    var view = this.buildChildView(model, ChildView, childViewOptions);
3358
3359
    return view;
3360
  },
3361
  _addChild: function _addChild(view, index) {
3362
    this.triggerMethod('before:add:child', this, view);
3363
3364
    this._setupChildView(view);
3365
    this.children._add(view, index);
3366
3367
    this.triggerMethod('add:child', this, view);
3368
  },
3369
3370
3371
  // Retrieve the `childView` class
3372
  // The `childView` property can be either a view class or a function that
3373
  // returns a view class. If it is a function, it will receive the model that
3374
  // will be passed to the view instance (created from the returned view class)
3375
  _getChildView: function _getChildView(child) {
3376
    var childView = this.childView;
3377
3378
    if (!childView) {
3379
      throw new MarionetteError({
3380
        name: 'NoChildViewError',
3381
        message: 'A "childView" must be specified'
3382
      });
3383
    }
3384
3385
    childView = this._getView(childView, child);
3386
3387
    if (!childView) {
3388
      throw new MarionetteError({
3389
        name: 'InvalidChildViewError',
3390
        message: '"childView" must be a view class or a function that returns a view class'
3391
      });
3392
    }
3393
3394
    return childView;
3395
  },
3396
3397
3398
  // First check if the `view` is a view class (the common case)
3399
  // Then check if it's a function (which we assume that returns a view class)
3400
  _getView: function _getView(view, child) {
3401
    if (view.prototype instanceof Backbone.View || view === Backbone.View) {
3402
      return view;
3403
    } else if (_.isFunction(view)) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if _.isFunction(view) is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
3404
      return view.call(this, child);
3405
    }
3406
  },
3407
  _getChildViewOptions: function _getChildViewOptions(child) {
3408
    if (_.isFunction(this.childViewOptions)) {
3409
      return this.childViewOptions(child);
3410
    }
3411
3412
    return this.childViewOptions;
3413
  },
3414
3415
3416
  // Build a `childView` for a model in the collection.
3417
  // Override to customize the build
3418
  buildChildView: function buildChildView(child, ChildViewClass, childViewOptions) {
3419
    var options = _.extend({ model: child }, childViewOptions);
3420
    return new ChildViewClass(options);
3421
  },
3422
  _setupChildView: function _setupChildView(view) {
3423
    monitorViewEvents(view);
3424
3425
    // We need to listen for if a view is destroyed in a way other
3426
    // than through the CollectionView.
3427
    // If this happens we need to remove the reference to the view
3428
    // since once a view has been destroyed we can not reuse it.
3429
    view.on('destroy', this.removeChildView, this);
3430
3431
    // set up the child view event forwarding
3432
    this._proxyChildViewEvents(view);
3433
  },
3434
3435
3436
  // used by ViewMixin's `_childViewEventHandler`
3437
  _getImmediateChildren: function _getImmediateChildren() {
3438
    return this.children._views;
3439
  },
3440
3441
3442
  // Overriding Backbone.View's `setElement` to handle
3443
  // if an el was previously defined. If so, the view might be
3444
  // attached on setElement.
3445
  setElement: function setElement() {
3446
    var hasEl = !!this.el;
3447
3448
    Backbone.View.prototype.setElement.apply(this, arguments);
3449
3450
    if (hasEl) {
3451
      this._isAttached = isNodeAttached(this.el);
3452
    }
3453
3454
    return this;
3455
  },
3456
3457
3458
  // Render children views.
3459
  render: function render() {
3460
    if (this._isDestroyed) {
3461
      return this;
3462
    }
3463
    this.triggerMethod('before:render', this);
3464
3465
    this._destroyChildren();
3466
3467
    // After all children have been destroyed re-init the container
3468
    this.children._init();
3469
3470
    if (this.collection) {
3471
      this._addChildModels(this.collection.models);
3472
    }
3473
3474
    this._showChildren();
3475
3476
    this._isRendered = true;
3477
3478
    this.triggerMethod('render', this);
3479
    return this;
3480
  },
3481
3482
3483
  // Sorts the children then filters and renders the results.
3484
  sort: function sort() {
3485
    if (this._isDestroyed) {
3486
      return this;
3487
    }
3488
3489
    if (!this.children.length) {
3490
      return this;
3491
    }
3492
3493
    this._showChildren();
3494
3495
    return this;
3496
  },
3497
  _showChildren: function _showChildren() {
3498
    if (this.isEmpty()) {
3499
      this._showEmptyView();
3500
      return;
3501
    }
3502
3503
    this._sortChildren();
3504
3505
    this.filter();
3506
  },
3507
3508
3509
  // Returns true if the collectionView is considered empty.
3510
  // This is called twice during a render. Once to check the data,
3511
  // and again when views are filtered. Override this function to
3512
  // customize what empty means.
3513
  isEmpty: function isEmpty(allViewsFiltered) {
3514
    return allViewsFiltered || !this.children.length;
3515
  },
3516
  _showEmptyView: function _showEmptyView() {
3517
    var EmptyView = this._getEmptyView();
3518
3519
    if (!EmptyView) {
3520
      return;
3521
    }
3522
3523
    var options = this._getEmptyViewOptions();
3524
3525
    this.emptyRegion.show(new EmptyView(options));
3526
  },
3527
3528
3529
  // Retrieve the empty view class
3530
  _getEmptyView: function _getEmptyView() {
3531
    var emptyView = this.emptyView;
3532
3533
    if (!emptyView) {
3534
      return;
3535
    }
3536
3537
    return this._getView(emptyView);
3538
  },
3539
3540
3541
  // Remove the emptyView
3542
  _destroyEmptyView: function _destroyEmptyView() {
3543
3544
    // Only empty if a view is show so the region
3545
    // doesn't detach any other unrelated HTML
3546
    if (this.emptyRegion.hasView()) {
3547
      this.emptyRegion.empty();
3548
    }
3549
  },
3550
3551
3552
  //
3553
  _getEmptyViewOptions: function _getEmptyViewOptions() {
3554
    var emptyViewOptions = this.emptyViewOptions || this.childViewOptions;
3555
3556
    if (_.isFunction(emptyViewOptions)) {
3557
      return emptyViewOptions.call(this);
3558
    }
3559
3560
    return emptyViewOptions;
3561
  },
3562
3563
3564
  // Sorts views by viewComparator and sets the children to the new order
3565
  _sortChildren: function _sortChildren() {
3566
    this.triggerMethod('before:sort', this);
3567
3568
    var viewComparator = this.getComparator();
3569
3570
    if (_.isFunction(viewComparator)) {
3571
      // Must use native bind to preserve length
3572
      viewComparator = viewComparator.bind(this);
3573
    }
3574
3575
    this.children._sort(viewComparator);
3576
3577
    this.triggerMethod('sort', this);
3578
  },
3579
3580
3581
  // Sets the view's `viewComparator` and applies the sort if the view is ready.
3582
  // To prevent the render pass `{ preventRender: true }` as the 2nd argument.
3583
  setComparator: function setComparator(comparator) {
3584
    var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
3585
        preventRender = _ref.preventRender;
3586
3587
    var comparatorChanged = this.viewComparator !== comparator;
3588
    var shouldSort = comparatorChanged && !preventRender;
3589
3590
    this.viewComparator = comparator;
3591
3592
    if (shouldSort) {
3593
      this.sort();
3594
    }
3595
3596
    return this;
3597
  },
3598
3599
3600
  // Clears the `viewComparator` and follows the same rules for rendering as `setComparator`.
3601
  removeComparator: function removeComparator(options) {
3602
    return this.setComparator(null, options);
3603
  },
3604
3605
3606
  // If viewComparator is overriden it will be returned here.
3607
  // Additionally override this function to provide custom
3608
  // viewComparator logic
3609
  getComparator: function getComparator() {
3610
    return this.viewComparator || this._viewComparator;
3611
  },
3612
3613
3614
  // Default internal view comparator that order the views by
3615
  // the order of the collection
3616
  _viewComparator: function _viewComparator(view) {
3617
    if (!this.collection) {
3618
      return;
3619
    }
3620
    return this.collection.indexOf(view.model);
3621
  },
3622
3623
3624
  // This method re-filters the children views and re-renders the results
3625
  filter: function filter() {
3626
    if (this._isDestroyed) {
3627
      return this;
3628
    }
3629
3630
    if (!this.children.length) {
3631
      return this;
3632
    }
3633
3634
    var filteredViews = this._filterChildren();
3635
3636
    this._renderChildren(filteredViews);
3637
3638
    return this;
3639
  },
3640
  _filterChildren: function _filterChildren() {
3641
    var viewFilter = this._getFilter();
3642
3643
    if (!viewFilter) {
3644
      return this.children._views;
3645
    }
3646
3647
    this.triggerMethod('before:filter', this);
3648
3649
    var filteredViews = this.children.partition(_.bind(viewFilter, this));
3650
3651
    this._detachChildren(filteredViews[1]);
3652
3653
    this.triggerMethod('filter', this);
3654
3655
    return filteredViews[0];
3656
  },
3657
3658
3659
  // This method returns a function for the viewFilter
3660
  _getFilter: function _getFilter() {
3661
    var viewFilter = this.getFilter();
3662
3663
    if (!viewFilter) {
3664
      return false;
3665
    }
3666
3667
    if (_.isFunction(viewFilter)) {
3668
      return viewFilter;
3669
    }
3670
3671
    // Support filter predicates `{ fooFlag: true }`
3672
    if (_.isObject(viewFilter)) {
3673
      var matcher = _.matches(viewFilter);
3674
      return function (view) {
3675
        return matcher(view.model && view.model.attributes);
3676
      };
3677
    }
3678
3679
    // Filter by model attribute
3680
    if (_.isString(viewFilter)) {
3681
      return function (view) {
3682
        return view.model && view.model.get(viewFilter);
3683
      };
3684
    }
3685
3686
    throw new MarionetteError({
3687
      name: 'InvalidViewFilterError',
3688
      message: '"viewFilter" must be a function, predicate object literal, a string indicating a model attribute, or falsy'
3689
    });
3690
  },
3691
3692
3693
  // Override this function to provide custom
3694
  // viewFilter logic
3695
  getFilter: function getFilter() {
3696
    return this.viewFilter;
3697
  },
3698
3699
3700
  // Sets the view's `viewFilter` and applies the filter if the view is ready.
3701
  // To prevent the render pass `{ preventRender: true }` as the 2nd argument.
3702
  setFilter: function setFilter(filter) {
3703
    var _ref2 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
3704
        preventRender = _ref2.preventRender;
3705
3706
    var filterChanged = this.viewFilter !== filter;
3707
    var shouldRender = filterChanged && !preventRender;
3708
3709
    this.viewFilter = filter;
3710
3711
    if (shouldRender) {
3712
      this.filter();
3713
    }
3714
3715
    return this;
3716
  },
3717
3718
3719
  // Clears the `viewFilter` and follows the same rules for rendering as `setFilter`.
3720
  removeFilter: function removeFilter(options) {
3721
    return this.setFilter(null, options);
3722
  },
3723
  _detachChildren: function _detachChildren(detachingViews) {
3724
    _.each(detachingViews, _.bind(this._detachChildView, this));
3725
  },
3726
  _detachChildView: function _detachChildView(view) {
3727
    var shouldTriggerDetach = !!view._isAttached;
3728
    if (shouldTriggerDetach) {
3729
      triggerMethodOn(view, 'before:detach', view);
3730
    }
3731
3732
    this.detachHtml(view);
3733
3734
    if (shouldTriggerDetach) {
3735
      view._isAttached = false;
3736
      triggerMethodOn(view, 'detach', view);
3737
    }
3738
  },
3739
3740
3741
  // Override this method to change how the collectionView detaches a child view
3742
  detachHtml: function detachHtml(view) {
3743
    this.detachEl(view.el);
3744
  },
3745
  _renderChildren: function _renderChildren(views) {
3746
    if (this.isEmpty(!views.length)) {
3747
      this._showEmptyView();
3748
      return;
3749
    }
3750
3751
    this._destroyEmptyView();
3752
3753
    this.triggerMethod('before:render:children', this, views);
3754
3755
    var els = this._getBuffer(views);
3756
3757
    this._attachChildren(els, views);
3758
3759
    this.triggerMethod('render:children', this, views);
3760
  },
3761
  _attachChildren: function _attachChildren(els, views) {
3762
    var shouldTriggerAttach = !!this._isAttached;
3763
3764
    views = shouldTriggerAttach ? views : [];
3765
3766
    _.each(views, function (view) {
3767
      if (view._isAttached) {
3768
        return;
3769
      }
3770
      triggerMethodOn(view, 'before:attach', view);
3771
    });
3772
3773
    this.attachHtml(this, els);
3774
3775
    _.each(views, function (view) {
3776
      if (view._isAttached) {
3777
        return;
3778
      }
3779
      view._isAttached = true;
3780
      triggerMethodOn(view, 'attach', view);
3781
    });
3782
  },
3783
3784
3785
  // Renders each view in children and creates a fragment buffer from them
3786
  _getBuffer: function _getBuffer(views) {
3787
    var _this2 = this;
3788
3789
    var elBuffer = this.createBuffer();
3790
3791
    _.each(views, function (view) {
3792
      _this2._renderChildView(view);
3793
      _this2.appendChildren(elBuffer, view.el);
3794
    });
3795
3796
    return elBuffer;
3797
  },
3798
  _renderChildView: function _renderChildView(view) {
3799
    if (view._isRendered) {
3800
      return;
3801
    }
3802
3803
    if (!view.supportsRenderLifecycle) {
3804
      triggerMethodOn(view, 'before:render', view);
3805
    }
3806
3807
    view.render();
3808
3809
    if (!view.supportsRenderLifecycle) {
3810
      view._isRendered = true;
3811
      triggerMethodOn(view, 'render', view);
3812
    }
3813
  },
3814
3815
3816
  // Override this method to do something other than `.append`.
3817
  // You can attach any HTML at this point including the els.
3818
  attachHtml: function attachHtml(collectionView, els) {
3819
    this.appendChildren(collectionView.el, els);
3820
  },
3821
3822
3823
  // Render the child's view and add it to the HTML for the collection view at a given index, based on the current sort
3824
  addChildView: function addChildView(view, index) {
3825
    if (!view || view._isDestroyed) {
3826
      return view;
3827
    }
3828
3829
    this._addChild(view, index);
3830
    this._showChildren();
3831
3832
    return view;
3833
  },
3834
3835
3836
  // Detach a view from the children.  Best used when adding a
3837
  // childView from `addChildView`
3838
  detachChildView: function detachChildView(view) {
3839
    this.removeChildView(view, { shouldDetach: true });
3840
3841
    return view;
3842
  },
3843
3844
3845
  // Remove the child view and destroy it.  Best used when adding a
3846
  // childView from `addChildView`
3847
  // The options argument is for internal use only
3848
  removeChildView: function removeChildView(view, options) {
3849
    if (!view) {
3850
      return view;
3851
    }
3852
3853
    this._removeChildView(view, options);
3854
3855
    this._removeChild(view);
3856
3857
    if (this.isEmpty()) {
3858
      this._showEmptyView();
3859
    }
3860
3861
    return view;
3862
  },
3863
  _removeChildViews: function _removeChildViews(views) {
3864
    _.each(views, _.bind(this._removeChildView, this));
3865
  },
3866
  _removeChildView: function _removeChildView(view) {
3867
    var _ref3 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
3868
        shouldDetach = _ref3.shouldDetach;
3869
3870
    view.off('destroy', this.removeChildView, this);
3871
3872
    if (shouldDetach) {
3873
      this._detachChildView(view);
3874
    } else {
3875
      this._destroyChildView(view);
3876
    }
3877
3878
    this.stopListening(view);
3879
  },
3880
  _destroyChildView: function _destroyChildView(view) {
3881
    if (view._isDestroyed) {
3882
      return;
3883
    }
3884
3885
    if (view.destroy) {
3886
      view.destroy();
3887
    } else {
3888
      destroyBackboneView(view);
3889
    }
3890
  },
3891
3892
3893
  // called by ViewMixin destroy
3894
  _removeChildren: function _removeChildren() {
3895
    this._destroyChildren();
3896
    this.emptyRegion.destroy();
3897
  },
3898
3899
3900
  // Destroy the child views that this collection view is holding on to, if any
3901
  _destroyChildren: function _destroyChildren() {
3902
    if (!this.children || !this.children.length) {
3903
      return;
3904
    }
3905
3906
    this.triggerMethod('before:destroy:children', this);
3907
    this.children.each(_.bind(this._removeChildView, this));
3908
    this.triggerMethod('destroy:children', this);
3909
  }
3910
});
3911
3912
_.extend(CollectionView$2.prototype, ViewMixin);
3913
3914
// Composite View
3915
// --------------
3916
3917
var ClassOptions$5 = ['childViewContainer', 'template', 'templateContext'];
3918
3919
// Used for rendering a branch-leaf, hierarchical structure.
3920
// Extends directly from CollectionView
3921
// @deprecated
3922
var CompositeView = CollectionView.extend({
3923
3924
  // Setting up the inheritance chain which allows changes to
3925
  // Marionette.CollectionView.prototype.constructor which allows overriding
3926
  // option to pass '{sort: false}' to prevent the CompositeView from
3927
  // maintaining the sorted order of the collection.
3928
  // This will fallback onto appending childView's to the end.
3929
  constructor: function constructor(options) {
3930
    deprecate('CompositeView is deprecated. Convert to View at your earliest convenience');
3931
3932
    this.mergeOptions(options, ClassOptions$5);
3933
3934
    CollectionView.prototype.constructor.apply(this, arguments);
3935
  },
3936
3937
3938
  // Configured the initial events that the composite view
3939
  // binds to. Override this method to prevent the initial
3940
  // events, or to add your own initial events.
3941
  _initialEvents: function _initialEvents() {
3942
3943
    // Bind only after composite view is rendered to avoid adding child views
3944
    // to nonexistent childViewContainer
3945
3946
    if (this.collection) {
3947
      this.listenTo(this.collection, 'add', this._onCollectionAdd);
3948
      this.listenTo(this.collection, 'update', this._onCollectionUpdate);
3949
      this.listenTo(this.collection, 'reset', this.renderChildren);
3950
3951
      if (this.sort) {
3952
        this.listenTo(this.collection, 'sort', this._sortViews);
3953
      }
3954
    }
3955
  },
3956
3957
3958
  // Retrieve the `childView` to be used when rendering each of
3959
  // the items in the collection. The default is to return
3960
  // `this.childView` or Marionette.CompositeView if no `childView`
3961
  // has been defined. As happens in CollectionView, `childView` can
3962
  // be a function (which should return a view class).
3963
  _getChildView: function _getChildView(child) {
3964
    var childView = this.childView;
3965
3966
    // for CompositeView, if `childView` is not specified, we'll get the same
3967
    // composite view class rendered for each child in the collection
3968
    // then check if the `childView` is a view class (the common case)
3969
    // finally check if it's a function (which we assume that returns a view class)
3970
    if (!childView) {
3971
      return this.constructor;
3972
    }
3973
3974
    childView = this._getView(childView, child);
3975
3976
    if (!childView) {
3977
      throw new MarionetteError({
3978
        name: 'InvalidChildViewError',
3979
        message: '"childView" must be a view class or a function that returns a view class'
3980
      });
3981
    }
3982
3983
    return childView;
3984
  },
3985
3986
3987
  // Return the serialized model
3988
  serializeData: function serializeData() {
3989
    return this.serializeModel();
3990
  },
3991
3992
3993
  // Renders the model and the collection.
3994
  render: function render() {
3995
    if (this._isDestroyed) {
3996
      return this;
3997
    }
3998
    this._isRendering = true;
3999
    this.resetChildViewContainer();
4000
4001
    this.triggerMethod('before:render', this);
4002
4003
    this._renderTemplate();
4004
    this.bindUIElements();
4005
    this.renderChildren();
4006
4007
    this._isRendering = false;
4008
    this._isRendered = true;
4009
    this.triggerMethod('render', this);
4010
    return this;
4011
  },
4012
  renderChildren: function renderChildren() {
4013
    if (this._isRendered || this._isRendering) {
4014
      CollectionView.prototype._renderChildren.call(this);
4015
    }
4016
  },
4017
4018
4019
  // You might need to override this if you've overridden attachHtml
4020
  attachBuffer: function attachBuffer(compositeView, buffer) {
4021
    var $container = this.getChildViewContainer(compositeView);
4022
    this.appendChildren($container, buffer);
4023
  },
4024
4025
4026
  // Internal method. Append a view to the end of the $el.
4027
  // Overidden from CollectionView to ensure view is appended to
4028
  // childViewContainer
4029
  _insertAfter: function _insertAfter(childView) {
4030
    var $container = this.getChildViewContainer(this, childView);
4031
    this.appendChildren($container, childView.el);
4032
  },
4033
4034
4035
  // Internal method. Append reordered childView'.
4036
  // Overidden from CollectionView to ensure reordered views
4037
  // are appended to childViewContainer
4038
  _appendReorderedChildren: function _appendReorderedChildren(children) {
4039
    var $container = this.getChildViewContainer(this);
4040
    this.appendChildren($container, children);
4041
  },
4042
4043
4044
  // Internal method to ensure an `$childViewContainer` exists, for the
4045
  // `attachHtml` method to use.
4046
  getChildViewContainer: function getChildViewContainer(containerView, childView) {
0 ignored issues
show
Unused Code introduced by
The parameter childView is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
4047
    if (!!containerView.$childViewContainer) {
4048
      return containerView.$childViewContainer;
4049
    }
4050
4051
    var container = void 0;
0 ignored issues
show
Coding Style introduced by
Consider using undefined instead of void(0). It is equivalent and more straightforward to read.
Loading history...
Unused Code introduced by
The assignment to variable container seems to be never used. Consider removing it.
Loading history...
4052
    var childViewContainer = containerView.childViewContainer;
4053
    if (childViewContainer) {
4054
4055
      var selector = _.result(containerView, 'childViewContainer');
4056
4057
      if (selector.charAt(0) === '@' && containerView.ui) {
4058
        container = containerView.ui[selector.substr(4)];
4059
      } else {
4060
        container = this.findEls(selector, containerView.$el);
4061
      }
4062
4063
      if (container.length <= 0) {
4064
        throw new MarionetteError({
4065
          name: 'ChildViewContainerMissingError',
4066
          message: 'The specified "childViewContainer" was not found: ' + containerView.childViewContainer
4067
        });
4068
      }
4069
    } else {
4070
      container = containerView.$el;
4071
    }
4072
4073
    containerView.$childViewContainer = container;
4074
    return container;
4075
  },
4076
4077
4078
  // Internal method to reset the `$childViewContainer` on render
4079
  resetChildViewContainer: function resetChildViewContainer() {
4080
    if (this.$childViewContainer) {
4081
      this.$childViewContainer = undefined;
4082
    }
4083
  }
4084
});
4085
4086
// To prevent duplication but allow the best View organization
4087
// Certain View methods are mixed directly into the deprecated CompositeView
4088
var MixinFromView = _.pick(View.prototype, 'serializeModel', 'getTemplate', '_renderTemplate', '_renderHtml', 'mixinTemplateContext', 'attachElContent');
4089
_.extend(CompositeView.prototype, MixinFromView);
4090
4091
// Behavior
4092
// --------
4093
4094
// A Behavior is an isolated set of DOM /
4095
// user interactions that can be mixed into any View.
4096
// Behaviors allow you to blackbox View specific interactions
4097
// into portable logical chunks, keeping your views simple and your code DRY.
4098
4099
var ClassOptions$6 = ['collectionEvents', 'events', 'modelEvents', 'triggers', 'ui'];
4100
4101
var Behavior = MarionetteObject.extend({
4102
  cidPrefix: 'mnb',
4103
4104
  constructor: function constructor(options, view) {
4105
    // Setup reference to the view.
4106
    // this comes in handle when a behavior
4107
    // wants to directly talk up the chain
4108
    // to the view.
4109
    this.view = view;
4110
4111
    if (this.defaults) {
4112
      deprecate('Behavior defaults are deprecated. For similar functionality set options on the Behavior class.');
4113
    }
4114
4115
    this.defaults = _.clone(_.result(this, 'defaults', {}));
4116
4117
    this._setOptions(this.defaults, options);
4118
    this.mergeOptions(this.options, ClassOptions$6);
4119
4120
    // Construct an internal UI hash using
4121
    // the behaviors UI hash and then the view UI hash.
4122
    // This allows the user to use UI hash elements
4123
    // defined in the parent view as well as those
4124
    // defined in the given behavior.
4125
    // This order will help the reuse and share of a behavior
4126
    // between multiple views, while letting a view override a
4127
    // selector under an UI key.
4128
    this.ui = _.extend({}, _.result(this, 'ui'), _.result(view, 'ui'));
4129
4130
    MarionetteObject.apply(this, arguments);
4131
  },
4132
4133
4134
  // proxy behavior $ method to the view
4135
  // this is useful for doing jquery DOM lookups
4136
  // scoped to behaviors view.
4137
  $: function $() {
4138
    return this.view.$.apply(this.view, arguments);
4139
  },
4140
4141
4142
  // Stops the behavior from listening to events.
4143
  // Overrides Object#destroy to prevent additional events from being triggered.
4144
  destroy: function destroy() {
4145
    this.stopListening();
4146
4147
    this.view._removeBehavior(this);
4148
4149
    return this;
4150
  },
4151
  proxyViewProperties: function proxyViewProperties() {
4152
    this.$el = this.view.$el;
4153
    this.el = this.view.el;
4154
4155
    return this;
4156
  },
4157
  bindUIElements: function bindUIElements() {
4158
    this._bindUIElements();
4159
4160
    return this;
4161
  },
4162
  unbindUIElements: function unbindUIElements() {
4163
    this._unbindUIElements();
4164
4165
    return this;
4166
  },
4167
  getUI: function getUI(name) {
4168
    return this._getUI(name);
4169
  },
4170
4171
4172
  // Handle `modelEvents`, and `collectionEvents` configuration
4173
  delegateEntityEvents: function delegateEntityEvents() {
4174
    this._delegateEntityEvents(this.view.model, this.view.collection);
4175
4176
    return this;
4177
  },
4178
  undelegateEntityEvents: function undelegateEntityEvents() {
4179
    this._undelegateEntityEvents(this.view.model, this.view.collection);
4180
4181
    return this;
4182
  },
4183
  getEvents: function getEvents() {
4184
    var _this = this;
4185
4186
    // Normalize behavior events hash to allow
4187
    // a user to use the @ui. syntax.
4188
    var behaviorEvents = this.normalizeUIKeys(_.result(this, 'events'));
4189
4190
    // binds the handler to the behavior and builds a unique eventName
4191
    return _.reduce(behaviorEvents, function (events, behaviorHandler, key) {
4192
      if (!_.isFunction(behaviorHandler)) {
4193
        behaviorHandler = _this[behaviorHandler];
4194
      }
4195
      if (!behaviorHandler) {
4196
        return;
4197
      }
4198
      key = getUniqueEventName(key);
4199
      events[key] = _.bind(behaviorHandler, _this);
4200
      return events;
4201
    }, {});
4202
  },
4203
4204
4205
  // Internal method to build all trigger handlers for a given behavior
4206
  getTriggers: function getTriggers() {
4207
    if (!this.triggers) {
4208
      return;
4209
    }
4210
4211
    // Normalize behavior triggers hash to allow
4212
    // a user to use the @ui. syntax.
4213
    var behaviorTriggers = this.normalizeUIKeys(_.result(this, 'triggers'));
4214
4215
    return this._getViewTriggers(this.view, behaviorTriggers);
4216
  }
4217
});
4218
4219
_.extend(Behavior.prototype, DelegateEntityEventsMixin, TriggersMixin, UIMixin);
4220
4221
// Application
4222
// -----------
4223
var ClassOptions$7 = ['region', 'regionClass'];
4224
4225
// A container for a Marionette application.
4226
var Application = MarionetteObject.extend({
4227
  cidPrefix: 'mna',
4228
4229
  constructor: function constructor(options) {
4230
    this._setOptions(options);
4231
4232
    this.mergeOptions(options, ClassOptions$7);
4233
4234
    this._initRegion();
4235
4236
    MarionetteObject.prototype.constructor.apply(this, arguments);
4237
  },
4238
4239
4240
  regionClass: Region,
4241
4242
  _initRegion: function _initRegion() {
4243
    var region = this.region;
4244
4245
    if (!region) {
4246
      return;
4247
    }
4248
4249
    var defaults = {
4250
      regionClass: this.regionClass
4251
    };
4252
4253
    this._region = buildRegion(region, defaults);
4254
  },
4255
  getRegion: function getRegion() {
4256
    return this._region;
4257
  },
4258
  showView: function showView(view) {
4259
    var region = this.getRegion();
4260
4261
    for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
4262
      args[_key - 1] = arguments[_key];
4263
    }
4264
4265
    return region.show.apply(region, [view].concat(args));
4266
  },
4267
  getView: function getView() {
4268
    return this.getRegion().currentView;
4269
  },
4270
4271
4272
  // kick off all of the application's processes.
4273
  start: function start(options) {
4274
    this.triggerMethod('before:start', this, options);
4275
    this.triggerMethod('start', this, options);
4276
    return this;
4277
  }
4278
});
4279
4280
// App Router
4281
// ----------
4282
4283
// Reduce the boilerplate code of handling route events
4284
// and then calling a single method on another object,
4285
// called a controller.
4286
// Have your routers configured to call the method on
4287
// your controller, directly.
4288
//
4289
// Configure an AppRouter with `appRoutes`.
4290
//
4291
// App routers can only take one `controller` object.
4292
// It is recommended that you divide your controller
4293
// objects in to smaller pieces of related functionality
4294
// and have multiple routers / controllers, instead of
4295
// just one giant router and controller.
4296
//
4297
// You can also add standard routes to an AppRouter.
4298
4299
var ClassOptions$8 = ['appRoutes', 'controller'];
4300
4301
var AppRouter = Backbone.Router.extend({
4302
  constructor: function constructor(options) {
4303
    this._setOptions(options);
4304
4305
    this.mergeOptions(options, ClassOptions$8);
4306
4307
    Backbone.Router.apply(this, arguments);
4308
4309
    var appRoutes = this.appRoutes;
4310
    var controller = this._getController();
4311
    this.processAppRoutes(controller, appRoutes);
4312
    this.on('route', this._processOnRoute, this);
4313
  },
4314
4315
4316
  // Similar to route method on a Backbone Router but
4317
  // method is called on the controller
4318
  appRoute: function appRoute(route, methodName) {
4319
    var controller = this._getController();
4320
    this._addAppRoute(controller, route, methodName);
4321
    return this;
4322
  },
4323
4324
4325
  // process the route event and trigger the onRoute
4326
  // method call, if it exists
4327
  _processOnRoute: function _processOnRoute(routeName, routeArgs) {
4328
    // make sure an onRoute before trying to call it
4329
    if (_.isFunction(this.onRoute)) {
4330
      // find the path that matches the current route
4331
      var routePath = _.invert(this.appRoutes)[routeName];
4332
      this.onRoute(routeName, routePath, routeArgs);
4333
    }
4334
  },
4335
4336
4337
  // Internal method to process the `appRoutes` for the
4338
  // router, and turn them in to routes that trigger the
4339
  // specified method on the specified `controller`.
4340
  processAppRoutes: function processAppRoutes(controller, appRoutes) {
4341
    var _this = this;
4342
4343
    if (!appRoutes) {
4344
      return this;
4345
    }
4346
4347
    var routeNames = _.keys(appRoutes).reverse(); // Backbone requires reverted order of routes
4348
4349
    _.each(routeNames, function (route) {
4350
      _this._addAppRoute(controller, route, appRoutes[route]);
4351
    });
4352
4353
    return this;
4354
  },
4355
  _getController: function _getController() {
4356
    return this.controller;
4357
  },
4358
  _addAppRoute: function _addAppRoute(controller, route, methodName) {
4359
    var method = controller[methodName];
4360
4361
    if (!method) {
4362
      throw new MarionetteError('Method "' + methodName + '" was not found on the controller');
4363
    }
4364
4365
    this.route(route, methodName, _.bind(method, controller));
4366
  },
4367
4368
4369
  triggerMethod: triggerMethod$1
4370
});
4371
4372
_.extend(AppRouter.prototype, CommonMixin);
4373
4374
// Placeholder method to be extended by the user.
4375
// The method should define the object that stores the behaviors.
4376
// i.e.
4377
//
4378
// ```js
4379
// Marionette.Behaviors.behaviorsLookup: function() {
4380
//   return App.Behaviors
4381
// }
4382
// ```
4383
function behaviorsLookup() {
4384
  throw new MarionetteError({
4385
    message: 'You must define where your behaviors are stored.',
4386
    url: 'marionette.behaviors.md#behaviorslookup'
4387
  });
4388
}
4389
4390
var previousMarionette = Backbone.Marionette;
4391
var Marionette = Backbone.Marionette = {};
4392
4393
// This allows you to run multiple instances of Marionette on the same
4394
// webapp. After loading the new version, call `noConflict()` to
4395
// get a reference to it. At the same time the old version will be
4396
// returned to Backbone.Marionette.
4397
Marionette.noConflict = function () {
4398
  Backbone.Marionette = previousMarionette;
4399
  return this;
4400
};
4401
4402
// Utilities
4403
Marionette.bindEvents = proxy(bindEvents);
4404
Marionette.unbindEvents = proxy(unbindEvents);
4405
Marionette.bindRequests = proxy(bindRequests);
4406
Marionette.unbindRequests = proxy(unbindRequests);
4407
Marionette.mergeOptions = proxy(mergeOptions);
4408
Marionette.getOption = proxy(getOption);
4409
Marionette.normalizeMethods = proxy(normalizeMethods);
4410
Marionette.extend = extend;
4411
Marionette.isNodeAttached = isNodeAttached;
4412
Marionette.deprecate = deprecate;
4413
Marionette.triggerMethod = proxy(triggerMethod$1);
4414
Marionette.triggerMethodOn = triggerMethodOn;
4415
Marionette.isEnabled = isEnabled;
4416
Marionette.setEnabled = setEnabled;
4417
Marionette.monitorViewEvents = monitorViewEvents;
4418
4419
Marionette.Behaviors = {};
4420
Marionette.Behaviors.behaviorsLookup = behaviorsLookup;
4421
4422
// Classes
4423
Marionette.Application = Application;
4424
Marionette.AppRouter = AppRouter;
4425
Marionette.Renderer = Renderer;
4426
Marionette.TemplateCache = TemplateCache;
4427
Marionette.View = View;
4428
Marionette.CollectionView = CollectionView;
4429
Marionette.NextCollectionView = CollectionView$2;
4430
Marionette.CompositeView = CompositeView;
4431
Marionette.Behavior = Behavior;
4432
Marionette.Region = Region;
4433
Marionette.Error = MarionetteError;
4434
Marionette.Object = MarionetteObject;
4435
4436
// Configuration
4437
Marionette.DEV_MODE = false;
4438
Marionette.FEATURES = FEATURES;
4439
Marionette.VERSION = version;
4440
4441
return Marionette;
4442
4443
})));
4444
4445
//# sourceMappingURL=backbone.marionette.js.map
4446