marionette-rails 2.2.2 → 2.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5bb72c3e7ebd42ae9f6ff1cab755e4d75cbd33d7
4
- data.tar.gz: 93bb3dd197d7c37ffc2698eebf39c93476d84246
3
+ metadata.gz: e6785ca4329e92cfb675a34ca251fd978c57e1e3
4
+ data.tar.gz: 0627a47da1f8b06cd8bb2316026ffcaaafcee58a
5
5
  SHA512:
6
- metadata.gz: 770e3d2afb69960a1572be93848b96a4a06dbc84f89652f0b0c61c98807fe3ecec3f088124750953b81d3c9b8914d9577c85ac575d061935e0d0bec7f63c01a6
7
- data.tar.gz: aae08d6c39199c82b4ffb94c0c0b47a8b2b5b84d69b8c909a9ebc3815b35c89a710c851a009583cd0d61c2ada9a582d22ca161b9a5861da904d02912a9c05f6a
6
+ metadata.gz: 1a63dbf909602bd5a283f5a4d622a9bfac2afa98aa4a78061216bfd14db9bafb4bbd267c75f8b2d29143ec6c83f8cedcdf5ca0431f235862487f0e55fae20628
7
+ data.tar.gz: 9a0560029ac2986b4a181ddc08cdca6043dbfed7864b518f5b53aca3d75d506058895cd5e723a4a37319ef688bf40e0b6954dd80b43fa2712479328e8ac6f069
@@ -1,5 +1,5 @@
1
1
  module Marionette
2
2
  module Rails
3
- VERSION = '2.2.2'
3
+ VERSION = '2.3.2'
4
4
  end
5
5
  end
@@ -1,8 +1,8 @@
1
1
  // MarionetteJS (Backbone.Marionette)
2
2
  // ----------------------------------
3
- // v2.2.2
3
+ // v2.3.2
4
4
  //
5
- // Copyright (c)2014 Derick Bailey, Muted Solutions, LLC.
5
+ // Copyright (c)2015 Derick Bailey, Muted Solutions, LLC.
6
6
  // Distributed under MIT license
7
7
  //
8
8
  // http://marionettejs.com
@@ -22,14 +22,14 @@
22
22
  /* istanbul ignore next */
23
23
  if (typeof define === 'function' && define.amd) {
24
24
  define(['backbone', 'underscore'], function(Backbone, _) {
25
- return (root.Marionette = factory(root, Backbone, _));
25
+ return (root.Marionette = root.Mn = factory(root, Backbone, _));
26
26
  });
27
27
  } else if (typeof exports !== 'undefined') {
28
28
  var Backbone = require('backbone');
29
29
  var _ = require('underscore');
30
30
  module.exports = factory(root, Backbone, _);
31
31
  } else {
32
- root.Marionette = factory(root, root.Backbone, root._);
32
+ root.Marionette = root.Mn = factory(root, root.Backbone, root._);
33
33
  }
34
34
 
35
35
  }(this, function(root, Backbone, _) {
@@ -38,7 +38,7 @@
38
38
  /* istanbul ignore next */
39
39
  // Backbone.BabySitter
40
40
  // -------------------
41
- // v0.1.4
41
+ // v0.1.5
42
42
  //
43
43
  // Copyright (c)2014 Derick Bailey, Muted Solutions, LLC.
44
44
  // Distributed under MIT license
@@ -167,7 +167,7 @@
167
167
  // return the public API
168
168
  return Container;
169
169
  }(Backbone, _);
170
- Backbone.ChildViewContainer.VERSION = "0.1.4";
170
+ Backbone.ChildViewContainer.VERSION = "0.1.5";
171
171
  Backbone.ChildViewContainer.noConflict = function() {
172
172
  Backbone.ChildViewContainer = previousChildViewContainer;
173
173
  return this;
@@ -493,7 +493,7 @@
493
493
 
494
494
  var Marionette = Backbone.Marionette = {};
495
495
 
496
- Marionette.VERSION = '2.2.2';
496
+ Marionette.VERSION = '2.3.2';
497
497
 
498
498
  Marionette.noConflict = function() {
499
499
  root.Marionette = previousMarionette;
@@ -505,20 +505,26 @@
505
505
  // Get the Deferred creator for later use
506
506
  Marionette.Deferred = Backbone.$.Deferred;
507
507
 
508
- /* jshint unused: false */
508
+ /* jshint unused: false *//* global console */
509
509
 
510
510
  // Helpers
511
511
  // -------
512
512
 
513
- // For slicing `arguments` in functions
514
- var slice = Array.prototype.slice;
515
-
516
513
  // Marionette.extend
517
514
  // -----------------
518
515
 
519
516
  // Borrow the Backbone `extend` method so we can use it as needed
520
517
  Marionette.extend = Backbone.Model.extend;
521
518
 
519
+ // Marionette.isNodeAttached
520
+ // -------------------------
521
+
522
+ // Determine if `el` is a child of the document
523
+ Marionette.isNodeAttached = function(el) {
524
+ return Backbone.$.contains(document.documentElement, el);
525
+ };
526
+
527
+
522
528
  // Marionette.getOption
523
529
  // --------------------
524
530
 
@@ -526,15 +532,11 @@
526
532
  // object or its `options`, with `options` taking precedence.
527
533
  Marionette.getOption = function(target, optionName) {
528
534
  if (!target || !optionName) { return; }
529
- var value;
530
-
531
535
  if (target.options && (target.options[optionName] !== undefined)) {
532
- value = target.options[optionName];
536
+ return target.options[optionName];
533
537
  } else {
534
- value = target[optionName];
538
+ return target[optionName];
535
539
  }
536
-
537
- return value;
538
540
  };
539
541
 
540
542
  // Proxy `Marionette.getOption`
@@ -542,23 +544,36 @@
542
544
  return Marionette.getOption(this, optionName);
543
545
  };
544
546
 
547
+ // Similar to `_.result`, this is a simple helper
548
+ // If a function is provided we call it with context
549
+ // otherwise just return the value. If the value is
550
+ // undefined return a default value
551
+ Marionette._getValue = function(value, context, params) {
552
+ if (_.isFunction(value)) {
553
+ // We need to ensure that params is not undefined
554
+ // to prevent `apply` from failing in ie8
555
+ params = params || [];
556
+
557
+ value = value.apply(context, params);
558
+ }
559
+ return value;
560
+ };
561
+
545
562
  // Marionette.normalizeMethods
546
563
  // ----------------------
547
564
 
548
565
  // Pass in a mapping of events => functions or function names
549
566
  // and return a mapping of events => functions
550
567
  Marionette.normalizeMethods = function(hash) {
551
- var normalizedHash = {};
552
- _.each(hash, function(method, name) {
568
+ return _.reduce(hash, function(normalizedHash, method, name) {
553
569
  if (!_.isFunction(method)) {
554
570
  method = this[method];
555
571
  }
556
- if (!method) {
557
- return;
572
+ if (method) {
573
+ normalizedHash[name] = method;
558
574
  }
559
- normalizedHash[name] = method;
560
- }, this);
561
- return normalizedHash;
575
+ return normalizedHash;
576
+ }, {}, this);
562
577
  };
563
578
 
564
579
  // utility method for parsing @ui. syntax strings
@@ -574,37 +589,22 @@
574
589
  // swaps the @ui with the associated selector.
575
590
  // Returns a new, non-mutated, parsed events hash.
576
591
  Marionette.normalizeUIKeys = function(hash, ui) {
577
- if (typeof(hash) === 'undefined') {
578
- return;
579
- }
580
-
581
- hash = _.clone(hash);
582
-
583
- _.each(_.keys(hash), function(key) {
592
+ return _.reduce(hash, function(memo, val, key) {
584
593
  var normalizedKey = Marionette.normalizeUIString(key, ui);
585
- if (normalizedKey !== key) {
586
- hash[normalizedKey] = hash[key];
587
- delete hash[key];
588
- }
589
- });
590
-
591
- return hash;
594
+ memo[normalizedKey] = val;
595
+ return memo;
596
+ }, {});
592
597
  };
593
598
 
594
599
  // allows for the use of the @ui. syntax within
595
600
  // a given value for regions
596
601
  // swaps the @ui with the associated selector
597
602
  Marionette.normalizeUIValues = function(hash, ui) {
598
- if (typeof(hash) === 'undefined') {
599
- return;
600
- }
601
-
602
603
  _.each(hash, function(val, key) {
603
604
  if (_.isString(val)) {
604
605
  hash[key] = Marionette.normalizeUIString(val, ui);
605
606
  }
606
607
  });
607
-
608
608
  return hash;
609
609
  };
610
610
 
@@ -627,15 +627,31 @@
627
627
  });
628
628
  };
629
629
 
