marionette-rails 2.2.2 → 2.3.2

Sign up to get free protection for your applications and to get access to all the features.
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