630
- // Trigger an event and/or a corresponding method name. Examples:
631
- //
632
- // `this.triggerMethod("foo")` will trigger the "foo" event and
633
- // call the "onFoo" method.
634
- //
635
- // `this.triggerMethod("foo:bar")` will trigger the "foo:bar" event and
636
- // call the "onFooBar" method.
637
- Marionette.triggerMethod = function(event) {
630
+ var deprecate = Marionette.deprecate = function(message, test) {
631
+ if (_.isObject(message)) {
632
+ message = (
633
+ message.prev + ' is going to be removed in the future. ' +
634
+ 'Please use ' + message.next + ' instead.' +
635
+ (message.url ? ' See: ' + message.url : '')
636
+ );
637
+ }
638
+
639
+ if ((test === undefined || !test) && !deprecate._cache[message]) {
640
+ deprecate._warn('Deprecation warning: ' + message);
641
+ deprecate._cache[message] = true;
642
+ }
643
+ };
644
+
645
+ deprecate._warn = typeof console !== 'undefined' && (console.warn || console.log) || function() {};
646
+ deprecate._cache = {};
638
647
 
648
+ /* jshint maxstatements: 14, maxcomplexity: 7 */
649
+
650
+ // Trigger Method
651
+ // --------------
652
+
653
+
654
+ Marionette._triggerMethod = (function() {
639
655
  // split the event name on the ":"
640
656
  var splitter = /(^|:)(\w)/gi;
641
657
 
@@ -645,102 +661,109 @@
645
661
  return eventName.toUpperCase();
646
662
  }
647
663
 
648
- // get the method name from the event name
649
- var methodName = 'on' + event.replace(splitter, getEventName);
650
- var method = this[methodName];
651
- var result;
664
+ return function(context, event, args) {
665
+ var noEventArg = arguments.length < 3;
666
+ if (noEventArg) {
667
+ args = event;
668
+ event = args[0];
669
+ }
652
670
 
653
- // call the onMethodName if it exists
654
- if (_.isFunction(method)) {
655
- // pass all arguments, except the event name
656
- result = method.apply(this, _.tail(arguments));
657
- }
671
+ // get the method name from the event name
672
+ var methodName = 'on' + event.replace(splitter, getEventName);
673
+ var method = context[methodName];
674
+ var result;
658
675
 
659
- // trigger the event, if a trigger method exists
660
- if (_.isFunction(this.trigger)) {
661
- this.trigger.apply(this, arguments);
662
- }
676
+ // call the onMethodName if it exists
677
+ if (_.isFunction(method)) {
678
+ // pass all args, except the event name
679
+ result = method.apply(context, noEventArg ? _.rest(args) : args);
680
+ }
681
+
682
+ // trigger the event, if a trigger method exists
683
+ if (_.isFunction(context.trigger)) {
684
+ if (noEventArg + args.length > 1) {
685
+ context.trigger.apply(context, noEventArg ? args : [event].concat(_.rest(args, 0)));
686
+ } else {
687
+ context.trigger(event);
688
+ }
689
+ }
690
+
691
+ return result;
692
+ };
693
+ })();
663
694
 
664
- return result;
695
+ // Trigger an event and/or a corresponding method name. Examples:
696
+ //
697
+ // `this.triggerMethod("foo")` will trigger the "foo" event and
698
+ // call the "onFoo" method.
699
+ //
700
+ // `this.triggerMethod("foo:bar")` will trigger the "foo:bar" event and
701
+ // call the "onFooBar" method.
702
+ Marionette.triggerMethod = function(event) {
703
+ return Marionette._triggerMethod(this, arguments);
665
704
  };
666
705
 
667
706
  // triggerMethodOn invokes triggerMethod on a specific context
668
707
  //
669
708
  // e.g. `Marionette.triggerMethodOn(view, 'show')`
670
709
  // will trigger a "show" event or invoke onShow the view.
671
- Marionette.triggerMethodOn = function(context, event) {
672
- var args = _.tail(arguments, 2);
673
- var fnc;
674
-
675
- if (_.isFunction(context.triggerMethod)) {
676
- fnc = context.triggerMethod;
677
- } else {
678
- fnc = Marionette.triggerMethod;
679
- }
710
+ Marionette.triggerMethodOn = function(context) {
711
+ var fnc = _.isFunction(context.triggerMethod) ?
712
+ context.triggerMethod :
713
+ Marionette.triggerMethod;
680
714
 
681
- return fnc.apply(context, [event].concat(args));
715
+ return fnc.apply(context, _.rest(arguments));
682
716
  };
683
717
 
684
- // DOMRefresh
685
- // ----------
686
- //
718
+ // DOM Refresh
719
+ // -----------
720
+
687
721
  // Monitor a view's state, and after it has been rendered and shown
688
722
  // in the DOM, trigger a "dom:refresh" event every time it is
689
723
  // re-rendered.
690
724
 
691
- Marionette.MonitorDOMRefresh = (function(documentElement) {
725
+ Marionette.MonitorDOMRefresh = function(view) {
726
+
692
727
  // track when the view has been shown in the DOM,
693
728
  // using a Marionette.Region (or by other means of triggering "show")
694
- function handleShow(view) {
729
+ function handleShow() {
695
730
  view._isShown = true;
696
- triggerDOMRefresh(view);
731
+ triggerDOMRefresh();
697
732
  }
698
733
 
699
734
  // track when the view has been rendered
700
- function handleRender(view) {
735
+ function handleRender() {
701
736
  view._isRendered = true;
702
- triggerDOMRefresh(view);
737
+ triggerDOMRefresh();
703
738
  }
704
739
 
705
740
  // Trigger the "dom:refresh" event and corresponding "onDomRefresh" method
706
- function triggerDOMRefresh(view) {
707
- if (view._isShown && view._isRendered && isInDOM(view)) {
741
+ function triggerDOMRefresh() {
742
+ if (view._isShown && view._isRendered && Marionette.isNodeAttached(view.el)) {
708
743
  if (_.isFunction(view.triggerMethod)) {
709
744
  view.triggerMethod('dom:refresh');
710
745
  }
711
746
  }
712
747
  }
713
748
 
714
- function isInDOM(view) {
715
- return Backbone.$.contains(documentElement, view.el);
716
- }
717
-
718
- // Export public API
719
- return function(view) {
720
- view.listenTo(view, 'show', function() {
721
- handleShow(view);
722
- });
723
-
724
- view.listenTo(view, 'render', function() {
725
- handleRender(view);
726
- });
727
- };
728
- })(document.documentElement);
749
+ view.on({
750
+ show: handleShow,
751
+ render: handleRender
752
+ });
753
+ };
729
754
 
730
-
731
755
  /* jshint maxparams: 5 */
732
756
 
733
- // Marionette.bindEntityEvents & unbindEntityEvents
734
- // ---------------------------
757
+ // Bind Entity Events & Unbind Entity Events
758
+ // -----------------------------------------
735
759
  //
736
- // These methods are used to bind/unbind a backbone "entity" (collection/model)
760
+ // These methods are used to bind/unbind a backbone "entity" (e.g. collection/model)
737
761
  // to methods on a target object.
738
762
  //
739
- // The first parameter, `target`, must have a `listenTo` method from the
740
- // EventBinder object.
763
+ // The first parameter, `target`, must have the Backbone.Events module mixed in.
741
764
  //
742
- // The second parameter is the entity (Backbone.Model or Backbone.Collection)
743
- // to bind the events from.
765
+ // The second parameter is the `entity` (Backbone.Model, Backbone.Collection or
766
+ // any object that has Backbone.Events mixed in) to bind the events from.
744
767
  //
745
768
  // The third parameter is a hash of { "event:name": "eventHandler" }
746
769
  // configuration. Multiple handlers can be separated by a space. A
@@ -793,7 +816,7 @@
793
816
  if (!entity || !bindings) { return; }
794
817
 
795
818
  // type-check bindings
796
- if (!_.isFunction(bindings) && !_.isObject(bindings)) {
819
+ if (!_.isObject(bindings)) {
797
820
  throw new Marionette.Error({
798
821
  message: 'Bindings must be an object or function.',
799
822
  url: 'marionette.functions.html#marionettebindentityevents'
@@ -801,9 +824,7 @@
801
824
  }
802
825
 
803
826
  // allow the bindings to be a function
804
- if (_.isFunction(bindings)) {
805
- bindings = bindings.call(target);
806
- }
827
+ bindings = Marionette._getValue(bindings, target);
807
828
 
808
829
  // iterate the bindings and bind them
809
830
  _.each(bindings, function(methods, evt) {
@@ -840,6 +861,9 @@
840
861
  })(Marionette);
841
862
 
842
863
 
864
+ // Error
865
+ // -----
866
+
843
867
  var errorProps = ['description', 'fileName', 'lineNumber', 'name', 'message', 'number'];
844
868
 
845
869
  Marionette.Error = Marionette.extend.call(Error, {
@@ -926,9 +950,9 @@
926
950
  }
927
951
  });
928
952
 
929
- // Marionette Controller
930
- // ---------------------
931
- //
953
+ // Controller
954
+ // ----------
955
+
932
956
  // A multi-purpose object to use as a controller for
933
957
  // modules and routers, and as a mediator for workflow
934
958
  // and coordination of other objects, views, and more.
@@ -948,9 +972,8 @@
948
972
  // Ensure it can trigger events with Backbone.Events
949
973
  _.extend(Marionette.Controller.prototype, Backbone.Events, {
950
974
  destroy: function() {
951
- var args = slice.call(arguments);
952
- this.triggerMethod.apply(this, ['before:destroy'].concat(args));
953
- this.triggerMethod.apply(this, ['destroy'].concat(args));
975
+ Marionette._triggerMethod(this, 'before:destroy', arguments);
976
+ Marionette._triggerMethod(this, 'destroy', arguments);
954
977
 
955
978
  this.stopListening();
956
979
  this.off();
@@ -966,9 +989,9 @@
966
989
 
967
990
  });
968
991
 
969
- // Marionette Object
970
- // ---------------------
971
- //
992
+ // Object
993
+ // ------
994
+
972
995
  // A Base Class that other Classes should descend from.
973
996
  // Object borrows many conventions and utilities from Backbone.
974
997
  Marionette.Object = function(options) {
@@ -982,7 +1005,8 @@
982
1005
  // Object Methods
983
1006
  // --------------
984
1007
 
985
- _.extend(Marionette.Object.prototype, {
1008
+ // Ensure it can trigger events with Backbone.Events
1009
+ _.extend(Marionette.Object.prototype, Backbone.Events, {
986
1010
 
987
1011
  //this is a noop method intended to be overridden by classes that extend from this base
988
1012
  initialize: function() {},
@@ -1000,138 +1024,43 @@
1000
1024
  // Proxy `getOption` to enable getting options from this or this.options by name.
1001
1025
  getOption: Marionette.proxyGetOption,
1002
1026
 
1003
- // Proxy `unbindEntityEvents` to enable binding view's events from another entity.
1027
+ // Proxy `bindEntityEvents` to enable binding view's events from another entity.
1004
1028
  bindEntityEvents: Marionette.proxyBindEntityEvents,
1005
1029
 
1006
1030
  // Proxy `unbindEntityEvents` to enable unbinding view's events from another entity.
1007
1031
  unbindEntityEvents: Marionette.proxyUnbindEntityEvents
1008
1032
  });
1009
1033
 
1010
- // Ensure it can trigger events with Backbone.Events
1011
- _.extend(Marionette.Object.prototype, Backbone.Events);
1012
-
1013
- /* jshint maxcomplexity: 10, maxstatements: 29 */
1034
+ /* jshint maxcomplexity: 16, maxstatements: 45, maxlen: 120 */
1014
1035
 
1015
1036
  // Region
1016
1037
  // ------
1017
- //
1038
+
1018
1039
  // Manage the visual regions of your composite application. See
1019
1040
  // http://lostechies.com/derickbailey/2011/12/12/composite-js-apps-regions-and-region-managers/
1020
1041
 
1021
- Marionette.Region = function(options) {
1022
- this.options = options || {};
1023
- this.el = this.getOption('el');
1024
-
1025
- // Handle when this.el is passed in as a $ wrapped element.
1026
- this.el = this.el instanceof Backbone.$ ? this.el[0] : this.el;
1027
-
1028
- if (!this.el) {
1029
- throw new Marionette.Error({
1030
- name: 'NoElError',
1031
- message: 'An "el" must be specified for a region.'
1032
- });
1033
- }
1034
-
1035
- this.$el = this.getEl(this.el);
1036
-
1037
- if (this.initialize) {
1038
- var args = slice.apply(arguments);
1039
- this.initialize.apply(this, args);
1040
- }
1041
- };
1042
-
1043
-
1044
- // Region Class methods
1045
- // -------------------
1046
-
1047
- _.extend(Marionette.Region, {
1042
+ Marionette.Region = Marionette.Object.extend({
1043
+ constructor: function (options) {
1048
1044
 
1049
- // Build an instance of a region by passing in a configuration object
1050
- // and a default region class to use if none is specified in the config.
1051
- //
1052
- // The config object should either be a string as a jQuery DOM selector,
1053
- // a Region class directly, or an object literal that specifies both
1054
- // a selector and regionClass:
1055
- //
1056
- // ```js
1057
- // {
1058
- // selector: "#foo",
1059
- // regionClass: MyCustomRegion
1060
- // }
1061
- // ```
1062
- //
1063
- buildRegion: function(regionConfig, DefaultRegionClass) {
1064
- if (_.isString(regionConfig)) {
1065
- return this._buildRegionFromSelector(regionConfig, DefaultRegionClass);
1066
- }
1067
-
1068
- if (regionConfig.selector || regionConfig.el || regionConfig.regionClass) {
1069
- return this._buildRegionFromObject(regionConfig, DefaultRegionClass);
1070
- }
1071
-
1072
- if (_.isFunction(regionConfig)) {
1073
- return this._buildRegionFromRegionClass(regionConfig);
1074
- }
1075
-
1076
- throw new Marionette.Error({
1077
- message: 'Improper region configuration type.',
1078
- url: 'marionette.region.html#region-configuration-types'
1079
- });
1080
- },
1081
-
1082
- // Build the region from a string selector like '#foo-region'
1083
- _buildRegionFromSelector: function(selector, DefaultRegionClass) {
1084
- return new DefaultRegionClass({ el: selector });
1085
- },
1086
-
1087
- // Build the region from a configuration object
1088
- // ```js
1089
- // { selector: '#foo', regionClass: FooRegion }
1090
- // ```
1091
- _buildRegionFromObject: function(regionConfig, DefaultRegionClass) {
1092
- var RegionClass = regionConfig.regionClass || DefaultRegionClass;
1093
- var options = _.omit(regionConfig, 'selector', 'regionClass');
1094
-
1095
- if (regionConfig.selector && !options.el) {
1096
- options.el = regionConfig.selector;
1097
- }
1045
+ // set options temporarily so that we can get `el`.
1046
+ // options will be overriden by Object.constructor
1047
+ this.options = options || {};
1048
+ this.el = this.getOption('el');
1098
1049
 
1099
- var region = new RegionClass(options);
1050
+ // Handle when this.el is passed in as a $ wrapped element.
1051
+ this.el = this.el instanceof Backbone.$ ? this.el[0] : this.el;
1100
1052
 
1101
- // override the `getEl` function if we have a parentEl
1102
- // this must be overridden to ensure the selector is found
1103
- // on the first use of the region. if we try to assign the
1104
- // region's `el` to `parentEl.find(selector)` in the object
1105
- // literal to build the region, the element will not be
1106
- // guaranteed to be in the DOM already, and will cause problems
1107
- if (regionConfig.parentEl) {
1108
- region.getEl = function(el) {
1109
- if (_.isObject(el)) {
1110
- return Backbone.$(el);
1111
- }
1112
- var parentEl = regionConfig.parentEl;
1113
- if (_.isFunction(parentEl)) {
1114
- parentEl = parentEl();
1115
- }
1116
- return parentEl.find(el);
1117
- };
1053
+ if (!this.el) {
1054
+ throw new Marionette.Error({
1055
+ name: 'NoElError',
1056
+ message: 'An "el" must be specified for a region.'
1057
+ });
1118
1058
  }
1119
1059
 
1120
- return region;
1060
+ this.$el = this.getEl(this.el);
1061
+ Marionette.Object.call(this, options);
1121
1062
  },
1122
1063
 
1123
- // Build the region directly from a given `RegionClass`
1124
- _buildRegionFromRegionClass: function(RegionClass) {
1125
- return new RegionClass();
1126
- }
1127
-
1128
- });
1129
-
1130
- // Region Instance Methods
1131
- // -----------------------
1132
-
1133
- _.extend(Marionette.Region.prototype, Backbone.Events, {
1134
-
1135
1064
  // Displays a backbone view instance inside of the region.
1136
1065
  // Handles calling the `render` method for you. Reads content
1137
1066
  // directly from the `el` attribute. Also calls an optional
@@ -1141,9 +1070,12 @@
1141
1070
  // the old view being destroyed on show.
1142
1071
  // The `forceShow` option can be used to force a view to be
1143
1072
  // re-rendered if it's already shown in the region.
1144
-
1145
1073
  show: function(view, options){
1146
- this._ensureElement();
1074
+ if (!this._ensureElement()) {
1075
+ return;
1076
+ }
1077
+
1078
+ this._ensureViewIsIntact(view);
1147
1079
 
1148
1080
  var showOptions = options || {};
1149
1081
  var isDifferentView = view !== this.currentView;
@@ -1163,11 +1095,21 @@
1163
1095
  var _shouldShowView = isDifferentView || forceShow;
1164
1096
 
1165
1097
  if (isChangingView) {
1166
- this.triggerMethod('before:swapOut', this.currentView);
1098
+ this.triggerMethod('before:swapOut', this.currentView, this, options);
1099
+ }
1100
+
1101
+ if (this.currentView) {
1102
+ delete this.currentView._parent;
1167
1103
  }
1168
1104
 
1169
1105
  if (_shouldDestroyView) {
1170
1106
  this.empty();
1107
+
1108
+ // A `destroy` event is attached to the clean up manually removed views.
1109
+ // We need to detach this event when a new view is going to be shown as it
1110
+ // is no longer relevant.
1111
+ } else if (isChangingView && _shouldShowView) {
1112
+ this.currentView.off('destroy', this.empty, this);
1171
1113
  }
1172
1114
 
1173
1115
  if (_shouldShowView) {
@@ -1180,27 +1122,49 @@
1180
1122
  view.once('destroy', this.empty, this);
1181
1123
  view.render();
1182
1124
 
1125
+ view._parent = this;
1126
+
1183
1127
  if (isChangingView) {
1184
- this.triggerMethod('before:swap', view);
1128
+ this.triggerMethod('before:swap', view, this, options);
1185
1129
  }
1186
1130
 
1187
- this.triggerMethod('before:show', view);
1188
- Marionette.triggerMethodOn(view, 'before:show');
1189
-
1190
- this.attachHtml(view);
1131
+ this.triggerMethod('before:show', view, this, options);
1132
+ Marionette.triggerMethodOn(view, 'before:show', view, this, options);
1191
1133
 
1192
1134
  if (isChangingView) {
1193
- this.triggerMethod('swapOut', this.currentView);
1135
+ this.triggerMethod('swapOut', this.currentView, this, options);
1194
1136
  }
1195
1137
 
1138
+ // An array of views that we're about to display
1139
+ var attachedRegion = Marionette.isNodeAttached(this.el);
1140
+
1141
+ // The views that we're about to attach to the document
1142
+ // It's important that we prevent _getNestedViews from being executed unnecessarily
1143
+ // as it's a potentially-slow method
1144
+ var displayedViews = [];
1145
+
1146
+ var triggerBeforeAttach = showOptions.triggerBeforeAttach || this.triggerBeforeAttach;
1147
+ var triggerAttach = showOptions.triggerAttach || this.triggerAttach;
1148
+
1149
+ if (attachedRegion && triggerBeforeAttach) {
1150
+ displayedViews = this._displayedViews(view);
1151
+ this._triggerAttach(displayedViews, 'before:');
1152
+ }
1153
+
1154
+ this.attachHtml(view);
1196
1155
  this.currentView = view;
1197
1156
 
1157
+ if (attachedRegion && triggerAttach) {
1158
+ displayedViews = this._displayedViews(view);
1159
+ this._triggerAttach(displayedViews);
1160
+ }
1161
+
1198
1162
  if (isChangingView) {
1199
- this.triggerMethod('swap', view);
1163
+ this.triggerMethod('swap', view, this, options);
1200
1164
  }
1201
1165
 
1202
- this.triggerMethod('show', view);
1203
- Marionette.triggerMethodOn(view, 'show');
1166
+ this.triggerMethod('show', view, this, options);
1167
+ Marionette.triggerMethodOn(view, 'show', view, this, options);
1204
1168
 
1205
1169
  return this;
1206
1170
  }
@@ -1208,6 +1172,20 @@
1208
1172
  return this;
1209
1173
  },
1210
1174
 
1175
+ triggerBeforeAttach: true,
1176
+ triggerAttach: true,
1177
+
1178
+ _triggerAttach: function(views, prefix) {
1179
+ var eventName = (prefix || '') + 'attach';
1180
+ _.each(views, function(view) {
1181
+ Marionette.triggerMethodOn(view, eventName, view, this);
1182
+ }, this);
1183
+ },
1184
+
1185
+ _displayedViews: function(view) {
1186
+ return _.union([view], _.result(view, '_getNestedViews') || []);
1187
+ },
1188
+
1211
1189
  _ensureElement: function(){
1212
1190
  if (!_.isObject(this.el)) {
1213
1191
  this.$el = this.getEl(this.el);
@@ -1215,21 +1193,43 @@
1215
1193
  }
1216
1194
 
1217
1195
  if (!this.$el || this.$el.length === 0) {
1218
- throw new Marionette.Error('An "el" ' + this.$el.selector + ' must exist in DOM');
1196
+ if (this.getOption('allowMissingEl')) {
1197
+ return false;
1198
+ } else {
1199
+ throw new Marionette.Error('An "el" ' + this.$el.selector + ' must exist in DOM');
1200
+ }
1219
1201
  }
1202
+ return true;
1220
1203
  },
1221
1204
 
1222
- // Override this method to change how the region finds the
1223
- // DOM element that it manages. Return a jQuery selector object.
1205
+ _ensureViewIsIntact: function(view) {
1206
+ if (!view) {
1207
+ throw new Marionette.Error({
1208
+ name: 'ViewNotValid',
1209
+ message: 'The view passed is undefined and therefore invalid. You must pass a view instance to show.'
1210
+ });
1211
+ }
1212
+
1213
+ if (view.isDestroyed) {
1214
+ throw new Marionette.Error({
1215
+ name: 'ViewDestroyedError',
1216
+ message: 'View (cid: "' + view.cid + '") has already been destroyed and cannot be used.'
1217
+ });
1218
+ }
1219
+ },
1220
+
1221
+ // Override this method to change how the region finds the DOM
1222
+ // element that it manages. Return a jQuery selector object scoped
1223
+ // to a provided parent el or the document if none exists.
1224
1224
  getEl: function(el) {
1225
- return Backbone.$(el);
1225
+ return Backbone.$(el, Marionette._getValue(this.options.parentEl, this));
1226
1226
  },
1227
1227
 
1228
1228
  // Override this method to change how the new view is
1229
1229
  // appended to the `$el` that the region is managing
1230
1230
  attachHtml: function(view) {
1231
- // empty the node and append new view
1232
- this.el.innerHTML='';
1231
+ this.$el.contents().detach();
1232
+
1233
1233
  this.el.appendChild(view.el);
1234
1234
  },
1235
1235
 
@@ -1261,6 +1261,10 @@
1261
1261
  view.destroy();
1262
1262
  } else if (view.remove) {
1263
1263
  view.remove();
1264
+
1265
+ // appending isDestroyed to raw Backbone View allows regions
1266
+ // to throw a ViewDestroyedError for this view
1267
+ view.isDestroyed = true;
1264
1268
  }
1265
1269
  },
1266
1270
 
@@ -1293,152 +1297,196 @@
1293
1297
 
1294
1298
  delete this.$el;
1295
1299
  return this;
1296
- },
1300
+ }
1297
1301
 
1298
- // Proxy `getOption` to enable getting options from this or this.options by name.
1299
- getOption: Marionette.proxyGetOption,
1302
+ },
1300
1303
 
1301
- // import the `triggerMethod` to trigger events with corresponding
1302
- // methods if the method exists
1303
- triggerMethod: Marionette.triggerMethod
1304
- });
1304
+ // Static Methods
1305
+ {
1306
+
1307
+ // Build an instance of a region by passing in a configuration object
1308
+ // and a default region class to use if none is specified in the config.
1309
+ //
1310
+ // The config object should either be a string as a jQuery DOM selector,
1311
+ // a Region class directly, or an object literal that specifies a selector,
1312
+ // a custom regionClass, and any options to be supplied to the region:
1313
+ //
1314
+ // ```js
1315
+ // {
1316
+ // selector: "#foo",
1317
+ // regionClass: MyCustomRegion,
1318
+ // allowMissingEl: false
1319
+ // }
1320
+ // ```
1321
+ //
1322
+ buildRegion: function(regionConfig, DefaultRegionClass) {
1323
+ if (_.isString(regionConfig)) {
1324
+ return this._buildRegionFromSelector(regionConfig, DefaultRegionClass);
1325
+ }
1305
1326
 
1306
- // Copy the `extend` function used by Backbone's classes
1307
- Marionette.Region.extend = Marionette.extend;
1327
+ if (regionConfig.selector || regionConfig.el || regionConfig.regionClass) {
1328
+ return this._buildRegionFromObject(regionConfig, DefaultRegionClass);
1329
+ }
1308
1330
 
1309
- // Marionette.RegionManager
1310
- // ------------------------
1311
- //
1312
- // Manage one or more related `Marionette.Region` objects.
1313
- Marionette.RegionManager = (function(Marionette) {
1331
+ if (_.isFunction(regionConfig)) {
1332
+ return this._buildRegionFromRegionClass(regionConfig);
1333
+ }
1314
1334
 
1315
- var RegionManager = Marionette.Controller.extend({
1316
- constructor: function(options) {
1317
- this._regions = {};
1318
- Marionette.Controller.call(this, options);
1319
- },
1335
+ throw new Marionette.Error({
1336
+ message: 'Improper region configuration type.',
1337
+ url: 'marionette.region.html#region-configuration-types'
1338
+ });
1339
+ },
1320
1340
 
1321
- // Add multiple regions using an object literal or a
1322
- // function that returns an object literal, where
1323
- // each key becomes the region name, and each value is
1324
- // the region definition.
1325
- addRegions: function(regionDefinitions, defaults) {
1326
- if (_.isFunction(regionDefinitions)) {
1327
- regionDefinitions = regionDefinitions.apply(this, arguments);
1328
- }
1341
+ // Build the region from a string selector like '#foo-region'
1342
+ _buildRegionFromSelector: function(selector, DefaultRegionClass) {
1343
+ return new DefaultRegionClass({ el: selector });
1344
+ },
1329
1345
 
1330
- var regions = {};
1346
+ // Build the region from a configuration object
1347
+ // ```js
1348
+ // { selector: '#foo', regionClass: FooRegion, allowMissingEl: false }
1349
+ // ```
1350
+ _buildRegionFromObject: function(regionConfig, DefaultRegionClass) {
1351
+ var RegionClass = regionConfig.regionClass || DefaultRegionClass;
1352
+ var options = _.omit(regionConfig, 'selector', 'regionClass');
1331
1353
 
1332
- _.each(regionDefinitions, function(definition, name) {
1333
- if (_.isString(definition)) {
1334
- definition = {selector: definition};
1335
- }
1354
+ if (regionConfig.selector && !options.el) {
1355
+ options.el = regionConfig.selector;
1356
+ }
1336
1357
 
1337
- if (definition.selector) {
1338
- definition = _.defaults({}, definition, defaults);
1339
- }
1358
+ return new RegionClass(options);
1359
+ },
1340
1360
 
1341
- var region = this.addRegion(name, definition);
1342
- regions[name] = region;
1343
- }, this);
1361
+ // Build the region directly from a given `RegionClass`
1362
+ _buildRegionFromRegionClass: function(RegionClass) {
1363
+ return new RegionClass();
1364
+ }
1365
+ });
1344
1366
 
1345
- return regions;
1346
- },
1367
+ // Region Manager
1368
+ // --------------
1347
1369
 
1348
- // Add an individual region to the region manager,
1349
- // and return the region instance
1350
- addRegion: function(name, definition) {
1351
- var region;
1370
+ // Manage one or more related `Marionette.Region` objects.
1371
+ Marionette.RegionManager = Marionette.Controller.extend({
1372
+ constructor: function(options) {
1373
+ this._regions = {};
1352
1374
 
1353
- if (definition instanceof Marionette.Region) {
1354
- region = definition;
1355
- } else {
1356
- region = Marionette.Region.buildRegion(definition, Marionette.Region);
1375
+ Marionette.Controller.call(this, options);
1376
+
1377
+ this.addRegions(this.getOption('regions'));
1378
+ },
1379
+
1380
+ // Add multiple regions using an object literal or a
1381
+ // function that returns an object literal, where
1382
+ // each key becomes the region name, and each value is
1383
+ // the region definition.
1384
+ addRegions: function(regionDefinitions, defaults) {
1385
+ regionDefinitions = Marionette._getValue(regionDefinitions, this, arguments);
1386
+
1387
+ return _.reduce(regionDefinitions, function(regions, definition, name) {
1388
+ if (_.isString(definition)) {
1389
+ definition = {selector: definition};
1390
+ }
1391
+ if (definition.selector) {
1392
+ definition = _.defaults({}, definition, defaults);
1357
1393
  }
1358
1394
 
1359
- this.triggerMethod('before:add:region', name, region);
1395
+ regions[name] = this.addRegion(name, definition);
1396
+ return regions;
1397
+ }, {}, this);
1398
+ },
1360
1399
 
1361
- this._store(name, region);
1400
+ // Add an individual region to the region manager,
1401
+ // and return the region instance
1402
+ addRegion: function(name, definition) {
1403
+ var region;
1362
1404
 
1363
- this.triggerMethod('add:region', name, region);
1364
- return region;
1365
- },
1405
+ if (definition instanceof Marionette.Region) {
1406
+ region = definition;
1407
+ } else {
1408
+ region = Marionette.Region.buildRegion(definition, Marionette.Region);
1409
+ }
1366
1410
 
1367
- // Get a region by name
1368
- get: function(name) {
1369
- return this._regions[name];
1370
- },
1411
+ this.triggerMethod('before:add:region', name, region);
1371
1412
 
1372
- // Gets all the regions contained within
1373
- // the `regionManager` instance.
1374
- getRegions: function(){
1375
- return _.clone(this._regions);
1376
- },
1413
+ region._parent = this;
1414
+ this._store(name, region);
1377
1415
 
1378
- // Remove a region by name
1379
- removeRegion: function(name) {
1380
- var region = this._regions[name];
1381
- this._remove(name, region);
1416
+ this.triggerMethod('add:region', name, region);
1417
+ return region;
1418
+ },
1382
1419
 
1383
- return region;
1384
- },
1420
+ // Get a region by name
1421
+ get: function(name) {
1422
+ return this._regions[name];
1423
+ },
1385
1424
 
1386
- // Empty all regions in the region manager, and
1387
- // remove them
1388
- removeRegions: function() {
1389
- var regions = this.getRegions();
1390
- _.each(this._regions, function(region, name) {
1391
- this._remove(name, region);
1392
- }, this);
1425
+ // Gets all the regions contained within
1426
+ // the `regionManager` instance.
1427
+ getRegions: function(){
1428
+ return _.clone(this._regions);
1429
+ },
1393
1430
 
1394
- return regions;
1395
- },
1431
+ // Remove a region by name
1432
+ removeRegion: function(name) {
1433
+ var region = this._regions[name];
1434
+ this._remove(name, region);
1396
1435
 
1397
- // Empty all regions in the region manager, but
1398
- // leave them attached
1399
- emptyRegions: function() {
1400
- var regions = this.getRegions();
1401
- _.each(regions, function(region) {
1402
- region.empty();
1403
- }, this);
1436
+ return region;
1437
+ },
1404
1438
 
1405
- return regions;
1406
- },
1439
+ // Empty all regions in the region manager, and
1440
+ // remove them
1441
+ removeRegions: function() {
1442
+ var regions = this.getRegions();
1443
+ _.each(this._regions, function(region, name) {
1444
+ this._remove(name, region);
1445
+ }, this);
1407
1446
 
1408
- // Destroy all regions and shut down the region
1409
- // manager entirely
1410
- destroy: function() {
1411
- this.removeRegions();
1412
- return Marionette.Controller.prototype.destroy.apply(this, arguments);
1413
- },
1447
+ return regions;
1448
+ },
1414
1449
 
1415
- // internal method to store regions
1416
- _store: function(name, region) {
1417
- this._regions[name] = region;
1418
- this._setLength();
1419
- },
1450
+ // Empty all regions in the region manager, but
1451
+ // leave them attached
1452
+ emptyRegions: function() {
1453
+ var regions = this.getRegions();
1454
+ _.invoke(regions, 'empty');
1455
+ return regions;
1456
+ },
1420
1457
 
1421
- // internal method to remove a region
1422
- _remove: function(name, region) {
1423
- this.triggerMethod('before:remove:region', name, region);
1424
- region.empty();
1425
- region.stopListening();
1426
- delete this._regions[name];
1427
- this._setLength();
1428
- this.triggerMethod('remove:region', name, region);
1429
- },
1458
+ // Destroy all regions and shut down the region
1459
+ // manager entirely
1460
+ destroy: function() {
1461
+ this.removeRegions();
1462
+ return Marionette.Controller.prototype.destroy.apply(this, arguments);
1463
+ },
1430
1464
 
1431
- // set the number of regions current held
1432
- _setLength: function() {
1433
- this.length = _.size(this._regions);
1434
- }
1465
+ // internal method to store regions
1466
+ _store: function(name, region) {
1467
+ this._regions[name] = region;
1468
+ this._setLength();
1469
+ },
1435
1470
 
1436
- });
1471
+ // internal method to remove a region
1472
+ _remove: function(name, region) {
1473
+ this.triggerMethod('before:remove:region', name, region);
1474
+ region.empty();
1475
+ region.stopListening();
1437
1476
 
1438
- Marionette.actAsCollection(RegionManager.prototype, '_regions');
1477
+ delete region._parent;
1478
+ delete this._regions[name];
1479
+ this._setLength();
1480
+ this.triggerMethod('remove:region', name, region);
1481
+ },
1439
1482
 
1440
- return RegionManager;
1441
- })(Marionette);
1483
+ // set the number of regions current held
1484
+ _setLength: function() {
1485
+ this.length = _.size(this._regions);
1486
+ }
1487
+ });
1488
+
1489
+ Marionette.actAsCollection(Marionette.RegionManager.prototype, '_regions');
1442
1490
 
1443
1491
 
1444
1492
  // Template Cache
@@ -1479,7 +1527,7 @@
1479
1527
  // `clear("#t1", "#t2", "...")`
1480
1528
  clear: function() {
1481
1529
  var i;
1482
- var args = slice.call(arguments);
1530
+ var args = _.toArray(arguments);
1483
1531
  var length = args.length;
1484
1532
 
1485
1533
  if (length > 0) {
@@ -1557,12 +1605,7 @@
1557
1605
  });
1558
1606
  }
1559
1607
 
1560
- var templateFunc;
1561
- if (typeof template === 'function') {
1562
- templateFunc = template;
1563
- } else {
1564
- templateFunc = Marionette.TemplateCache.get(template);
1565
- }
1608
+ var templateFunc = _.isFunction(template) ? template : Marionette.TemplateCache.get(template);
1566
1609
 
1567
1610
  return templateFunc(data);
1568
1611
  }
@@ -1570,27 +1613,30 @@
1570
1613
 
1571
1614
 
1572
1615
  /* jshint maxlen: 114, nonew: false */
1573
- // Marionette.View
1574
- // ---------------
1616
+ // View
1617
+ // ----
1575
1618
 
1576
1619
  // The core view class that other Marionette views extend from.
1577
1620
  Marionette.View = Backbone.View.extend({
1621
+ isDestroyed: false,
1578
1622
 
1579
1623
  constructor: function(options) {
1580
1624
  _.bindAll(this, 'render');
1581
1625
 
1626
+ options = Marionette._getValue(options, this);
1627
+
1582
1628
  // this exposes view options to the view initializer
1583
1629
  // this is a backfill since backbone removed the assignment
1584
1630
  // of this.options
1585
1631
  // at some point however this may be removed
1586
- this.options = _.extend({}, _.result(this, 'options'), _.isFunction(options) ? options.call(this) : options);
1632
+ this.options = _.extend({}, _.result(this, 'options'), options);
1587
1633
 
1588
1634
  this._behaviors = Marionette.Behaviors(this);
1589
1635
 
1590
1636
  Backbone.View.apply(this, arguments);
1591
1637
 
1592
1638
  Marionette.MonitorDOMRefresh(this);
1593
- this.listenTo(this, 'show', this.onShowCalled);
1639
+ this.on('show', this.onShowCalled);
1594
1640
  },
1595
1641
 
1596
1642
  // Get the template for this view
@@ -1604,7 +1650,7 @@
1604
1650
  // Serialize a model by returning its attributes. Clones
1605
1651
  // the attributes to allow modification.
1606
1652
  serializeModel: function(model){
1607
- return model.toJSON.apply(model, slice.call(arguments, 1));
1653
+ return model.toJSON.apply(model, _.rest(arguments));
1608
1654
  },
1609
1655
 
1610
1656
  // Mix in template helper methods. Looks for a
@@ -1615,18 +1661,15 @@
1615
1661
  mixinTemplateHelpers: function(target) {
1616
1662
  target = target || {};
1617
1663
  var templateHelpers = this.getOption('templateHelpers');
1618
- if (_.isFunction(templateHelpers)) {
1619
- templateHelpers = templateHelpers.call(this);
1620
- }
1664
+ templateHelpers = Marionette._getValue(templateHelpers, this);
1621
1665
  return _.extend(target, templateHelpers);
1622
1666
  },
1623
1667
 
1624
1668
  // normalize the keys of passed hash with the views `ui` selectors.
1625
1669
  // `{"@ui.foo": "bar"}`
1626
1670
  normalizeUIKeys: function(hash) {
1627
- var ui = _.result(this, 'ui');
1628
1671
  var uiBindings = _.result(this, '_uiBindings');
1629
- return Marionette.normalizeUIKeys(hash, uiBindings || ui);
1672
+ return Marionette.normalizeUIKeys(hash, uiBindings || _.result(this, 'ui'));
1630
1673
  },
1631
1674
 
1632
1675
  // normalize the values of passed hash with the views `ui` selectors.
@@ -1642,18 +1685,15 @@
1642
1685
  configureTriggers: function() {
1643
1686
  if (!this.triggers) { return; }
1644
1687
 
1645
- var triggerEvents = {};
1646
-
1647
1688
  // Allow `triggers` to be configured as a function
1648
1689
  var triggers = this.normalizeUIKeys(_.result(this, 'triggers'));
1649
1690
 
1650
1691
  // Configure the triggers, prevent default
1651
1692
  // action and stop propagation of DOM events
1652
- _.each(triggers, function(value, key) {
1653
- triggerEvents[key] = this._buildViewTrigger(value);
1654
- }, this);
1655
-
1656
- return triggerEvents;
1693
+ return _.reduce(triggers, function(events, value, key) {
1694
+ events[key] = this._buildViewTrigger(value);
1695
+ return events;
1696
+ }, {}, this);
1657
1697
  },
1658
1698
 
1659
1699
  // Overriding Backbone.View's delegateEvents to handle
@@ -1673,8 +1713,7 @@
1673
1713
 
1674
1714
  // internal method to delegate DOM events and triggers
1675
1715
  _delegateDOMEvents: function(eventsArg) {
1676
- var events = eventsArg || this.events;
1677
- if (_.isFunction(events)) { events = events.call(this); }
1716
+ var events = Marionette._getValue(eventsArg || this.events, this);
1678
1717
 
1679
1718
  // normalize ui keys
1680
1719
  events = this.normalizeUIKeys(events);
@@ -1696,8 +1735,7 @@
1696
1735
  // Overriding Backbone.View's undelegateEvents to handle unbinding
1697
1736
  // the `triggers`, `modelEvents`, and `collectionEvents` config
1698
1737
  undelegateEvents: function() {
1699
- var args = slice.call(arguments);
1700
- Backbone.View.prototype.undelegateEvents.apply(this, args);
1738
+ Backbone.View.prototype.undelegateEvents.apply(this, arguments);
1701
1739
 
1702
1740
  this.unbindEntityEvents(this.model, this.getOption('modelEvents'));
1703
1741
  this.unbindEntityEvents(this.collection, this.getOption('collectionEvents'));
@@ -1730,7 +1768,7 @@
1730
1768
  destroy: function() {
1731
1769
  if (this.isDestroyed) { return; }
1732
1770
 
1733
- var args = slice.call(arguments);
1771
+ var args = _.toArray(arguments);
1734
1772
 
1735
1773
  this.triggerMethod.apply(this, ['before:destroy'].concat(args));
1736
1774
 
@@ -1778,8 +1816,7 @@
1778
1816
  this.ui = {};
1779
1817
 
1780
1818
  // bind each of the selectors
1781
- _.each(_.keys(bindings), function(key) {
1782
- var selector = bindings[key];
1819
+ _.each(bindings, function(selector, key) {
1783
1820
  this.ui[key] = this.$(selector);
1784
1821
  }, this);
1785
1822
  },
@@ -1850,17 +1887,35 @@
1850
1887
  // import the `triggerMethod` to trigger events with corresponding
1851
1888
  // methods if the method exists
1852
1889
  triggerMethod: function() {
1853
- var args = arguments;
1854
- var triggerMethod = Marionette.triggerMethod;
1855
-
1856
- var ret = triggerMethod.apply(this, args);
1857
- _.each(this._behaviors, function(b) {
1858
- triggerMethod.apply(b, args);
1859
- });
1890
+ var triggerMethod = Marionette._triggerMethod;
1891
+ var ret = triggerMethod(this, arguments);
1892
+ var behaviors = this._behaviors;
1893
+ // Use good ol' for as this is a very hot function
1894
+ for (var i = 0, length = behaviors && behaviors.length; i < length; i++) {
1895
+ triggerMethod(behaviors[i], arguments);
1896
+ }
1860
1897
 
1861
1898
  return ret;
1862
1899
  },
1863
1900
 
1901
+ // This method returns any views that are immediate
1902
+ // children of this view
1903
+ _getImmediateChildren: function() {
1904
+ return [];
1905
+ },
1906
+
1907
+ // Returns an array of every nested view within this view
1908
+ _getNestedViews: function() {
1909
+ var children = this._getImmediateChildren();
1910
+
1911
+ if (!children.length) { return children; }
1912
+
1913
+ return _.reduce(children, function(memo, view) {
1914
+ if (!view._getNestedViews) { return memo; }
1915
+ return memo.concat(view._getNestedViews());
1916
+ }, children);
1917
+ },
1918
+
1864
1919
  // Imports the "normalizeMethods" to transform hashes of
1865
1920
  // events=>function references/names to a hash of events=>function references
1866
1921
  normalizeMethods: Marionette.normalizeMethods,
@@ -1868,7 +1923,7 @@
1868
1923
  // Proxy `getOption` to enable getting options from this or this.options by name.
1869
1924
  getOption: Marionette.proxyGetOption,
1870
1925
 
1871
- // Proxy `unbindEntityEvents` to enable binding view's events from another entity.
1926
+ // Proxy `bindEntityEvents` to enable binding view's events from another entity.
1872
1927
  bindEntityEvents: Marionette.proxyBindEntityEvents,
1873
1928
 
1874
1929
  // Proxy `unbindEntityEvents` to enable unbinding view's events from another entity.
@@ -1897,21 +1952,27 @@
1897
1952
  // You can override the `serializeData` method in your own view definition,
1898
1953
  // to provide custom serialization for your view's data.
1899
1954
  serializeData: function(){
1900
- var data = {};
1901
-
1902
- if (this.model) {
1903
- data = _.partial(this.serializeModel, this.model).apply(this, arguments);
1955
+ if (!this.model && !this.collection) {
1956
+ return {};
1904
1957
  }
1905
- else if (this.collection) {
1906
- data = { items: _.partial(this.serializeCollection, this.collection).apply(this, arguments) };
1958
+
1959
+ var args = [this.model || this.collection];
1960
+ if (arguments.length) {
1961
+ args.push.apply(args, arguments);
1907
1962
  }
1908
1963
 
1909
- return data;
1964
+ if (this.model) {
1965
+ return this.serializeModel.apply(this, args);
1966
+ } else {
1967
+ return {
1968
+ items: this.serializeCollection.apply(this, args)
1969
+ };
1970
+ }
1910
1971
  },
1911
1972
 
1912
1973
  // Serialize a collection by serializing each of its models.
1913
1974
  serializeCollection: function(collection){
1914
- return collection.toJSON.apply(collection, slice.call(arguments, 1));
1975
+ return collection.toJSON.apply(collection, _.rest(arguments));
1915
1976
  },
1916
1977
 
1917
1978
  // Render the view, defaulting to underscore.js templates.
@@ -1978,14 +2039,6 @@
1978
2039
  this.$el.html(html);
1979
2040
 
1980
2041
  return this;
1981
- },
1982
-
1983
- // Override the default destroy event to add a few
1984
- // more events that are triggered.
1985
- destroy: function() {
1986
- if (this.isDestroyed) { return; }
1987
-
1988
- return Marionette.View.prototype.destroy.apply(this, arguments);
1989
2042
  }
1990
2043
  });
1991
2044
 
@@ -2008,7 +2061,9 @@
2008
2061
  // This will fallback onto appending childView's to the end.
2009
2062
  constructor: function(options){
2010
2063
  var initOptions = options || {};
2011
- this.sort = _.isUndefined(initOptions.sort) ? true : initOptions.sort;
2064
+ if (_.isUndefined(this.sort)){
2065
+ this.sort = _.isUndefined(initOptions.sort) ? true : initOptions.sort;
2066
+ }
2012
2067
 
2013
2068
  this.once('render', this._initialEvents);
2014
2069
  this._initChildViewStorage();
@@ -2126,6 +2181,9 @@
2126
2181
  }
2127
2182
  },
2128
2183
 
2184
+ // Internal reference to what index a `emptyView` is.
2185
+ _emptyViewIndex: -1,
2186
+
2129
2187
  // Internal method. Separated so that CompositeView can have
2130
2188
  // more control over events being triggered, around the rendering
2131
2189
  // process
@@ -2189,7 +2247,7 @@
2189
2247
  },
2190
2248
 
2191
2249
  // Render and show the emptyView. Similar to addChild method
2192
- // but "child:added" events are not fired, and the event from
2250
+ // but "add:child" events are not fired, and the event from
2193
2251
  // emptyView are not forwarded
2194
2252
  addEmptyView: function(child, EmptyView) {
2195
2253
 
@@ -2198,12 +2256,14 @@
2198
2256
  this.getOption('childViewOptions');
2199
2257
 
2200
2258
  if (_.isFunction(emptyViewOptions)){
2201
- emptyViewOptions = emptyViewOptions.call(this);
2259
+ emptyViewOptions = emptyViewOptions.call(this, child, this._emptyViewIndex);
2202
2260
  }
2203
2261
 
2204
2262
  // build the empty view
2205
2263
  var view = this.buildChildView(child, EmptyView, emptyViewOptions);
2206
2264
 
2265
+ view._parent = this;
2266
+
2207
2267
  // Proxy emptyView events
2208
2268
  this.proxyChildEvents(view);
2209
2269
 
@@ -2218,7 +2278,7 @@
2218
2278
  this.children.add(view);
2219
2279
 
2220
2280
  // Render it and show it
2221
- this.renderChildView(view, -1);
2281
+ this.renderChildView(view, this._emptyViewIndex);
2222
2282
 
2223
2283
  // call the 'show' method if the collection view
2224
2284
  // has already been shown
@@ -2252,9 +2312,7 @@
2252
2312
  // in order to keep the children in sync with the collection.
2253
2313
  addChild: function(child, ChildView, index) {
2254
2314
  var childViewOptions = this.getOption('childViewOptions');
2255
- if (_.isFunction(childViewOptions)) {
2256
- childViewOptions = childViewOptions.call(this, child, index);
2257
- }
2315
+ childViewOptions = Marionette._getValue(childViewOptions, this, [child, index]);
2258
2316
 
2259
2317
  var view = this.buildChildView(child, ChildView, childViewOptions);
2260
2318
 
@@ -2263,6 +2321,8 @@
2263
2321
 
2264
2322
  this._addChildView(view, index);
2265
2323
 
2324
+ view._parent = this;
2325
+
2266
2326
  return view;
2267
2327
  },
2268
2328
 
@@ -2276,22 +2336,14 @@
2276
2336
  if (increment) {
2277
2337
  // assign the index to the view
2278
2338
  view._index = index;
2279
-
2280
- // increment the index of views after this one
2281
- this.children.each(function (laterView) {
2282
- if (laterView._index >= view._index) {
2283
- laterView._index++;
2284
- }
2285
- });
2286
- }
2287
- else {
2288
- // decrement the index of views after this one
2289
- this.children.each(function (laterView) {
2290
- if (laterView._index >= view._index) {
2291
- laterView._index--;
2292
- }
2293
- });
2294
2339
  }
2340
+
2341
+ // update the indexes of views after this one
2342
+ this.children.each(function (laterView) {
2343
+ if (laterView._index >= view._index) {
2344
+ laterView._index += increment ? 1 : -1;
2345
+ }
2346
+ });
2295
2347
  },
2296
2348
 
2297
2349
 
@@ -2340,6 +2392,7 @@
2340
2392
  if (view.destroy) { view.destroy(); }
2341
2393
  else if (view.remove) { view.remove(); }
2342
2394
 
2395
+ delete view._parent;
2343
2396
  this.stopListening(view);
2344
2397
  this.children.remove(view);
2345
2398
  this.triggerMethod('remove:child', view);
@@ -2448,7 +2501,7 @@
2448
2501
  // Forward all child view events through the parent,
2449
2502
  // prepending "childview:" to the event name
2450
2503
  this.listenTo(view, 'all', function() {
2451
- var args = slice.call(arguments);
2504
+ var args = _.toArray(arguments);
2452
2505
  var rootEvent = args[0];
2453
2506
  var childEvents = this.normalizeMethods(_.result(this, 'childEvents'));
2454
2507
 
@@ -2462,6 +2515,10 @@
2462
2515
 
2463
2516
  this.triggerMethod.apply(this, args);
2464
2517
  }, this);
2518
+ },
2519
+
2520
+ _getImmediateChildren: function() {
2521
+ return _.values(this.children._views);
2465
2522
  }
2466
2523
  });
2467
2524
 
@@ -2510,17 +2567,10 @@
2510
2567
  getChildView: function(child) {
2511
2568
  var childView = this.getOption('childView') || this.constructor;
2512
2569
 
2513
- if (!childView) {
2514
- throw new Marionette.Error({
2515
- name: 'NoChildViewError',
2516
- message: 'A "childView" must be specified'
2517
- });
2518
- }
2519
-
2520
2570
  return childView;
2521
2571
  },
2522
2572
 
2523
- // Serialize the collection for the view.
2573
+ // Serialize the model for the view.
2524
2574
  // You can override the `serializeData` method in your own view
2525
2575
  // definition, to provide custom serialization for your view's data.
2526
2576
  serializeData: function() {
@@ -2533,9 +2583,7 @@
2533
2583
  return data;
2534
2584
  },
2535
2585
 
2536
- // Renders the model once, and the collection once. Calling
2537
- // this again will tell the model's view to re-render itself
2538
- // but the collection will not re-render.
2586
+ // Renders the model and the collection.
2539
2587
  render: function() {
2540
2588
  this._ensureViewIsIntact();
2541
2589
  this.isRendered = true;
@@ -2604,13 +2652,13 @@
2604
2652
  // Overidden from CollectionView to ensure view is appended to
2605
2653
  // childViewContainer
2606
2654
  _insertAfter: function (childView) {
2607
- var $container = this.getChildViewContainer(this);
2655
+ var $container = this.getChildViewContainer(this, childView);
2608
2656
  $container.append(childView.el);
2609
2657
  },
2610
2658
 
2611
2659
  // Internal method to ensure an `$childViewContainer` exists, for the
2612
2660
  // `attachHtml` method to use.
2613
- getChildViewContainer: function(containerView) {
2661
+ getChildViewContainer: function(containerView, childView) {
2614
2662
  if ('$childViewContainer' in containerView) {
2615
2663
  return containerView.$childViewContainer;
2616
2664
  }
@@ -2619,7 +2667,7 @@
2619
2667
  var childViewContainer = Marionette.getOption(containerView, 'childViewContainer');
2620
2668
  if (childViewContainer) {
2621
2669
 
2622
- var selector = _.isFunction(childViewContainer) ? childViewContainer.call(containerView) : childViewContainer;
2670
+ var selector = Marionette._getValue(childViewContainer, containerView);
2623
2671
 
2624
2672
  if (selector.charAt(0) === '@' && containerView.ui) {
2625
2673
  container = containerView.ui[selector.substr(4)];
@@ -2650,8 +2698,8 @@
2650
2698
  }
2651
2699
  });
2652
2700
 
2653
- // LayoutView
2654
- // ----------
2701
+ // Layout View
2702
+ // -----------
2655
2703
 
2656
2704
  // Used for managing application layoutViews, nested layoutViews and
2657
2705
  // multiple regions within an application or sub-application.
@@ -2734,11 +2782,9 @@
2734
2782
 
2735
2783
  // internal method to build regions
2736
2784
  _buildRegions: function(regions) {
2737
- var that = this;
2738
-
2739
2785
  var defaults = {
2740
2786
  regionClass: this.getOption('regionClass'),
2741
- parentEl: function() { return that.$el; }
2787
+ parentEl: _.partial(_.result, this, 'el')
2742
2788
  };
2743
2789
 
2744
2790
  return this.regionManager.addRegions(regions, defaults);
@@ -2750,19 +2796,13 @@
2750
2796
  var regions;
2751
2797
  this._initRegionManager();
2752
2798
 
2753
- if (_.isFunction(this.regions)) {
2754
- regions = this.regions(options);
2755
- } else {
2756
- regions = this.regions || {};
2757
- }
2799
+ regions = Marionette._getValue(this.regions, this, [options]) || {};
2758
2800
 
2759
2801
  // Enable users to define `regions` as instance options.
2760
2802
  var regionOptions = this.getOption.call(options, 'regions');
2761
2803
 
2762
2804
  // enable region options to be a function
2763
- if (_.isFunction(regionOptions)) {
2764
- regionOptions = regionOptions.call(this, options);
2765
- }
2805
+ regionOptions = Marionette._getValue(regionOptions, this, [options]);
2766
2806
 
2767
2807
  _.extend(regions, regionOptions);
2768
2808
 
@@ -2776,10 +2816,7 @@
2776
2816
  // Internal method to re-initialize all of the regions by updating the `el` that
2777
2817
  // they point to
2778
2818
  _reInitializeRegions: function() {
2779
- this.regionManager.emptyRegions();
2780
- this.regionManager.each(function(region) {
2781
- region.reset();
2782
- });
2819
+ this.regionManager.invoke('reset');
2783
2820
  },
2784
2821
 
2785
2822
  // Enable easy overriding of the default `RegionManager`
@@ -2793,6 +2830,7 @@
2793
2830
  // and all regions in it
2794
2831
  _initRegionManager: function() {
2795
2832
  this.regionManager = this.getRegionManager();
2833
+ this.regionManager._parent = this;
2796
2834
 
2797
2835
  this.listenTo(this.regionManager, 'before:add:region', function(name) {
2798
2836
  this.triggerMethod('before:add:region', name);
@@ -2811,20 +2849,27 @@
2811
2849
  delete this[name];
2812
2850
  this.triggerMethod('remove:region', name, region);
2813
2851
  });
2852
+ },
2853
+
2854
+ _getImmediateChildren: function() {
2855
+ return _.chain(this.regionManager.getRegions())
2856
+ .pluck('currentView')
2857
+ .compact()
2858
+ .value();
2814
2859
  }
2815
2860
  });
2816
2861
 
2817
2862
 
2818
2863
  // Behavior
2819
- // -----------
2864
+ // --------
2820
2865
 
2821
2866
  // A Behavior is an isolated set of DOM /
2822
2867
  // user interactions that can be mixed into any View.
2823
2868
  // Behaviors allow you to blackbox View specific interactions
2824
2869
  // into portable logical chunks, keeping your views simple and your code DRY.
2825
2870
 
2826
- Marionette.Behavior = (function(_, Backbone) {
2827
- function Behavior(options, view) {
2871
+ Marionette.Behavior = Marionette.Object.extend({
2872
+ constructor: function(options, view) {
2828
2873
  // Setup reference to the view.
2829
2874
  // this comes in handle when a behavior
2830
2875
  // wants to directly talk up the chain
@@ -2833,57 +2878,31 @@
2833
2878
  this.defaults = _.result(this, 'defaults') || {};
2834
2879
  this.options = _.extend({}, this.defaults, options);
2835
2880
 
2836
- // proxy behavior $ method to the view
2837
- // this is useful for doing jquery DOM lookups
2838
- // scoped to behaviors view.
2839
- this.$ = function() {
2840
- return this.view.$.apply(this.view, arguments);
2841
- };
2842
-
2843
- // Call the initialize method passing
2844
- // the arguments from the instance constructor
2845
- this.initialize.apply(this, arguments);
2846
- }
2847
-
2848
- _.extend(Behavior.prototype, Backbone.Events, {
2849
- initialize: function() {},
2850
-
2851
- // stopListening to behavior `onListen` events.
2852
- destroy: function() {
2853
- this.stopListening();
2854
- },
2855
-
2856
- proxyViewProperties: function (view) {
2857
- this.$el = view.$el;
2858
- this.el = view.el;
2859
- },
2860
-
2861
- // import the `triggerMethod` to trigger events with corresponding
2862
- // methods if the method exists
2863
- triggerMethod: Marionette.triggerMethod,
2864
-
2865
- // Proxy `getOption` to enable getting options from this or this.options by name.
2866
- getOption: Marionette.proxyGetOption,
2867
-
2868
- // Proxy `unbindEntityEvents` to enable binding view's events from another entity.
2869
- bindEntityEvents: Marionette.proxyBindEntityEvents,
2881
+ Marionette.Object.apply(this, arguments);
2882
+ },
2870
2883
 
2871
- // Proxy `unbindEntityEvents` to enable unbinding view's events from another entity.
2872
- unbindEntityEvents: Marionette.proxyUnbindEntityEvents
2873
- });
2884
+ // proxy behavior $ method to the view
2885
+ // this is useful for doing jquery DOM lookups
2886
+ // scoped to behaviors view.
2887
+ $: function() {
2888
+ return this.view.$.apply(this.view, arguments);
2889
+ },
2874
2890
 
2875
- // Borrow Backbones extend implementation
2876
- // this allows us to setup a proper
2877
- // inheritance pattern that follows suit
2878
- // with the rest of Marionette views.
2879
- Behavior.extend = Marionette.extend;
2891
+ // Stops the behavior from listening to events.
2892
+ // Overrides Object#destroy to prevent additional events from being triggered.
2893
+ destroy: function() {
2894
+ this.stopListening();
2895
+ },
2880
2896
 
2881
- return Behavior;
2882
- })(_, Backbone);
2897
+ proxyViewProperties: function (view) {
2898
+ this.$el = view.$el;
2899
+ this.el = view.el;
2900
+ }
2901
+ });
2883
2902
 
2884
2903
  /* jshint maxlen: 143 */
2885
- // Marionette.Behaviors
2886
- // --------
2904
+ // Behaviors
2905
+ // ---------
2887
2906
 
2888
2907
  // Behaviors is a utility class that takes care of
2889
2908
  // gluing your behavior instances to their given View.
@@ -2892,6 +2911,8 @@
2892
2911
  // method for things to work properly.
2893
2912
 
2894
2913
  Marionette.Behaviors = (function(Marionette, _) {
2914
+ // Borrow event splitter from Backbone
2915
+ var delegateEventSplitter = /^(\S+)\s*(.*)$/;
2895
2916
 
2896
2917
  function Behaviors(view, behaviors) {
2897
2918
 
@@ -2918,12 +2939,12 @@
2918
2939
 
2919
2940
  behaviorEvents: function(behaviorEvents, behaviors) {
2920
2941
  var _behaviorsEvents = {};
2921
- var viewUI = _.result(this, 'ui');
2942
+ var viewUI = this._uiBindings || _.result(this, 'ui');
2922
2943
 
2923
2944
  _.each(behaviors, function(b, i) {
2924
2945
  var _events = {};
2925
2946
  var behaviorEvents = _.clone(_.result(b, 'events')) || {};
2926
- var behaviorUI = _.result(b, 'ui');
2947
+ var behaviorUI = b._uiBindings || _.result(b, 'ui');
2927
2948
 
2928
2949
  // Construct an internal UI hash first using
2929
2950
  // the views UI hash and then the behaviors UI hash.
@@ -2936,21 +2957,25 @@
2936
2957
  // a user to use the @ui. syntax.
2937
2958
  behaviorEvents = Marionette.normalizeUIKeys(behaviorEvents, ui);
2938
2959
 
2939
- _.each(_.keys(behaviorEvents), function(key) {
2940
- // Append white-space at the end of each key to prevent behavior key collisions.
2941
- // This is relying on the fact that backbone events considers "click .foo" the same as
2942
- // "click .foo ".
2960
+ var j = 0;
2961
+ _.each(behaviorEvents, function(behaviour, key) {
2962
+ var match = key.match(delegateEventSplitter);
2963
+
2964
+ // Set event name to be namespaced using the view cid,
2965
+ // the behavior index, and the behavior event index
2966
+ // to generate a non colliding event namespace
2967
+ // http://api.jquery.com/event.namespace/
2968
+ var eventName = match[1] + '.' + [this.cid, i, j++, ' '].join(''),
2969
+ selector = match[2];
2943
2970
 
2944
- // +2 is used because new Array(1) or 0 is "" and not " "
2945
- var whitespace = (new Array(i + 2)).join(' ');
2946
- var eventKey = key + whitespace;
2947
- var handler = _.isFunction(behaviorEvents[key]) ? behaviorEvents[key] : b[behaviorEvents[key]];
2971
+ var eventKey = eventName + selector;
2972
+ var handler = _.isFunction(behaviour) ? behaviour : b[behaviour];
2948
2973
 
2949
2974
  _events[eventKey] = _.bind(handler, b);
2950
- });
2975
+ }, this);
2951
2976
 
2952
2977
  _behaviorsEvents = _.extend(_behaviorsEvents, _events);
2953
- });
2978
+ }, this);
2954
2979
 
2955
2980
  return _behaviorsEvents;
2956
2981
  }
@@ -2985,7 +3010,7 @@
2985
3010
  }
2986
3011
 
2987
3012
  // Get behavior class can be either a flat object or a method
2988
- return _.isFunction(Behaviors.behaviorsLookup) ? Behaviors.behaviorsLookup.apply(this, arguments)[key] : Behaviors.behaviorsLookup[key];
3013
+ return Marionette._getValue(Behaviors.behaviorsLookup, this, [options, key])[key];
2989
3014
  },
2990
3015
 
2991
3016
  // Iterate over the behaviors object, for each behavior
@@ -3036,7 +3061,7 @@
3036
3061
 
3037
3062
  triggersHash = Marionette.normalizeUIKeys(triggersHash, ui);
3038
3063
 
3039
- _.each(triggersHash, _.partial(this._setHandlerForBehavior, behavior, i), this);
3064
+ _.each(triggersHash, _.bind(this._setHandlerForBehavior, this, behavior, i));
3040
3065
  },
3041
3066
 
3042
3067
  // Internal method to create and assign the trigger handler for a given
@@ -3056,8 +3081,8 @@
3056
3081
  })(Marionette, _);
3057
3082
 
3058
3083
 
3059
- // AppRouter
3060
- // ---------
3084
+ // App Router
3085
+ // ----------
3061
3086
 
3062
3087
  // Reduce the boilerplate code of handling route events
3063
3088
  // and then calling a single method on another object.
@@ -3077,10 +3102,10 @@
3077
3102
  Marionette.AppRouter = Backbone.Router.extend({
3078
3103
 
3079
3104
  constructor: function(options) {
3080
- Backbone.Router.apply(this, arguments);
3081
-
3082
3105
  this.options = options || {};
3083
3106
 
3107
+ Backbone.Router.apply(this, arguments);
3108
+
3084
3109
  var appRoutes = this.getOption('appRoutes');
3085
3110
  var controller = this._getController();
3086
3111
  this.processAppRoutes(controller, appRoutes);
@@ -3097,11 +3122,10 @@
3097
3122
  // process the route event and trigger the onRoute
3098
3123
  // method call, if it exists
3099
3124
  _processOnRoute: function(routeName, routeArgs) {
3100
- // find the path that matched
3101
- var routePath = _.invert(this.getOption('appRoutes'))[routeName];
3102
-
3103
- // make sure an onRoute is there, and call it
3125
+ // make sure an onRoute before trying to call it
3104
3126
  if (_.isFunction(this.onRoute)) {
3127
+ // find the path that matches the current route
3128
+ var routePath = _.invert(this.getOption('appRoutes'))[routeName];
3105
3129
  this.onRoute(routeName, routePath, routeArgs);
3106
3130
  }
3107
3131
  },
@@ -3134,7 +3158,13 @@
3134
3158
  },
3135
3159
 
3136
3160
  // Proxy `getOption` to enable getting options from this or this.options by name.
3137
- getOption: Marionette.proxyGetOption
3161
+ getOption: Marionette.proxyGetOption,
3162
+
3163
+ triggerMethod: Marionette.triggerMethod,
3164
+
3165
+ bindEntityEvents: Marionette.proxyBindEntityEvents,
3166
+
3167
+ unbindEntityEvents: Marionette.proxyUnbindEntityEvents
3138
3168
  });
3139
3169
 
3140
3170
  // Application
@@ -3143,20 +3173,15 @@
3143
3173
  // Contain and manage the composite application as a whole.
3144
3174
  // Stores and starts up `Region` objects, includes an
3145
3175
  // event aggregator as `app.vent`
3146
- Marionette.Application = function(options) {
3147
- this.options = options;
3148
- this._initializeRegions(options);
3149
- this._initCallbacks = new Marionette.Callbacks();
3150
- this.submodules = {};
3151
- _.extend(this, options);
3152
- this._initChannel();
3153
- this.initialize.apply(this, arguments);
3154
- };
3155
-
3156
- _.extend(Marionette.Application.prototype, Backbone.Events, {
3157
- // Initialize is an empty function by default. Override it with your own
3158
- // initialization logic.
3159
- initialize: function() {},
3176
+ Marionette.Application = Marionette.Object.extend({
3177
+ constructor: function(options) {
3178
+ this._initializeRegions(options);
3179
+ this._initCallbacks = new Marionette.Callbacks();
3180
+ this.submodules = {};
3181
+ _.extend(this, options);
3182
+ this._initChannel();
3183
+ Marionette.Object.call(this, options);
3184
+ },
3160
3185
 
3161
3186
  // Command execution, facilitated by Backbone.Wreqr.Commands
3162
3187
  execute: function() {
@@ -3222,9 +3247,7 @@
3222
3247
  // Overwrite the module class if the user specifies one
3223
3248
  var ModuleClass = Marionette.Module.getClass(moduleDefinition);
3224
3249
 
3225
- // slice the args, and add this application object as the
3226
- // first argument of the array
3227
- var args = slice.call(arguments);
3250
+ var args = _.toArray(arguments);
3228
3251
  args.unshift(this);
3229
3252
 
3230
3253
  // see the Marionette.Module object for more information
@@ -3264,23 +3287,24 @@
3264
3287
  // Internal method to set up the region manager
3265
3288
  _initRegionManager: function() {
3266
3289
  this._regionManager = this.getRegionManager();
3290
+ this._regionManager._parent = this;
3267
3291
 
3268
- this.listenTo(this._regionManager, 'before:add:region', function(name) {
3269
- this.triggerMethod('before:add:region', name);
3292
+ this.listenTo(this._regionManager, 'before:add:region', function() {
3293
+ Marionette._triggerMethod(this, 'before:add:region', arguments);
3270
3294
  });
3271
3295
 
3272
3296
  this.listenTo(this._regionManager, 'add:region', function(name, region) {
3273
3297
  this[name] = region;
3274
- this.triggerMethod('add:region', name, region);
3298
+ Marionette._triggerMethod(this, 'add:region', arguments);
3275
3299
  });
3276
3300
 
3277
- this.listenTo(this._regionManager, 'before:remove:region', function(name) {
3278
- this.triggerMethod('before:remove:region', name);
3301
+ this.listenTo(this._regionManager, 'before:remove:region', function() {
3302
+ Marionette._triggerMethod(this, 'before:remove:region', arguments);
3279
3303
  });
3280
3304
 
3281
- this.listenTo(this._regionManager, 'remove:region', function(name, region) {
3305
+ this.listenTo(this._regionManager, 'remove:region', function(name) {
3282
3306
  delete this[name];
3283
- this.triggerMethod('remove:region', name, region);
3307
+ Marionette._triggerMethod(this, 'remove:region', arguments);
3284
3308
  });
3285
3309
  },
3286
3310
 
@@ -3291,19 +3315,9 @@
3291
3315
  this.vent = _.result(this, 'vent') || this.channel.vent;
3292
3316
  this.commands = _.result(this, 'commands') || this.channel.commands;
3293
3317
  this.reqres = _.result(this, 'reqres') || this.channel.reqres;
3294
- },
3295
-
3296
- // import the `triggerMethod` to trigger events with corresponding
3297
- // methods if the method exists
3298
- triggerMethod: Marionette.triggerMethod,
3299
-
3300
- // Proxy `getOption` to enable getting options from this or this.options by name.
3301
- getOption: Marionette.proxyGetOption
3318
+ }
3302
3319
  });
3303
3320
 
3304
- // Copy the `extend` function used by Backbone's classes
3305
- Marionette.Application.extend = Marionette.extend;
3306
-
3307
3321
  /* jshint maxparams: 9 */
3308
3322
 
3309
3323
  // Module
@@ -3391,7 +3405,7 @@
3391
3405
 
3392
3406
  // stop the sub-modules; depth-first, to make sure the
3393
3407
  // sub-modules are stopped / finalized before parents
3394
- _.each(this.submodules, function(mod) { mod.stop(); });
3408
+ _.invoke(this.submodules, 'stop');
3395
3409
 
3396
3410
  // run the finalizers
3397
3411
  this._finalizerCallbacks.run(undefined, this);
@@ -3450,8 +3464,7 @@
3450
3464
 
3451
3465
  // get the custom args passed in after the module definition and
3452
3466
  // get rid of the module name and definition function
3453
- var customArgs = slice.call(arguments);
3454
- customArgs.splice(0, 3);
3467
+ var customArgs = _.rest(arguments, 3);
3455
3468
 
3456
3469
  // Split the module names and get the number of submodules.
3457
3470
  // i.e. an example module name of `Doge.Wow.Amaze` would