marionette-rails 2.1.0 → 2.2.1

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: 5d10008a4bdd7395c481f9176275a9a54da10d01
4
- data.tar.gz: ab674f817ccd0980590767aeae47cb69a8e3b42a
3
+ metadata.gz: 97731fd8838b5590ee15c0d9cb0fbabfe575d264
4
+ data.tar.gz: fa19df0cc00e1a5887bbaf02ea6a348579b9afd2
5
5
  SHA512:
6
- metadata.gz: 48b594f9ca211caa9108bcc5bd2df708a9f5b70461346e9fc53d747d69567727d9f86291067b503337443e051df8087510b16c4af83d14566f783392940bf04d
7
- data.tar.gz: 6b6fad38c2a7ae1ad774c6702d5364b0787125c68c86de6a37ab60ee2837875dde2919959e2f7421b0aa15ecef4c59d6e1bc1380670b93e75bb9deadf309ee5b
6
+ metadata.gz: 3a8a8069c059b6ee0b4100a2e31b58c476ecda37aa9b1938c2573bf556917540ef750ecbe065bd45d08ed8db2df7bc1c818394d733272eca31284c81e6d83ed2
7
+ data.tar.gz: 92f348ccd1500aef5677e420ecda8f0dd0537a38b765aee6f5513e0175a3fc85d6d20997993d256b51bff2992c44c285e01efe3a55a4b14fa2fb1a48e189861b
@@ -1,5 +1,5 @@
1
1
  module Marionette
2
2
  module Rails
3
- VERSION = '2.1.0'
3
+ VERSION = '2.2.1'
4
4
  end
5
5
  end
@@ -1,6 +1,6 @@
1
1
  // MarionetteJS (Backbone.Marionette)
2
2
  // ----------------------------------
3
- // v2.1.0
3
+ // v2.2.1
4
4
  //
5
5
  // Copyright (c)2014 Derick Bailey, Muted Solutions, LLC.
6
6
  // Distributed under MIT license
@@ -19,6 +19,7 @@
19
19
 
20
20
  (function(root, factory) {
21
21
 
22
+ /* istanbul ignore next */
22
23
  if (typeof define === 'function' && define.amd) {
23
24
  define(['backbone', 'underscore'], function(Backbone, _) {
24
25
  return (root.Marionette = factory(root, Backbone, _));
@@ -34,6 +35,7 @@
34
35
  }(this, function(root, Backbone, _) {
35
36
  'use strict';
36
37
 
38
+ /* istanbul ignore next */
37
39
  // Backbone.BabySitter
38
40
  // -------------------
39
41
  // v0.1.4
@@ -172,6 +174,8 @@
172
174
  };
173
175
  return Backbone.ChildViewContainer;
174
176
  })(Backbone, _);
177
+
178
+ /* istanbul ignore next */
175
179
  // Backbone.Wreqr (Backbone.Marionette)
176
180
  // ----------------------------------
177
181
  // v1.3.1
@@ -489,7 +493,7 @@
489
493
 
490
494
  var Marionette = Backbone.Marionette = {};
491
495
 
492
- Marionette.VERSION = '2.1.0';
496
+ Marionette.VERSION = '2.2.1';
493
497
 
494
498
  Marionette.noConflict = function() {
495
499
  root.Marionette = previousMarionette;
@@ -502,51 +506,45 @@
502
506
  Marionette.Deferred = Backbone.$.Deferred;
503
507
 
504
508
  /* jshint unused: false */
505
-
509
+
506
510
  // Helpers
507
511
  // -------
508
-
512
+
509
513
  // For slicing `arguments` in functions
510
514
  var slice = Array.prototype.slice;
511
-
512
- function throwError(message, name) {
513
- var error = new Error(message);
514
- error.name = name || 'Error';
515
- throw error;
516
- }
517
-
515
+
518
516
  // Marionette.extend
519
517
  // -----------------
520
-
518
+
521
519
  // Borrow the Backbone `extend` method so we can use it as needed
522
520
  Marionette.extend = Backbone.Model.extend;
523
-
521
+
524
522
  // Marionette.getOption
525
523
  // --------------------
526
-
524
+
527
525
  // Retrieve an object, function or other value from a target
528
526
  // object or its `options`, with `options` taking precedence.
529
527
  Marionette.getOption = function(target, optionName) {
530
528
  if (!target || !optionName) { return; }
531
529
  var value;
532
-
530
+
533
531
  if (target.options && (target.options[optionName] !== undefined)) {
534
532
  value = target.options[optionName];
535
533
  } else {
536
534
  value = target[optionName];
537
535
  }
538
-
536
+
539
537
  return value;
540
538
  };
541
-
539
+
542
540
  // Proxy `Marionette.getOption`
543
541
  Marionette.proxyGetOption = function(optionName) {
544
542
  return Marionette.getOption(this, optionName);
545
543
  };
546
-
544
+
547
545
  // Marionette.normalizeMethods
548
546
  // ----------------------
549
-
547
+
550
548
  // Pass in a mapping of events => functions or function names
551
549
  // and return a mapping of events => functions
552
550
  Marionette.normalizeMethods = function(hash) {
@@ -562,29 +560,54 @@
562
560
  }, this);
563
561
  return normalizedHash;
564
562
  };
565
-
566
-
563
+
564
+ // utility method for parsing @ui. syntax strings
565
+ // into associated selector
566
+ Marionette.normalizeUIString = function(uiString, ui) {
567
+ return uiString.replace(/@ui\.[a-zA-Z_$0-9]*/g, function(r) {
568
+ return ui[r.slice(4)];
569
+ });
570
+ };
571
+
567
572
  // allows for the use of the @ui. syntax within
568
573
  // a given key for triggers and events
569
- // swaps the @ui with the associated selector
574
+ // swaps the @ui with the associated selector.
575
+ // Returns a new, non-mutated, parsed events hash.
570
576
  Marionette.normalizeUIKeys = function(hash, ui) {
571
577
  if (typeof(hash) === 'undefined') {
572
578
  return;
573
579
  }
574
-
575
- _.each(_.keys(hash), function(v) {
576
- var pattern = /@ui\.[a-zA-Z_$0-9]*/g;
577
- if (v.match(pattern)) {
578
- hash[v.replace(pattern, function(r) {
579
- return ui[r.slice(4)];
580
- })] = hash[v];
581
- delete hash[v];
580
+
581
+ hash = _.clone(hash);
582
+
583
+ _.each(_.keys(hash), function(key) {
584
+ 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;
592
+ };
593
+
594
+ // allows for the use of the @ui. syntax within
595
+ // a given value for regions
596
+ // swaps the @ui with the associated selector
597
+ Marionette.normalizeUIValues = function(hash, ui) {
598
+ if (typeof(hash) === 'undefined') {
599
+ return;
600
+ }
601
+
602
+ _.each(hash, function(val, key) {
603
+ if (_.isString(val)) {
604
+ hash[key] = Marionette.normalizeUIString(val, ui);
582
605
  }
583
606
  });
584
-
607
+
585
608
  return hash;
586
609
  };
587
-
610
+
588
611
  // Mix in methods from Underscore, for iteration, and other
589
612
  // collection related features.
590
613
  // Borrowing this code from Backbone.Collection:
@@ -594,7 +617,7 @@
594
617
  'select', 'reject', 'every', 'all', 'some', 'any', 'include',
595
618
  'contains', 'invoke', 'toArray', 'first', 'initial', 'rest',
596
619
  'last', 'without', 'isEmpty', 'pluck'];
597
-
620
+
598
621
  _.each(methods, function(method) {
599
622
  object[method] = function() {
600
623
  var list = _.values(_.result(this, listProperty));
@@ -603,7 +626,7 @@
603
626
  };
604
627
  });
605
628
  };
606
-
629
+
607
630
  // Trigger an event and/or a corresponding method name. Examples:
608
631
  //
609
632
  // `this.triggerMethod("foo")` will trigger the "foo" event and
@@ -611,48 +634,60 @@
611
634
  //
612
635
  // `this.triggerMethod("foo:bar")` will trigger the "foo:bar" event and
613
636
  // call the "onFooBar" method.
614
- Marionette.triggerMethod = (function() {
615
-
637
+ Marionette.triggerMethod = function(event) {
638
+
616
639
  // split the event name on the ":"
617
640
  var splitter = /(^|:)(\w)/gi;
618
-
641
+
619
642
  // take the event section ("section1:section2:section3")
620
643
  // and turn it in to uppercase name
621
644
  function getEventName(match, prefix, eventName) {
622
645
  return eventName.toUpperCase();
623
646
  }
624
-
625
- // actual triggerMethod implementation
626
- var triggerMethod = function(event) {
627
- // get the method name from the event name
628
- var methodName = 'on' + event.replace(splitter, getEventName);
629
- var method = this[methodName];
630
- var result;
631
-
632
- // call the onMethodName if it exists
633
- if (_.isFunction(method)) {
634
- // pass all arguments, except the event name
635
- result = method.apply(this, _.tail(arguments));
636
- }
637
-
638
- // trigger the event, if a trigger method exists
639
- if (_.isFunction(this.trigger)) {
640
- this.trigger.apply(this, arguments);
641
- }
642
-
643
- return result;
644
- };
645
-
646
- return triggerMethod;
647
- })();
648
-
647
+
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;
652
+
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
+ }
658
+
659
+ // trigger the event, if a trigger method exists
660
+ if (_.isFunction(this.trigger)) {
661
+ this.trigger.apply(this, arguments);
662
+ }
663
+
664
+ return result;
665
+ };
666
+
667
+ // triggerMethodOn invokes triggerMethod on a specific context
668
+ //
669
+ // e.g. `Marionette.triggerMethodOn(view, 'show')`
670
+ // 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
+ }
680
+
681
+ return fnc.apply(context, [event].concat(args));
682
+ };
683
+
649
684
  // DOMRefresh
650
685
  // ----------
651
686
  //
652
687
  // Monitor a view's state, and after it has been rendered and shown
653
688
  // in the DOM, trigger a "dom:refresh" event every time it is
654
689
  // re-rendered.
655
-
690
+
656
691
  Marionette.MonitorDOMRefresh = (function(documentElement) {
657
692
  // track when the view has been shown in the DOM,
658
693
  // using a Marionette.Region (or by other means of triggering "show")
@@ -660,13 +695,13 @@
660
695
  view._isShown = true;
661
696
  triggerDOMRefresh(view);
662
697
  }
663
-
698
+
664
699
  // track when the view has been rendered
665
700
  function handleRender(view) {
666
701
  view._isRendered = true;
667
702
  triggerDOMRefresh(view);
668
703
  }
669
-
704
+
670
705
  // Trigger the "dom:refresh" event and corresponding "onDomRefresh" method
671
706
  function triggerDOMRefresh(view) {
672
707
  if (view._isShown && view._isRendered && isInDOM(view)) {
@@ -675,26 +710,26 @@
675
710
  }
676
711
  }
677
712
  }
678
-
713
+
679
714
  function isInDOM(view) {
680
715
  return Backbone.$.contains(documentElement, view.el);
681
716
  }
682
-
717
+
683
718
  // Export public API
684
719
  return function(view) {
685
720
  view.listenTo(view, 'show', function() {
686
721
  handleShow(view);
687
722
  });
688
-
723
+
689
724
  view.listenTo(view, 'render', function() {
690
725
  handleRender(view);
691
726
  });
692
727
  };
693
728
  })(document.documentElement);
694
-
729
+
695
730
 
696
731
  /* jshint maxparams: 5 */
697
-
732
+
698
733
  // Marionette.bindEntityEvents & unbindEntityEvents
699
734
  // ---------------------------
700
735
  //
@@ -710,61 +745,69 @@
710
745
  // The third parameter is a hash of { "event:name": "eventHandler" }
711
746
  // configuration. Multiple handlers can be separated by a space. A
712
747
  // function can be supplied instead of a string handler name.
713
-
748
+
714
749
  (function(Marionette) {
715
750
  'use strict';
716
-
751
+
717
752
  // Bind the event to handlers specified as a string of
718
753
  // handler names on the target object
719
754
  function bindFromStrings(target, entity, evt, methods) {
720
755
  var methodNames = methods.split(/\s+/);
721
-
756
+
722
757
  _.each(methodNames, function(methodName) {
723
-
758
+
724
759
  var method = target[methodName];
725
760
  if (!method) {
726
- throwError('Method "' + methodName +
761
+ throw new Marionette.Error('Method "' + methodName +
727
762
  '" was configured as an event handler, but does not exist.');
728
763
  }
729
-
764
+
730
765
  target.listenTo(entity, evt, method);
731
766
  });
732
767
  }
733
-
768
+
734
769
  // Bind the event to a supplied callback function
735
770
  function bindToFunction(target, entity, evt, method) {
736
771
  target.listenTo(entity, evt, method);
737
772
  }
738
-
773
+
739
774
  // Bind the event to handlers specified as a string of
740
775
  // handler names on the target object
741
776
  function unbindFromStrings(target, entity, evt, methods) {
742
777
  var methodNames = methods.split(/\s+/);
743
-
778
+
744
779
  _.each(methodNames, function(methodName) {
745
780
  var method = target[methodName];
746
781
  target.stopListening(entity, evt, method);
747
782
  });
748
783
  }
749
-
784
+
750
785
  // Bind the event to a supplied callback function
751
786
  function unbindToFunction(target, entity, evt, method) {
752
787
  target.stopListening(entity, evt, method);
753
788
  }
754
-
755
-
789
+
790
+
756
791
  // generic looping function
757
792
  function iterateEvents(target, entity, bindings, functionCallback, stringCallback) {
758
793
  if (!entity || !bindings) { return; }
759
-
794
+
795
+ // type-check bindings
796
+ if (!_.isFunction(bindings) && !_.isObject(bindings)) {
797
+ throw new Marionette.Error({
798
+ message: 'Bindings must be an object or function.',
799
+ url: 'marionette.functions.html#marionettebindentityevents'
800
+ });
801
+ }
802
+
760
803
  // allow the bindings to be a function
761
804
  if (_.isFunction(bindings)) {
762
805
  bindings = bindings.call(target);
763
806
  }
764
-
807
+
765
808
  // iterate the bindings and bind them
766
809
  _.each(bindings, function(methods, evt) {
767
-
810
+
768
811
  // allow for a function as the handler,
769
812
  // or a list of event names as a string
770
813
  if (_.isFunction(methods)) {
@@ -772,34 +815,70 @@
772
815
  } else {
773
816
  stringCallback(target, entity, evt, methods);
774
817
  }
775
-
818
+
776
819
  });
777
820
  }
778
-
821
+
779
822
  // Export Public API
780
823
  Marionette.bindEntityEvents = function(target, entity, bindings) {
781
824
  iterateEvents(target, entity, bindings, bindToFunction, bindFromStrings);
782
825
  };
783
-
826
+
784
827
  Marionette.unbindEntityEvents = function(target, entity, bindings) {
785
828
  iterateEvents(target, entity, bindings, unbindToFunction, unbindFromStrings);
786
829
  };
787
-
830
+
788
831
  // Proxy `bindEntityEvents`
789
832
  Marionette.proxyBindEntityEvents = function(entity, bindings) {
790
833
  return Marionette.bindEntityEvents(this, entity, bindings);
791
834
  };
792
-
835
+
793
836
  // Proxy `unbindEntityEvents`
794
837
  Marionette.proxyUnbindEntityEvents = function(entity, bindings) {
795
838
  return Marionette.unbindEntityEvents(this, entity, bindings);
796
839
  };
797
840
  })(Marionette);
798
-
841
+
842
+
843
+ var errorProps = ['description', 'fileName', 'lineNumber', 'name', 'message', 'number'];
844
+
845
+ Marionette.Error = Marionette.extend.call(Error, {
846
+ urlRoot: 'http://marionettejs.com/docs/' + Marionette.VERSION + '/',
847
+
848
+ constructor: function(message, options) {
849
+ if (_.isObject(message)) {
850
+ options = message;
851
+ message = options.message;
852
+ } else if (!options) {
853
+ options = {};
854
+ }
855
+
856
+ var error = Error.call(this, message);
857
+ _.extend(this, _.pick(error, errorProps), _.pick(options, errorProps));
858
+
859
+ this.captureStackTrace();
860
+
861
+ if (options.url) {
862
+ this.url = this.urlRoot + options.url;
863
+ }
864
+ },
865
+
866
+ captureStackTrace: function() {
867
+ if (Error.captureStackTrace) {
868
+ Error.captureStackTrace(this, Marionette.Error);
869
+ }
870
+ },
871
+
872
+ toString: function() {
873
+ return this.name + ': ' + this.message + (this.url ? ' See: ' + this.url : '');
874
+ }
875
+ });
876
+
877
+ Marionette.Error.extend = Marionette.extend;
799
878
 
800
879
  // Callbacks
801
880
  // ---------
802
-
881
+
803
882
  // A simple way of managing a collection of callbacks
804
883
  // and executing them at a later point in time, using jQuery's
805
884
  // `Deferred` object.
@@ -807,23 +886,23 @@
807
886
  this._deferred = Marionette.Deferred();
808
887
  this._callbacks = [];
809
888
  };
810
-
889
+
811
890
  _.extend(Marionette.Callbacks.prototype, {
812
-
891
+
813
892
  // Add a callback to be executed. Callbacks added here are
814
893
  // guaranteed to execute, even if they are added after the
815
894
  // `run` method is called.
816
895
  add: function(callback, contextOverride) {
817
896
  var promise = _.result(this._deferred, 'promise');
818
-
897
+
819
898
  this._callbacks.push({cb: callback, ctx: contextOverride});
820
-
899
+
821
900
  promise.then(function(args) {
822
901
  if (contextOverride){ args.context = contextOverride; }
823
902
  callback.call(args.context, args.options);
824
903
  });
825
904
  },
826
-
905
+
827
906
  // Run all registered callbacks with the context specified.
828
907
  // Additional callbacks can be added after this has been run
829
908
  // and they will still be executed.
@@ -833,20 +912,20 @@
833
912
  context: context
834
913
  });
835
914
  },
836
-
915
+
837
916
  // Resets the list of callbacks to be run, allowing the same list
838
917
  // to be run multiple times - whenever the `run` method is called.
839
918
  reset: function() {
840
919
  var callbacks = this._callbacks;
841
920
  this._deferred = Marionette.Deferred();
842
921
  this._callbacks = [];
843
-
922
+
844
923
  _.each(callbacks, function(cb) {
845
924
  this.add(cb.cb, cb.ctx);
846
925
  }, this);
847
926
  }
848
927
  });
849
-
928
+
850
929
  // Marionette Controller
851
930
  // ---------------------
852
931
  //
@@ -855,116 +934,118 @@
855
934
  // and coordination of other objects, views, and more.
856
935
  Marionette.Controller = function(options) {
857
936
  this.options = options || {};
858
-
937
+
859
938
  if (_.isFunction(this.initialize)) {
860
939
  this.initialize(this.options);
861
940
  }
862
941
  };
863
-
942
+
864
943
  Marionette.Controller.extend = Marionette.extend;
865
-
944
+
866
945
  // Controller Methods
867
946
  // --------------
868
-
947
+
869
948
  // Ensure it can trigger events with Backbone.Events
870
949
  _.extend(Marionette.Controller.prototype, Backbone.Events, {
871
950
  destroy: function() {
872
951
  var args = slice.call(arguments);
873
952
  this.triggerMethod.apply(this, ['before:destroy'].concat(args));
874
953
  this.triggerMethod.apply(this, ['destroy'].concat(args));
875
-
954
+
876
955
  this.stopListening();
877
956
  this.off();
878
957
  return this;
879
958
  },
880
-
959
+
881
960
  // import the `triggerMethod` to trigger events with corresponding
882
961
  // methods if the method exists
883
962
  triggerMethod: Marionette.triggerMethod,
884
-
963
+
885
964
  // Proxy `getOption` to enable getting options from this or this.options by name.
886
965
  getOption: Marionette.proxyGetOption
887
-
966
+
888
967
  });
889
-
968
+
890
969
  // Marionette Object
891
970
  // ---------------------
892
971
  //
893
972
  // A Base Class that other Classes should descend from.
894
973
  // Object borrows many conventions and utilities from Backbone.
895
974
  Marionette.Object = function(options) {
896
-
897
975
  this.options = _.extend({}, _.result(this, 'options'), options);
898
-
899
- this.initialize(this.options);
976
+
977
+ this.initialize.apply(this, arguments);
900
978
  };
901
-
979
+
902
980
  Marionette.Object.extend = Marionette.extend;
903
-
981
+
904
982
  // Object Methods
905
983
  // --------------
906
-
984
+
907
985
  _.extend(Marionette.Object.prototype, {
908
-
986
+
909
987
  //this is a noop method intended to be overridden by classes that extend from this base
910
988
  initialize: function() {},
911
-
989
+
912
990
  destroy: function() {
913
991
  this.triggerMethod('before:destroy');
914
992
  this.triggerMethod('destroy');
915
993
  this.stopListening();
916
994
  },
917
-
995
+
918
996
  // Import the `triggerMethod` to trigger events with corresponding
919
997
  // methods if the method exists
920
998
  triggerMethod: Marionette.triggerMethod,
921
-
999
+
922
1000
  // Proxy `getOption` to enable getting options from this or this.options by name.
923
1001
  getOption: Marionette.proxyGetOption,
924
-
1002
+
925
1003
  // Proxy `unbindEntityEvents` to enable binding view's events from another entity.
926
1004
  bindEntityEvents: Marionette.proxyBindEntityEvents,
927
-
1005
+
928
1006
  // Proxy `unbindEntityEvents` to enable unbinding view's events from another entity.
929
1007
  unbindEntityEvents: Marionette.proxyUnbindEntityEvents
930
1008
  });
931
-
1009
+
932
1010
  // Ensure it can trigger events with Backbone.Events
933
1011
  _.extend(Marionette.Object.prototype, Backbone.Events);
934
-
1012
+
935
1013
  /* jshint maxcomplexity: 10, maxstatements: 29 */
936
-
1014
+
937
1015
  // Region
938
1016
  // ------
939
1017
  //
940
1018
  // Manage the visual regions of your composite application. See
941
1019
  // http://lostechies.com/derickbailey/2011/12/12/composite-js-apps-regions-and-region-managers/
942
-
1020
+
943
1021
  Marionette.Region = function(options) {
944
1022
  this.options = options || {};
945
1023
  this.el = this.getOption('el');
946
-
1024
+
947
1025
  // Handle when this.el is passed in as a $ wrapped element.
948
1026
  this.el = this.el instanceof Backbone.$ ? this.el[0] : this.el;
949
-
1027
+
950
1028
  if (!this.el) {
951
- throwError('An "el" must be specified for a region.', 'NoElError');
1029
+ throw new Marionette.Error({
1030
+ name: 'NoElError',
1031
+ message: 'An "el" must be specified for a region.'
1032
+ });
952
1033
  }
953
-
1034
+
954
1035
  this.$el = this.getEl(this.el);
955
-
1036
+
956
1037
  if (this.initialize) {
957
1038
  var args = slice.apply(arguments);
958
1039
  this.initialize.apply(this, args);
959
1040
  }
960
1041
  };
961
-
962
-
1042
+
1043
+
963
1044
  // Region Class methods
964
1045
  // -------------------
965
-
1046
+
966
1047
  _.extend(Marionette.Region, {
967
-
1048
+
968
1049
  // Build an instance of a region by passing in a configuration object
969
1050
  // and a default region class to use if none is specified in the config.
970
1051
  //
@@ -983,24 +1064,26 @@
983
1064
  if (_.isString(regionConfig)) {
984
1065
  return this._buildRegionFromSelector(regionConfig, DefaultRegionClass);
985
1066
  }
986
-
1067
+
987
1068
  if (regionConfig.selector || regionConfig.el || regionConfig.regionClass) {
988
1069
  return this._buildRegionFromObject(regionConfig, DefaultRegionClass);
989
1070
  }
990
-
1071
+
991
1072
  if (_.isFunction(regionConfig)) {
992
1073
  return this._buildRegionFromRegionClass(regionConfig);
993
1074
  }
994
-
995
- throwError('Improper region configuration type. Please refer ' +
996
- 'to http://marionettejs.com/docs/marionette.region.html#region-configuration-types');
1075
+
1076
+ throw new Marionette.Error({
1077
+ message: 'Improper region configuration type.',
1078
+ url: 'marionette.region.html#region-configuration-types'
1079
+ });
997
1080
  },
998
-
1081
+
999
1082
  // Build the region from a string selector like '#foo-region'
1000
1083
  _buildRegionFromSelector: function(selector, DefaultRegionClass) {
1001
1084
  return new DefaultRegionClass({ el: selector });
1002
1085
  },
1003
-
1086
+
1004
1087
  // Build the region from a configuration object
1005
1088
  // ```js
1006
1089
  // { selector: '#foo', regionClass: FooRegion }
@@ -1008,13 +1091,13 @@
1008
1091
  _buildRegionFromObject: function(regionConfig, DefaultRegionClass) {
1009
1092
  var RegionClass = regionConfig.regionClass || DefaultRegionClass;
1010
1093
  var options = _.omit(regionConfig, 'selector', 'regionClass');
1011
-
1094
+
1012
1095
  if (regionConfig.selector && !options.el) {
1013
1096
  options.el = regionConfig.selector;
1014
1097
  }
1015
-
1098
+
1016
1099
  var region = new RegionClass(options);
1017
-
1100
+
1018
1101
  // override the `getEl` function if we have a parentEl
1019
1102
  // this must be overridden to ensure the selector is found
1020
1103
  // on the first use of the region. if we try to assign the
@@ -1033,22 +1116,22 @@
1033
1116
  return parentEl.find(el);
1034
1117
  };
1035
1118
  }
1036
-
1119
+
1037
1120
  return region;
1038
1121
  },
1039
-
1122
+
1040
1123
  // Build the region directly from a given `RegionClass`
1041
1124
  _buildRegionFromRegionClass: function(RegionClass) {
1042
1125
  return new RegionClass();
1043
1126
  }
1044
-
1127
+
1045
1128
  });
1046
-
1129
+
1047
1130
  // Region Instance Methods
1048
1131
  // -----------------------
1049
-
1132
+
1050
1133
  _.extend(Marionette.Region.prototype, Backbone.Events, {
1051
-
1134
+
1052
1135
  // Displays a backbone view instance inside of the region.
1053
1136
  // Handles calling the `render` method for you. Reads content
1054
1137
  // directly from the `el` attribute. Also calls an optional
@@ -1058,30 +1141,34 @@
1058
1141
  // the old view being destroyed on show.
1059
1142
  // The `forceShow` option can be used to force a view to be
1060
1143
  // re-rendered if it's already shown in the region.
1061
-
1144
+
1062
1145
  show: function(view, options){
1063
1146
  this._ensureElement();
1064
-
1147
+
1065
1148
  var showOptions = options || {};
1066
1149
  var isDifferentView = view !== this.currentView;
1067
1150
  var preventDestroy = !!showOptions.preventDestroy;
1068
1151
  var forceShow = !!showOptions.forceShow;
1069
-
1152
+
1070
1153
  // we are only changing the view if there is a view to change to begin with
1071
1154
  var isChangingView = !!this.currentView;
1072
-
1155
+
1073
1156
  // only destroy the view if we don't want to preventDestroy and the view is different
1074
1157
  var _shouldDestroyView = !preventDestroy && isDifferentView;
1075
-
1158
+
1159
+ // show the view if the view is different or if you want to re-show the view
1160
+ var _shouldShowView = isDifferentView || forceShow;
1161
+
1162
+ if (isChangingView) {
1163
+ this.triggerMethod('before:swapOut', this.currentView);
1164
+ }
1165
+
1076
1166
  if (_shouldDestroyView) {
1077
1167
  this.empty();
1078
1168
  }
1079
-
1080
- // show the view if the view is different or if you want to re-show the view
1081
- var _shouldShowView = isDifferentView || forceShow;
1082
-
1169
+
1083
1170
  if (_shouldShowView) {
1084
-
1171
+
1085
1172
  // We need to listen for if a view is destroyed
1086
1173
  // in a way other than through the region.
1087
1174
  // If this happens we need to remove the reference
@@ -1089,57 +1176,51 @@
1089
1176
  // we can not reuse it.
1090
1177
  view.once('destroy', _.bind(this.empty, this));
1091
1178
  view.render();
1092
-
1179
+
1093
1180
  if (isChangingView) {
1094
1181
  this.triggerMethod('before:swap', view);
1095
1182
  }
1096
-
1183
+
1097
1184
  this.triggerMethod('before:show', view);
1098
-
1099
- if (_.isFunction(view.triggerMethod)) {
1100
- view.triggerMethod('before:show');
1101
- } else {
1102
- this.triggerMethod.call(view, 'before:show');
1185
+ Marionette.triggerMethodOn(view, 'before:show');
1186
+
1187
+ if (isChangingView) {
1188
+ this.triggerMethod('swapOut', this.currentView);
1103
1189
  }
1104
-
1190
+
1105
1191
  this.attachHtml(view);
1106
1192
  this.currentView = view;
1107
-
1193
+
1108
1194
  if (isChangingView) {
1109
1195
  this.triggerMethod('swap', view);
1110
1196
  }
1111
-
1197
+
1112
1198
  this.triggerMethod('show', view);
1113
-
1114
- if (_.isFunction(view.triggerMethod)) {
1115
- view.triggerMethod('show');
1116
- } else {
1117
- this.triggerMethod.call(view, 'show');
1118
- }
1119
-
1199
+ Marionette.triggerMethodOn(view, 'show');
1200
+
1120
1201
  return this;
1121
1202
  }
1122
-
1203
+
1123
1204
  return this;
1124
1205
  },
1125
-
1206
+
1126
1207
  _ensureElement: function(){
1127
1208
  if (!_.isObject(this.el)) {
1128
1209
  this.$el = this.getEl(this.el);
1129
1210
  this.el = this.$el[0];
1130
1211
  }
1131
-
1212
+
1132
1213
  if (!this.$el || this.$el.length === 0) {
1133
- throwError('An "el" ' + this.$el.selector + ' must exist in DOM');
1214
+ throw new Marionette.Error('An "el" ' + this.$el.selector + ' must exist in DOM');
1134
1215
  }
1135
1216
  },
1136
-
1217
+
1137
1218
  // Override this method to change how the region finds the
1138
1219
  // DOM element that it manages. Return a jQuery selector object.
1139
1220
  getEl: function(el) {
1140
1221
  return Backbone.$(el);
1141
1222
  },
1142
-
1223
+
1143
1224
  // Override this method to change how the new view is
1144
1225
  // appended to the `$el` that the region is managing
1145
1226
  attachHtml: function(view) {
@@ -1147,37 +1228,37 @@
1147
1228
  this.el.innerHTML='';
1148
1229
  this.el.appendChild(view.el);
1149
1230
  },
1150
-
1231
+
1151
1232
  // Destroy the current view, if there is one. If there is no
1152
1233
  // current view, it does nothing and returns immediately.
1153
1234
  empty: function() {
1154
1235
  var view = this.currentView;
1155
-
1236
+
1156
1237
  // If there is no view in the region
1157
1238
  // we should not remove anything
1158
1239
  if (!view) { return; }
1159
-
1240
+
1160
1241
  this.triggerMethod('before:empty', view);
1161
1242
  this._destroyView();
1162
1243
  this.triggerMethod('empty', view);
1163
-
1244
+
1164
1245
  // Remove region pointer to the currentView
1165
1246
  delete this.currentView;
1166
1247
  return this;
1167
1248
  },
1168
-
1249
+
1169
1250
  // call 'destroy' or 'remove', depending on which is found
1170
1251
  // on the view (if showing a raw Backbone view or a Marionette View)
1171
1252
  _destroyView: function() {
1172
1253
  var view = this.currentView;
1173
-
1254
+
1174
1255
  if (view.destroy && !view.isDestroyed) {
1175
1256
  view.destroy();
1176
1257
  } else if (view.remove) {
1177
1258
  view.remove();
1178
1259
  }
1179
1260
  },
1180
-
1261
+
1181
1262
  // Attach an existing view to the region. This
1182
1263
  // will not call `render` or `onShow` for the new view,
1183
1264
  // and will not replace the current HTML for the `el`
@@ -1186,52 +1267,52 @@
1186
1267
  this.currentView = view;
1187
1268
  return this;
1188
1269
  },
1189
-
1270
+
1190
1271
  // Checks whether a view is currently present within
1191
1272
  // the region. Returns `true` if there is and `false` if
1192
1273
  // no view is present.
1193
1274
  hasView: function() {
1194
1275
  return !!this.currentView;
1195
1276
  },
1196
-
1277
+
1197
1278
  // Reset the region by destroying any existing view and
1198
1279
  // clearing out the cached `$el`. The next time a view
1199
1280
  // is shown via this region, the region will re-query the
1200
1281
  // DOM for the region's `el`.
1201
1282
  reset: function() {
1202
1283
  this.empty();
1203
-
1284
+
1204
1285
  if (this.$el) {
1205
1286
  this.el = this.$el.selector;
1206
1287
  }
1207
-
1288
+
1208
1289
  delete this.$el;
1209
1290
  return this;
1210
1291
  },
1211
-
1292
+
1212
1293
  // Proxy `getOption` to enable getting options from this or this.options by name.
1213
1294
  getOption: Marionette.proxyGetOption,
1214
-
1295
+
1215
1296
  // import the `triggerMethod` to trigger events with corresponding
1216
1297
  // methods if the method exists
1217
1298
  triggerMethod: Marionette.triggerMethod
1218
1299
  });
1219
-
1300
+
1220
1301
  // Copy the `extend` function used by Backbone's classes
1221
1302
  Marionette.Region.extend = Marionette.extend;
1222
-
1303
+
1223
1304
  // Marionette.RegionManager
1224
1305
  // ------------------------
1225
1306
  //
1226
1307
  // Manage one or more related `Marionette.Region` objects.
1227
1308
  Marionette.RegionManager = (function(Marionette) {
1228
-
1309
+
1229
1310
  var RegionManager = Marionette.Controller.extend({
1230
1311
  constructor: function(options) {
1231
1312
  this._regions = {};
1232
1313
  Marionette.Controller.call(this, options);
1233
1314
  },
1234
-
1315
+
1235
1316
  // Add multiple regions using an object literal or a
1236
1317
  // function that returns an object literal, where
1237
1318
  // each key becomes the region name, and each value is
@@ -1240,69 +1321,63 @@
1240
1321
  if (_.isFunction(regionDefinitions)) {
1241
1322
  regionDefinitions = regionDefinitions.apply(this, arguments);
1242
1323
  }
1243
-
1324
+
1244
1325
  var regions = {};
1245
-
1326
+
1246
1327
  _.each(regionDefinitions, function(definition, name) {
1247
1328
  if (_.isString(definition)) {
1248
1329
  definition = {selector: definition};
1249
1330
  }
1250
-
1331
+
1251
1332
  if (definition.selector) {
1252
1333
  definition = _.defaults({}, definition, defaults);
1253
1334
  }
1254
-
1335
+
1255
1336
  var region = this.addRegion(name, definition);
1256
1337
  regions[name] = region;
1257
1338
  }, this);
1258
-
1339
+
1259
1340
  return regions;
1260
1341
  },
1261
-
1342
+
1262
1343
  // Add an individual region to the region manager,
1263
1344
  // and return the region instance
1264
1345
  addRegion: function(name, definition) {
1265
1346
  var region;
1266
-
1267
- var isObject = _.isObject(definition);
1268
- var isString = _.isString(definition);
1269
- var hasSelector = !!definition.selector;
1270
-
1271
- if (isString || (isObject && hasSelector)) {
1272
- region = Marionette.Region.buildRegion(definition, Marionette.Region);
1273
- } else if (_.isFunction(definition)) {
1274
- region = Marionette.Region.buildRegion(definition, Marionette.Region);
1275
- } else {
1347
+
1348
+ if (definition instanceof Marionette.Region) {
1276
1349
  region = definition;
1350
+ } else {
1351
+ region = Marionette.Region.buildRegion(definition, Marionette.Region);
1277
1352
  }
1278
-
1353
+
1279
1354
  this.triggerMethod('before:add:region', name, region);
1280
-
1355
+
1281
1356
  this._store(name, region);
1282
-
1357
+
1283
1358
  this.triggerMethod('add:region', name, region);
1284
1359
  return region;
1285
1360
  },
1286
-
1361
+
1287
1362
  // Get a region by name
1288
1363
  get: function(name) {
1289
1364
  return this._regions[name];
1290
1365
  },
1291
-
1366
+
1292
1367
  // Gets all the regions contained within
1293
1368
  // the `regionManager` instance.
1294
1369
  getRegions: function(){
1295
1370
  return _.clone(this._regions);
1296
1371
  },
1297
-
1372
+
1298
1373
  // Remove a region by name
1299
1374
  removeRegion: function(name) {
1300
1375
  var region = this._regions[name];
1301
1376
  this._remove(name, region);
1302
-
1377
+
1303
1378
  return region;
1304
1379
  },
1305
-
1380
+
1306
1381
  // Empty all regions in the region manager, and
1307
1382
  // remove them
1308
1383
  removeRegions: function() {
@@ -1310,10 +1385,10 @@
1310
1385
  _.each(this._regions, function(region, name) {
1311
1386
  this._remove(name, region);
1312
1387
  }, this);
1313
-
1388
+
1314
1389
  return regions;
1315
1390
  },
1316
-
1391
+
1317
1392
  // Empty all regions in the region manager, but
1318
1393
  // leave them attached
1319
1394
  emptyRegions: function() {
@@ -1321,23 +1396,23 @@
1321
1396
  _.each(regions, function(region) {
1322
1397
  region.empty();
1323
1398
  }, this);
1324
-
1399
+
1325
1400
  return regions;
1326
1401
  },
1327
-
1402
+
1328
1403
  // Destroy all regions and shut down the region
1329
1404
  // manager entirely
1330
1405
  destroy: function() {
1331
1406
  this.removeRegions();
1332
1407
  return Marionette.Controller.prototype.destroy.apply(this, arguments);
1333
1408
  },
1334
-
1409
+
1335
1410
  // internal method to store regions
1336
1411
  _store: function(name, region) {
1337
1412
  this._regions[name] = region;
1338
1413
  this._setLength();
1339
1414
  },
1340
-
1415
+
1341
1416
  // internal method to remove a region
1342
1417
  _remove: function(name, region) {
1343
1418
  this.triggerMethod('before:remove:region', name, region);
@@ -1347,49 +1422,49 @@
1347
1422
  this._setLength();
1348
1423
  this.triggerMethod('remove:region', name, region);
1349
1424
  },
1350
-
1425
+
1351
1426
  // set the number of regions current held
1352
1427
  _setLength: function() {
1353
1428
  this.length = _.size(this._regions);
1354
1429
  }
1355
-
1430
+
1356
1431
  });
1357
-
1432
+
1358
1433
  Marionette.actAsCollection(RegionManager.prototype, '_regions');
1359
-
1434
+
1360
1435
  return RegionManager;
1361
1436
  })(Marionette);
1362
-
1437
+
1363
1438
 
1364
1439
  // Template Cache
1365
1440
  // --------------
1366
-
1441
+
1367
1442
  // Manage templates stored in `<script>` blocks,
1368
1443
  // caching them for faster access.
1369
1444
  Marionette.TemplateCache = function(templateId) {
1370
1445
  this.templateId = templateId;
1371
1446
  };
1372
-
1447
+
1373
1448
  // TemplateCache object-level methods. Manage the template
1374
1449
  // caches from these method calls instead of creating
1375
1450
  // your own TemplateCache instances
1376
1451
  _.extend(Marionette.TemplateCache, {
1377
1452
  templateCaches: {},
1378
-
1453
+
1379
1454
  // Get the specified template by id. Either
1380
1455
  // retrieves the cached version, or loads it
1381
1456
  // from the DOM.
1382
1457
  get: function(templateId) {
1383
1458
  var cachedTemplate = this.templateCaches[templateId];
1384
-
1459
+
1385
1460
  if (!cachedTemplate) {
1386
1461
  cachedTemplate = new Marionette.TemplateCache(templateId);
1387
1462
  this.templateCaches[templateId] = cachedTemplate;
1388
1463
  }
1389
-
1464
+
1390
1465
  return cachedTemplate.load();
1391
1466
  },
1392
-
1467
+
1393
1468
  // Clear templates from the cache. If no arguments
1394
1469
  // are specified, clears all templates:
1395
1470
  // `clear()`
@@ -1401,7 +1476,7 @@
1401
1476
  var i;
1402
1477
  var args = slice.call(arguments);
1403
1478
  var length = args.length;
1404
-
1479
+
1405
1480
  if (length > 0) {
1406
1481
  for (i = 0; i < length; i++) {
1407
1482
  delete this.templateCaches[args[i]];
@@ -1411,26 +1486,26 @@
1411
1486
  }
1412
1487
  }
1413
1488
  });
1414
-
1489
+
1415
1490
  // TemplateCache instance methods, allowing each
1416
1491
  // template cache object to manage its own state
1417
1492
  // and know whether or not it has been loaded
1418
1493
  _.extend(Marionette.TemplateCache.prototype, {
1419
-
1494
+
1420
1495
  // Internal method to load the template
1421
1496
  load: function() {
1422
1497
  // Guard clause to prevent loading this template more than once
1423
1498
  if (this.compiledTemplate) {
1424
1499
  return this.compiledTemplate;
1425
1500
  }
1426
-
1501
+
1427
1502
  // Load the template and compile it
1428
1503
  var template = this.loadTemplate(this.templateId);
1429
1504
  this.compiledTemplate = this.compileTemplate(template);
1430
-
1505
+
1431
1506
  return this.compiledTemplate;
1432
1507
  },
1433
-
1508
+
1434
1509
  // Load a template from the DOM, by default. Override
1435
1510
  // this method to provide your own template retrieval
1436
1511
  // For asynchronous loading with AMD/RequireJS, consider
@@ -1438,14 +1513,17 @@
1438
1513
  // https://github.com/marionettejs/backbone.marionette/wiki/Using-marionette-with-requirejs
1439
1514
  loadTemplate: function(templateId) {
1440
1515
  var template = Backbone.$(templateId).html();
1441
-
1516
+
1442
1517
  if (!template || template.length === 0) {
1443
- throwError('Could not find template: "' + templateId + '"', 'NoTemplateError');
1518
+ throw new Marionette.Error({
1519
+ name: 'NoTemplateError',
1520
+ message: 'Could not find template: "' + templateId + '"'
1521
+ });
1444
1522
  }
1445
-
1523
+
1446
1524
  return template;
1447
1525
  },
1448
-
1526
+
1449
1527
  // Pre-compile the template before caching it. Override
1450
1528
  // this method if you do not need to pre-compile a template
1451
1529
  // (JST / RequireJS for example) or if you want to change
@@ -1454,64 +1532,62 @@
1454
1532
  return _.template(rawTemplate);
1455
1533
  }
1456
1534
  });
1457
-
1535
+
1458
1536
  // Renderer
1459
1537
  // --------
1460
-
1538
+
1461
1539
  // Render a template with data by passing in the template
1462
1540
  // selector and the data to render.
1463
1541
  Marionette.Renderer = {
1464
-
1542
+
1465
1543
  // Render a template with data. The `template` parameter is
1466
1544
  // passed to the `TemplateCache` object to retrieve the
1467
1545
  // template function. Override this method to provide your own
1468
1546
  // custom rendering and template handling for all of Marionette.
1469
1547
  render: function(template, data) {
1470
1548
  if (!template) {
1471
- throwError('Cannot render the template since its false, null or undefined.',
1472
- 'TemplateNotFoundError');
1549
+ throw new Marionette.Error({
1550
+ name: 'TemplateNotFoundError',
1551
+ message: 'Cannot render the template since its false, null or undefined.'
1552
+ });
1473
1553
  }
1474
-
1554
+
1475
1555
  var templateFunc;
1476
1556
  if (typeof template === 'function') {
1477
1557
  templateFunc = template;
1478
1558
  } else {
1479
1559
  templateFunc = Marionette.TemplateCache.get(template);
1480
1560
  }
1481
-
1561
+
1482
1562
  return templateFunc(data);
1483
1563
  }
1484
1564
  };
1485
-
1565
+
1486
1566
 
1487
1567
  /* jshint maxlen: 114, nonew: false */
1488
1568
  // Marionette.View
1489
1569
  // ---------------
1490
-
1570
+
1491
1571
  // The core view class that other Marionette views extend from.
1492
1572
  Marionette.View = Backbone.View.extend({
1493
-
1573
+
1494
1574
  constructor: function(options) {
1495
1575
  _.bindAll(this, 'render');
1496
-
1576
+
1497
1577
  // this exposes view options to the view initializer
1498
1578
  // this is a backfill since backbone removed the assignment
1499
1579
  // of this.options
1500
1580
  // at some point however this may be removed
1501
1581
  this.options = _.extend({}, _.result(this, 'options'), _.isFunction(options) ? options.call(this) : options);
1502
- // parses out the @ui DSL for events
1503
- this.events = this.normalizeUIKeys(_.result(this, 'events'));
1504
-
1505
- if (_.isObject(this.behaviors)) {
1506
- new Marionette.Behaviors(this);
1507
- }
1508
-
1582
+
1583
+ this._behaviors = Marionette.Behaviors(this);
1584
+
1509
1585
  Backbone.View.apply(this, arguments);
1510
-
1586
+
1511
1587
  Marionette.MonitorDOMRefresh(this);
1512
1588
  this.listenTo(this, 'show', this.onShowCalled);
1513
1589
  },
1514
-
1590
+
1515
1591
  // Get the template for this view
1516
1592
  // instance. You can set a `template` attribute in the view
1517
1593
  // definition or pass a `template: "whatever"` parameter in
@@ -1519,13 +1595,13 @@
1519
1595
  getTemplate: function() {
1520
1596
  return this.getOption('template');
1521
1597
  },
1522
-
1598
+
1523
1599
  // Serialize a model by returning its attributes. Clones
1524
1600
  // the attributes to allow modification.
1525
1601
  serializeModel: function(model){
1526
1602
  return model.toJSON.apply(model, slice.call(arguments, 1));
1527
1603
  },
1528
-
1604
+
1529
1605
  // Mix in template helper methods. Looks for a
1530
1606
  // `templateHelpers` attribute, which can either be an
1531
1607
  // object literal, or a function that returns an object
@@ -1539,208 +1615,275 @@
1539
1615
  }
1540
1616
  return _.extend(target, templateHelpers);
1541
1617
  },
1542
-
1543
-
1618
+
1619
+ // normalize the keys of passed hash with the views `ui` selectors.
1620
+ // `{"@ui.foo": "bar"}`
1544
1621
  normalizeUIKeys: function(hash) {
1545
1622
  var ui = _.result(this, 'ui');
1546
1623
  var uiBindings = _.result(this, '_uiBindings');
1547
1624
  return Marionette.normalizeUIKeys(hash, uiBindings || ui);
1548
1625
  },
1549
-
1626
+
1627
+ // normalize the values of passed hash with the views `ui` selectors.
1628
+ // `{foo: "@ui.bar"}`
1629
+ normalizeUIValues: function(hash) {
1630
+ var ui = _.result(this, 'ui');
1631
+ var uiBindings = _.result(this, '_uiBindings');
1632
+ return Marionette.normalizeUIValues(hash, uiBindings || ui);
1633
+ },
1634
+
1550
1635
  // Configure `triggers` to forward DOM events to view
1551
1636
  // events. `triggers: {"click .foo": "do:foo"}`
1552
1637
  configureTriggers: function() {
1553
1638
  if (!this.triggers) { return; }
1554
-
1639
+
1555
1640
  var triggerEvents = {};
1556
-
1641
+
1557
1642
  // Allow `triggers` to be configured as a function
1558
1643
  var triggers = this.normalizeUIKeys(_.result(this, 'triggers'));
1559
-
1644
+
1560
1645
  // Configure the triggers, prevent default
1561
1646
  // action and stop propagation of DOM events
1562
1647
  _.each(triggers, function(value, key) {
1563
-
1564
- var hasOptions = _.isObject(value);
1565
- var eventName = hasOptions ? value.event : value;
1566
-
1567
- // build the event handler function for the DOM event
1568
- triggerEvents[key] = function(e) {
1569
-
1570
- // stop the event in its tracks
1571
- if (e) {
1572
- var prevent = e.preventDefault;
1573
- var stop = e.stopPropagation;
1574
-
1575
- var shouldPrevent = hasOptions ? value.preventDefault : prevent;
1576
- var shouldStop = hasOptions ? value.stopPropagation : stop;
1577
-
1578
- if (shouldPrevent && prevent) { prevent.apply(e); }
1579
- if (shouldStop && stop) { stop.apply(e); }
1580
- }
1581
-
1582
- // build the args for the event
1583
- var args = {
1584
- view: this,
1585
- model: this.model,
1586
- collection: this.collection
1587
- };
1588
-
1589
- // trigger the event
1590
- this.triggerMethod(eventName, args);
1591
- };
1592
-
1648
+ triggerEvents[key] = this._buildViewTrigger(value);
1593
1649
  }, this);
1594
-
1650
+
1595
1651
  return triggerEvents;
1596
1652
  },
1597
-
1653
+
1598
1654
  // Overriding Backbone.View's delegateEvents to handle
1599
1655
  // the `triggers`, `modelEvents`, and `collectionEvents` configuration
1600
1656
  delegateEvents: function(events) {
1601
1657
  this._delegateDOMEvents(events);
1602
1658
  this.bindEntityEvents(this.model, this.getOption('modelEvents'));
1603
1659
  this.bindEntityEvents(this.collection, this.getOption('collectionEvents'));
1660
+
1661
+ _.each(this._behaviors, function(behavior) {
1662
+ behavior.bindEntityEvents(this.model, behavior.getOption('modelEvents'));
1663
+ behavior.bindEntityEvents(this.collection, behavior.getOption('collectionEvents'));
1664
+ }, this);
1665
+
1604
1666
  return this;
1605
1667
  },
1606
-
1668
+
1607
1669
  // internal method to delegate DOM events and triggers
1608
- _delegateDOMEvents: function(events) {
1609
- events = events || this.events;
1670
+ _delegateDOMEvents: function(eventsArg) {
1671
+ var events = eventsArg || this.events;
1610
1672
  if (_.isFunction(events)) { events = events.call(this); }
1611
-
1673
+
1612
1674
  // normalize ui keys
1613
1675
  events = this.normalizeUIKeys(events);
1614
-
1676
+ if(_.isUndefined(eventsArg)) {this.events = events;}
1677
+
1615
1678
  var combinedEvents = {};
1616
-
1679
+
1617
1680
  // look up if this view has behavior events
1618
1681
  var behaviorEvents = _.result(this, 'behaviorEvents') || {};
1619
1682
  var triggers = this.configureTriggers();
1620
-
1683
+ var behaviorTriggers = _.result(this, 'behaviorTriggers') || {};
1684
+
1621
1685
  // behavior events will be overriden by view events and or triggers
1622
- _.extend(combinedEvents, behaviorEvents, events, triggers);
1623
-
1686
+ _.extend(combinedEvents, behaviorEvents, events, triggers, behaviorTriggers);
1687
+
1624
1688
  Backbone.View.prototype.delegateEvents.call(this, combinedEvents);
1625
1689
  },
1626
-
1690
+
1627
1691
  // Overriding Backbone.View's undelegateEvents to handle unbinding
1628
1692
  // the `triggers`, `modelEvents`, and `collectionEvents` config
1629
1693
  undelegateEvents: function() {
1630
1694
  var args = slice.call(arguments);
1631
1695
  Backbone.View.prototype.undelegateEvents.apply(this, args);
1696
+
1632
1697
  this.unbindEntityEvents(this.model, this.getOption('modelEvents'));
1633
1698
  this.unbindEntityEvents(this.collection, this.getOption('collectionEvents'));
1699
+
1700
+ _.each(this._behaviors, function(behavior) {
1701
+ behavior.unbindEntityEvents(this.model, behavior.getOption('modelEvents'));
1702
+ behavior.unbindEntityEvents(this.collection, behavior.getOption('collectionEvents'));
1703
+ }, this);
1704
+
1634
1705
  return this;
1635
1706
  },
1636
-
1707
+
1637
1708
  // Internal method, handles the `show` event.
1638
1709
  onShowCalled: function() {},
1639
-
1710
+
1640
1711
  // Internal helper method to verify whether the view hasn't been destroyed
1641
1712
  _ensureViewIsIntact: function() {
1642
1713
  if (this.isDestroyed) {
1643
- var err = new Error('Cannot use a view thats already been destroyed.');
1644
- err.name = 'ViewDestroyedError';
1645
- throw err;
1714
+ throw new Marionette.Error({
1715
+ name: 'ViewDestroyedError',
1716
+ message: 'View (cid: "' + this.cid + '") has already been destroyed and cannot be used.'
1717
+ });
1646
1718
  }
1647
1719
  },
1648
-
1720
+
1649
1721
  // Default `destroy` implementation, for removing a view from the
1650
1722
  // DOM and unbinding it. Regions will call this method
1651
1723
  // for you. You can specify an `onDestroy` method in your view to
1652
1724
  // add custom code that is called after the view is destroyed.
1653
1725
  destroy: function() {
1654
1726
  if (this.isDestroyed) { return; }
1655
-
1727
+
1656
1728
  var args = slice.call(arguments);
1657
-
1729
+
1658
1730
  this.triggerMethod.apply(this, ['before:destroy'].concat(args));
1659
-
1731
+
1660
1732
  // mark as destroyed before doing the actual destroy, to
1661
1733
  // prevent infinite loops within "destroy" event handlers
1662
1734
  // that are trying to destroy other views
1663
1735
  this.isDestroyed = true;
1664
1736
  this.triggerMethod.apply(this, ['destroy'].concat(args));
1665
-
1737
+
1666
1738
  // unbind UI elements
1667
1739
  this.unbindUIElements();
1668
-
1740
+
1669
1741
  // remove the view from the DOM
1670
1742
  this.remove();
1743
+
1744
+ // Call destroy on each behavior after
1745
+ // destroying the view.
1746
+ // This unbinds event listeners
1747
+ // that behaviors have registered for.
1748
+ _.invoke(this._behaviors, 'destroy', args);
1749
+
1671
1750
  return this;
1672
1751
  },
1673
-
1752
+
1753
+ bindUIElements: function() {
1754
+ this._bindUIElements();
1755
+ _.invoke(this._behaviors, this._bindUIElements);
1756
+ },
1757
+
1674
1758
  // This method binds the elements specified in the "ui" hash inside the view's code with
1675
1759
  // the associated jQuery selectors.
1676
- bindUIElements: function() {
1760
+ _bindUIElements: function() {
1677
1761
  if (!this.ui) { return; }
1678
-
1762
+
1679
1763
  // store the ui hash in _uiBindings so they can be reset later
1680
1764
  // and so re-rendering the view will be able to find the bindings
1681
1765
  if (!this._uiBindings) {
1682
1766
  this._uiBindings = this.ui;
1683
1767
  }
1684
-
1768
+
1685
1769
  // get the bindings result, as a function or otherwise
1686
1770
  var bindings = _.result(this, '_uiBindings');
1687
-
1771
+
1688
1772
  // empty the ui so we don't have anything to start with
1689
1773
  this.ui = {};
1690
-
1774
+
1691
1775
  // bind each of the selectors
1692
1776
  _.each(_.keys(bindings), function(key) {
1693
1777
  var selector = bindings[key];
1694
1778
  this.ui[key] = this.$(selector);
1695
1779
  }, this);
1696
1780
  },
1697
-
1781
+
1698
1782
  // This method unbinds the elements specified in the "ui" hash
1699
1783
  unbindUIElements: function() {
1784
+ this._unbindUIElements();
1785
+ _.invoke(this._behaviors, this._unbindUIElements);
1786
+ },
1787
+
1788
+ _unbindUIElements: function() {
1700
1789
  if (!this.ui || !this._uiBindings) { return; }
1701
-
1790
+
1702
1791
  // delete all of the existing ui bindings
1703
1792
  _.each(this.ui, function($el, name) {
1704
1793
  delete this.ui[name];
1705
1794
  }, this);
1706
-
1795
+
1707
1796
  // reset the ui element to the original bindings configuration
1708
1797
  this.ui = this._uiBindings;
1709
1798
  delete this._uiBindings;
1710
1799
  },
1711
-
1800
+
1801
+ // Internal method to create an event handler for a given `triggerDef` like
1802
+ // 'click:foo'
1803
+ _buildViewTrigger: function(triggerDef) {
1804
+ var hasOptions = _.isObject(triggerDef);
1805
+
1806
+ var options = _.defaults({}, (hasOptions ? triggerDef : {}), {
1807
+ preventDefault: true,
1808
+ stopPropagation: true
1809
+ });
1810
+
1811
+ var eventName = hasOptions ? options.event : triggerDef;
1812
+
1813
+ return function(e) {
1814
+ if (e) {
1815
+ if (e.preventDefault && options.preventDefault) {
1816
+ e.preventDefault();
1817
+ }
1818
+
1819
+ if (e.stopPropagation && options.stopPropagation) {
1820
+ e.stopPropagation();
1821
+ }
1822
+ }
1823
+
1824
+ var args = {
1825
+ view: this,
1826
+ model: this.model,
1827
+ collection: this.collection
1828
+ };
1829
+
1830
+ this.triggerMethod(eventName, args);
1831
+ };
1832
+ },
1833
+
1834
+ setElement: function() {
1835
+ var ret = Backbone.View.prototype.setElement.apply(this, arguments);
1836
+
1837
+ // proxy behavior $el to the view's $el.
1838
+ // This is needed because a view's $el proxy
1839
+ // is not set until after setElement is called.
1840
+ _.invoke(this._behaviors, 'proxyViewProperties', this);
1841
+
1842
+ return ret;
1843
+ },
1844
+
1712
1845
  // import the `triggerMethod` to trigger events with corresponding
1713
1846
  // methods if the method exists
1714
- triggerMethod: Marionette.triggerMethod,
1715
-
1847
+ triggerMethod: function() {
1848
+ var args = arguments;
1849
+ var triggerMethod = Marionette.triggerMethod;
1850
+
1851
+ var ret = triggerMethod.apply(this, args);
1852
+ _.each(this._behaviors, function(b) {
1853
+ triggerMethod.apply(b, args);
1854
+ });
1855
+
1856
+ return ret;
1857
+ },
1858
+
1716
1859
  // Imports the "normalizeMethods" to transform hashes of
1717
1860
  // events=>function references/names to a hash of events=>function references
1718
1861
  normalizeMethods: Marionette.normalizeMethods,
1719
-
1862
+
1720
1863
  // Proxy `getOption` to enable getting options from this or this.options by name.
1721
1864
  getOption: Marionette.proxyGetOption,
1722
-
1865
+
1723
1866
  // Proxy `unbindEntityEvents` to enable binding view's events from another entity.
1724
1867
  bindEntityEvents: Marionette.proxyBindEntityEvents,
1725
-
1868
+
1726
1869
  // Proxy `unbindEntityEvents` to enable unbinding view's events from another entity.
1727
1870
  unbindEntityEvents: Marionette.proxyUnbindEntityEvents
1728
1871
  });
1729
-
1872
+
1730
1873
  // Item View
1731
1874
  // ---------
1732
-
1875
+
1733
1876
  // A single item view implementation that contains code for rendering
1734
1877
  // with underscore.js templates, serializing the view's model or collection,
1735
1878
  // and calling several methods on extended views, such as `onRender`.
1736
1879
  Marionette.ItemView = Marionette.View.extend({
1737
-
1880
+
1738
1881
  // Setting up the inheritance chain which allows changes to
1739
1882
  // Marionette.View.prototype.constructor which allows overriding
1740
1883
  constructor: function() {
1741
1884
  Marionette.View.apply(this, arguments);
1742
1885
  },
1743
-
1886
+
1744
1887
  // Serialize the model or collection for the view. If a model is
1745
1888
  // found, the view's `serializeModel` is called. If a collection is found,
1746
1889
  // each model in the collection is serialized by calling
@@ -1750,22 +1893,22 @@
1750
1893
  // to provide custom serialization for your view's data.
1751
1894
  serializeData: function(){
1752
1895
  var data = {};
1753
-
1896
+
1754
1897
  if (this.model) {
1755
1898
  data = _.partial(this.serializeModel, this.model).apply(this, arguments);
1756
1899
  }
1757
1900
  else if (this.collection) {
1758
1901
  data = { items: _.partial(this.serializeCollection, this.collection).apply(this, arguments) };
1759
1902
  }
1760
-
1903
+
1761
1904
  return data;
1762
1905
  },
1763
-
1906
+
1764
1907
  // Serialize a collection by serializing each of its models.
1765
1908
  serializeCollection: function(collection){
1766
1909
  return collection.toJSON.apply(collection, slice.call(arguments, 1));
1767
1910
  },
1768
-
1911
+
1769
1912
  // Render the view, defaulting to underscore.js templates.
1770
1913
  // You can override this in your view definition to provide
1771
1914
  // a very specific rendering for your view. In general, though,
@@ -1773,47 +1916,49 @@
1773
1916
  // change how Marionette renders views.
1774
1917
  render: function() {
1775
1918
  this._ensureViewIsIntact();
1776
-
1919
+
1777
1920
  this.triggerMethod('before:render', this);
1778
-
1921
+
1779
1922
  this._renderTemplate();
1780
1923
  this.bindUIElements();
1781
-
1924
+
1782
1925
  this.triggerMethod('render', this);
1783
-
1926
+
1784
1927
  return this;
1785
1928
  },
1786
-
1929
+
1787
1930
  // Internal method to render the template with the serialized data
1788
1931
  // and template helpers via the `Marionette.Renderer` object.
1789
1932
  // Throws an `UndefinedTemplateError` error if the template is
1790
1933
  // any falsely value but literal `false`.
1791
1934
  _renderTemplate: function() {
1792
1935
  var template = this.getTemplate();
1793
-
1936
+
1794
1937
  // Allow template-less item views
1795
1938
  if (template === false) {
1796
1939
  return;
1797
1940
  }
1798
-
1941
+
1799
1942
  if (!template) {
1800
- throwError('Cannot render the template since it is null or undefined.',
1801
- 'UndefinedTemplateError');
1943
+ throw new Marionette.Error({
1944
+ name: 'UndefinedTemplateError',
1945
+ message: 'Cannot render the template since it is null or undefined.'
1946
+ });
1802
1947
  }
1803
-
1948
+
1804
1949
  // Add in entity data and template helpers
1805
1950
  var data = this.serializeData();
1806
1951
  data = this.mixinTemplateHelpers(data);
1807
-
1952
+
1808
1953
  // Render and add to el
1809
1954
  var html = Marionette.Renderer.render(template, data, this);
1810
1955
  this.attachElContent(html);
1811
-
1956
+
1812
1957
  return this;
1813
1958
  },
1814
-
1959
+
1815
1960
  // Attaches the content of a given view.
1816
- // This method can be overriden to optimize rendering,
1961
+ // This method can be overridden to optimize rendering,
1817
1962
  // or to render in a non standard way.
1818
1963
  //
1819
1964
  // For example, using `innerHTML` instead of `$el.html`
@@ -1826,32 +1971,32 @@
1826
1971
  // ```
1827
1972
  attachElContent: function(html) {
1828
1973
  this.$el.html(html);
1829
-
1974
+
1830
1975
  return this;
1831
1976
  },
1832
-
1977
+
1833
1978
  // Override the default destroy event to add a few
1834
1979
  // more events that are triggered.
1835
1980
  destroy: function() {
1836
1981
  if (this.isDestroyed) { return; }
1837
-
1982
+
1838
1983
  return Marionette.View.prototype.destroy.apply(this, arguments);
1839
1984
  }
1840
1985
  });
1841
-
1986
+
1842
1987
  /* jshint maxstatements: 14 */
1843
-
1988
+
1844
1989
  // Collection View
1845
1990
  // ---------------
1846
-
1991
+
1847
1992
  // A view that iterates over a Backbone.Collection
1848
1993
  // and renders an individual child view for each model.
1849
1994
  Marionette.CollectionView = Marionette.View.extend({
1850
-
1995
+
1851
1996
  // used as the prefix for child view events
1852
1997
  // that are forwarded through the collectionview
1853
1998
  childViewEventPrefix: 'childview',
1854
-
1999
+
1855
2000
  // constructor
1856
2001
  // option to pass `{sort: false}` to prevent the `CollectionView` from
1857
2002
  // maintaining the sorted order of the collection.
@@ -1859,15 +2004,15 @@
1859
2004
  constructor: function(options){
1860
2005
  var initOptions = options || {};
1861
2006
  this.sort = _.isUndefined(initOptions.sort) ? true : initOptions.sort;
1862
-
2007
+
2008
+ this.once('render', this._initialEvents);
1863
2009
  this._initChildViewStorage();
1864
-
2010
+
1865
2011
  Marionette.View.apply(this, arguments);
1866
-
1867
- this._initialEvents();
2012
+
1868
2013
  this.initRenderBuffer();
1869
2014
  },
1870
-
2015
+
1871
2016
  // Instead of inserting elements one by one into the page,
1872
2017
  // it's much more performant to insert elements into a document
1873
2018
  // fragment and then insert that document fragment into the page
@@ -1875,12 +2020,12 @@
1875
2020
  this.elBuffer = document.createDocumentFragment();
1876
2021
  this._bufferedChildren = [];
1877
2022
  },
1878
-
2023
+
1879
2024
  startBuffering: function() {
1880
2025
  this.initRenderBuffer();
1881
2026
  this.isBuffering = true;
1882
2027
  },
1883
-
2028
+
1884
2029
  endBuffering: function() {
1885
2030
  this.isBuffering = false;
1886
2031
  this._triggerBeforeShowBufferedChildren();
@@ -1888,26 +2033,27 @@
1888
2033
  this._triggerShowBufferedChildren();
1889
2034
  this.initRenderBuffer();
1890
2035
  },
1891
-
2036
+
1892
2037
  _triggerBeforeShowBufferedChildren: function() {
1893
2038
  if (this._isShown) {
1894
- _.invoke(this._bufferedChildren, 'triggerMethod', 'before:show');
2039
+ _.each(this._bufferedChildren, _.partial(this._triggerMethodOnChild, 'before:show'));
1895
2040
  }
1896
2041
  },
1897
-
2042
+
1898
2043
  _triggerShowBufferedChildren: function() {
1899
2044
  if (this._isShown) {
1900
- _.each(this._bufferedChildren, function (child) {
1901
- if (_.isFunction(child.triggerMethod)) {
1902
- child.triggerMethod('show');
1903
- } else {
1904
- Marionette.triggerMethod.call(child, 'show');
1905
- }
1906
- });
2045
+ _.each(this._bufferedChildren, _.partial(this._triggerMethodOnChild, 'show'));
2046
+
1907
2047
  this._bufferedChildren = [];
1908
2048
  }
1909
2049
  },
1910
-
2050
+
2051
+ // Internal method for _.each loops to call `Marionette.triggerMethodOn` on
2052
+ // a child view
2053
+ _triggerMethodOnChild: function(event, childView) {
2054
+ Marionette.triggerMethodOn(childView, event);
2055
+ },
2056
+
1911
2057
  // Configured the initial events that the collection view
1912
2058
  // binds to.
1913
2059
  _initialEvents: function() {
@@ -1915,13 +2061,13 @@
1915
2061
  this.listenTo(this.collection, 'add', this._onCollectionAdd);
1916
2062
  this.listenTo(this.collection, 'remove', this._onCollectionRemove);
1917
2063
  this.listenTo(this.collection, 'reset', this.render);
1918
-
2064
+
1919
2065
  if (this.sort) {
1920
2066
  this.listenTo(this.collection, 'sort', this._sortViews);
1921
2067
  }
1922
2068
  }
1923
2069
  },
1924
-
2070
+
1925
2071
  // Handle a child added to the collection
1926
2072
  _onCollectionAdd: function(child) {
1927
2073
  this.destroyEmptyView();
@@ -1929,25 +2075,19 @@
1929
2075
  var index = this.collection.indexOf(child);
1930
2076
  this.addChild(child, ChildView, index);
1931
2077
  },
1932
-
2078
+
1933
2079
  // get the child view by model it holds, and remove it
1934
2080
  _onCollectionRemove: function(model) {
1935
2081
  var view = this.children.findByModel(model);
1936
2082
  this.removeChildView(view);
1937
2083
  this.checkEmpty();
1938
2084
  },
1939
-
2085
+
1940
2086
  // Override from `Marionette.View` to trigger show on child views
1941
- onShowCalled: function(){
1942
- this.children.each(function(child){
1943
- if (_.isFunction(child.triggerMethod)) {
1944
- child.triggerMethod('show');
1945
- } else {
1946
- Marionette.triggerMethod.call(child, 'show');
1947
- }
1948
- });
2087
+ onShowCalled: function() {
2088
+ this.children.each(_.partial(this._triggerMethodOnChild, 'show'));
1949
2089
  },
1950
-
2090
+
1951
2091
  // Render children views. Override this method to
1952
2092
  // provide your own implementation of a render function for
1953
2093
  // the collection view.
@@ -1958,7 +2098,7 @@
1958
2098
  this.triggerMethod('render', this);
1959
2099
  return this;
1960
2100
  },
1961
-
2101
+
1962
2102
  // Render view after sorting. Override this method to
1963
2103
  // change how the view renders after a `sort` on the collection.
1964
2104
  // An example of this would be to only `renderChildren` in a `CompositeView`
@@ -1966,7 +2106,7 @@
1966
2106
  resortView: function() {
1967
2107
  this.render();
1968
2108
  },
1969
-
2109
+
1970
2110
  // Internal method. This checks for any changes in the order of the collection.
1971
2111
  // If the index of any view doesn't match, it will render.
1972
2112
  _sortViews: function() {
@@ -1975,19 +2115,19 @@
1975
2115
  var view = this.children.findByModel(item);
1976
2116
  return !view || view._index !== index;
1977
2117
  }, this);
1978
-
2118
+
1979
2119
  if (orderChanged) {
1980
2120
  this.resortView();
1981
2121
  }
1982
2122
  },
1983
-
2123
+
1984
2124
  // Internal method. Separated so that CompositeView can have
1985
2125
  // more control over events being triggered, around the rendering
1986
2126
  // process
1987
2127
  _renderChildren: function() {
1988
2128
  this.destroyEmptyView();
1989
2129
  this.destroyChildren();
1990
-
2130
+
1991
2131
  if (this.isEmpty(this.collection)) {
1992
2132
  this.showEmptyView();
1993
2133
  } else {
@@ -1998,7 +2138,7 @@
1998
2138
  this.triggerMethod('render:collection', this);
1999
2139
  }
2000
2140
  },
2001
-
2141
+
2002
2142
  // Internal method to loop through collection and show each child view.
2003
2143
  showCollection: function() {
2004
2144
  var ChildView;
@@ -2007,74 +2147,81 @@
2007
2147
  this.addChild(child, ChildView, index);
2008
2148
  }, this);
2009
2149
  },
2010
-
2150
+
2011
2151
  // Internal method to show an empty view in place of
2012
2152
  // a collection of child views, when the collection is empty
2013
2153
  showEmptyView: function() {
2014
2154
  var EmptyView = this.getEmptyView();
2015
-
2155
+
2016
2156
  if (EmptyView && !this._showingEmptyView) {
2017
2157
  this.triggerMethod('before:render:empty');
2018
-
2158
+
2019
2159
  this._showingEmptyView = true;
2020
2160
  var model = new Backbone.Model();
2021
2161
  this.addEmptyView(model, EmptyView);
2022
-
2162
+
2023
2163
  this.triggerMethod('render:empty');
2024
2164
  }
2025
2165
  },
2026
-
2166
+
2027
2167
  // Internal method to destroy an existing emptyView instance
2028
2168
  // if one exists. Called when a collection view has been
2029
2169
  // rendered empty, and then a child is added to the collection.
2030
2170
  destroyEmptyView: function() {
2031
2171
  if (this._showingEmptyView) {
2172
+ this.triggerMethod('before:remove:empty');
2173
+
2032
2174
  this.destroyChildren();
2033
2175
  delete this._showingEmptyView;
2176
+
2177
+ this.triggerMethod('remove:empty');
2034
2178
  }
2035
2179
  },
2036
-
2180
+
2037
2181
  // Retrieve the empty view class
2038
2182
  getEmptyView: function() {
2039
2183
  return this.getOption('emptyView');
2040
2184
  },
2041
-
2185
+
2042
2186
  // Render and show the emptyView. Similar to addChild method
2043
2187
  // but "child:added" events are not fired, and the event from
2044
2188
  // emptyView are not forwarded
2045
- addEmptyView: function(child, EmptyView){
2046
-
2189
+ addEmptyView: function(child, EmptyView) {
2190
+
2047
2191
  // get the emptyViewOptions, falling back to childViewOptions
2048
2192
  var emptyViewOptions = this.getOption('emptyViewOptions') ||
2049
2193
  this.getOption('childViewOptions');
2050
-
2194
+
2051
2195
  if (_.isFunction(emptyViewOptions)){
2052
2196
  emptyViewOptions = emptyViewOptions.call(this);
2053
2197
  }
2054
-
2198
+
2055
2199
  // build the empty view
2056
2200
  var view = this.buildChildView(child, EmptyView, emptyViewOptions);
2057
-
2201
+
2202
+ // Proxy emptyView events
2203
+ this.proxyChildEvents(view);
2204
+
2058
2205
  // trigger the 'before:show' event on `view` if the collection view
2059
2206
  // has already been shown
2060
- if (this._isShown){
2061
- this.triggerMethod.call(view, 'before:show');
2207
+ if (this._isShown) {
2208
+ Marionette.triggerMethodOn(view, 'before:show');
2062
2209
  }
2063
-
2210
+
2064
2211
  // Store the `emptyView` like a `childView` so we can properly
2065
2212
  // remove and/or close it later
2066
2213
  this.children.add(view);
2067
-
2214
+
2068
2215
  // Render it and show it
2069
2216
  this.renderChildView(view, -1);
2070
-
2217
+
2071
2218
  // call the 'show' method if the collection view
2072
2219
  // has already been shown
2073
- if (this._isShown){
2074
- this.triggerMethod.call(view, 'show');
2220
+ if (this._isShown) {
2221
+ Marionette.triggerMethodOn(view, 'show');
2075
2222
  }
2076
2223
  },
2077
-
2224
+
2078
2225
  // Retrieve the `childView` class, either from `this.options.childView`
2079
2226
  // or from the `childView` in the object definition. The "options"
2080
2227
  // takes precedence.
@@ -2083,14 +2230,17 @@
2083
2230
  // to determine what `childView` class to return.
2084
2231
  getChildView: function(child) {
2085
2232
  var childView = this.getOption('childView');
2086
-
2233
+
2087
2234
  if (!childView) {
2088
- throwError('A "childView" must be specified', 'NoChildViewError');
2235
+ throw new Marionette.Error({
2236
+ name: 'NoChildViewError',
2237
+ message: 'A "childView" must be specified'
2238
+ });
2089
2239
  }
2090
-
2240
+
2091
2241
  return childView;
2092
2242
  },
2093
-
2243
+
2094
2244
  // Render the child's view and add it to the
2095
2245
  // HTML for the collection view at a given index.
2096
2246
  // This will also update the indices of later views in the collection
@@ -2100,28 +2250,28 @@
2100
2250
  if (_.isFunction(childViewOptions)) {
2101
2251
  childViewOptions = childViewOptions.call(this, child, index);
2102
2252
  }
2103
-
2253
+
2104
2254
  var view = this.buildChildView(child, ChildView, childViewOptions);
2105
-
2255
+
2106
2256
  // increment indices of views after this one
2107
2257
  this._updateIndices(view, true, index);
2108
-
2258
+
2109
2259
  this._addChildView(view, index);
2110
-
2260
+
2111
2261
  return view;
2112
2262
  },
2113
-
2263
+
2114
2264
  // Internal method. This decrements or increments the indices of views after the
2115
2265
  // added/removed view to keep in sync with the collection.
2116
2266
  _updateIndices: function(view, increment, index) {
2117
2267
  if (!this.sort) {
2118
2268
  return;
2119
2269
  }
2120
-
2270
+
2121
2271
  if (increment) {
2122
2272
  // assign the index to the view
2123
2273
  view._index = index;
2124
-
2274
+
2125
2275
  // increment the index of views after this one
2126
2276
  this.children.each(function (laterView) {
2127
2277
  if (laterView._index >= view._index) {
@@ -2138,85 +2288,81 @@
2138
2288
  });
2139
2289
  }
2140
2290
  },
2141
-
2142
-
2291
+
2292
+
2143
2293
  // Internal Method. Add the view to children and render it at
2144
2294
  // the given index.
2145
2295
  _addChildView: function(view, index) {
2146
2296
  // set up the child view event forwarding
2147
2297
  this.proxyChildEvents(view);
2148
-
2298
+
2149
2299
  this.triggerMethod('before:add:child', view);
2150
-
2300
+
2151
2301
  // Store the child view itself so we can properly
2152
2302
  // remove and/or destroy it later
2153
2303
  this.children.add(view);
2154
2304
  this.renderChildView(view, index);
2155
-
2156
- if (this._isShown && !this.isBuffering){
2157
- if (_.isFunction(view.triggerMethod)) {
2158
- view.triggerMethod('show');
2159
- } else {
2160
- Marionette.triggerMethod.call(view, 'show');
2161
- }
2305
+
2306
+ if (this._isShown && !this.isBuffering) {
2307
+ Marionette.triggerMethodOn(view, 'show');
2162
2308
  }
2163
-
2309
+
2164
2310
  this.triggerMethod('add:child', view);
2165
2311
  },
2166
-
2312
+
2167
2313
  // render the child view
2168
2314
  renderChildView: function(view, index) {
2169
2315
  view.render();
2170
2316
  this.attachHtml(this, view, index);
2171
2317
  return view;
2172
2318
  },
2173
-
2319
+
2174
2320
  // Build a `childView` for a model in the collection.
2175
2321
  buildChildView: function(child, ChildViewClass, childViewOptions) {
2176
2322
  var options = _.extend({model: child}, childViewOptions);
2177
2323
  return new ChildViewClass(options);
2178
2324
  },
2179
-
2325
+
2180
2326
  // Remove the child view and destroy it.
2181
2327
  // This function also updates the indices of
2182
2328
  // later views in the collection in order to keep
2183
2329
  // the children in sync with the collection.
2184
2330
  removeChildView: function(view) {
2185
-
2331
+
2186
2332
  if (view) {
2187
2333
  this.triggerMethod('before:remove:child', view);
2188
2334
  // call 'destroy' or 'remove', depending on which is found
2189
2335
  if (view.destroy) { view.destroy(); }
2190
2336
  else if (view.remove) { view.remove(); }
2191
-
2337
+
2192
2338
  this.stopListening(view);
2193
2339
  this.children.remove(view);
2194
2340
  this.triggerMethod('remove:child', view);
2195
-
2341
+
2196
2342
  // decrement the index of views after this one
2197
2343
  this._updateIndices(view, false);
2198
2344
  }
2199
-
2345
+
2200
2346
  return view;
2201
2347
  },
2202
-
2348
+
2203
2349
  // check if the collection is empty
2204
- isEmpty: function(collection) {
2350
+ isEmpty: function() {
2205
2351
  return !this.collection || this.collection.length === 0;
2206
2352
  },
2207
-
2353
+
2208
2354
  // If empty, show the empty view
2209
2355
  checkEmpty: function() {
2210
2356
  if (this.isEmpty(this.collection)) {
2211
2357
  this.showEmptyView();
2212
2358
  }
2213
2359
  },
2214
-
2360
+
2215
2361
  // You might need to override this if you've overridden attachHtml
2216
2362
  attachBuffer: function(collectionView, buffer) {
2217
2363
  collectionView.$el.append(buffer);
2218
2364
  },
2219
-
2365
+
2220
2366
  // Append the HTML to the collection's `el`.
2221
2367
  // Override this method to do something other
2222
2368
  // than `.append`.
@@ -2237,7 +2383,7 @@
2237
2383
  }
2238
2384
  }
2239
2385
  },
2240
-
2386
+
2241
2387
  // Internal method. Check whether we need to insert the view into
2242
2388
  // the correct position.
2243
2389
  _insertBefore: function(childView, index) {
@@ -2249,37 +2395,37 @@
2249
2395
  return view._index === index + 1;
2250
2396
  });
2251
2397
  }
2252
-
2398
+
2253
2399
  if (currentView) {
2254
2400
  currentView.$el.before(childView.el);
2255
2401
  return true;
2256
2402
  }
2257
-
2403
+
2258
2404
  return false;
2259
2405
  },
2260
-
2406
+
2261
2407
  // Internal method. Append a view to the end of the $el
2262
2408
  _insertAfter: function(childView) {
2263
2409
  this.$el.append(childView.el);
2264
2410
  },
2265
-
2411
+
2266
2412
  // Internal method to set up the `children` object for
2267
2413
  // storing all of the child views
2268
2414
  _initChildViewStorage: function() {
2269
2415
  this.children = new Backbone.ChildViewContainer();
2270
2416
  },
2271
-
2417
+
2272
2418
  // Handle cleanup and other destroying needs for the collection of views
2273
2419
  destroy: function() {
2274
2420
  if (this.isDestroyed) { return; }
2275
-
2421
+
2276
2422
  this.triggerMethod('before:destroy:collection');
2277
2423
  this.destroyChildren();
2278
2424
  this.triggerMethod('destroy:collection');
2279
-
2425
+
2280
2426
  return Marionette.View.prototype.destroy.apply(this, arguments);
2281
2427
  },
2282
-
2428
+
2283
2429
  // Destroy the child views that this collection view
2284
2430
  // is holding on to, if any
2285
2431
  destroyChildren: function() {
@@ -2288,42 +2434,42 @@
2288
2434
  this.checkEmpty();
2289
2435
  return childViews;
2290
2436
  },
2291
-
2437
+
2292
2438
  // Set up the child view event forwarding. Uses a "childview:"
2293
2439
  // prefix in front of all forwarded events.
2294
2440
  proxyChildEvents: function(view) {
2295
2441
  var prefix = this.getOption('childViewEventPrefix');
2296
-
2442
+
2297
2443
  // Forward all child view events through the parent,
2298
2444
  // prepending "childview:" to the event name
2299
2445
  this.listenTo(view, 'all', function() {
2300
2446
  var args = slice.call(arguments);
2301
2447
  var rootEvent = args[0];
2302
2448
  var childEvents = this.normalizeMethods(_.result(this, 'childEvents'));
2303
-
2449
+
2304
2450
  args[0] = prefix + ':' + rootEvent;
2305
2451
  args.splice(1, 0, view);
2306
-
2452
+
2307
2453
  // call collectionView childEvent if defined
2308
2454
  if (typeof childEvents !== 'undefined' && _.isFunction(childEvents[rootEvent])) {
2309
2455
  childEvents[rootEvent].apply(this, args.slice(1));
2310
2456
  }
2311
-
2457
+
2312
2458
  this.triggerMethod.apply(this, args);
2313
2459
  }, this);
2314
2460
  }
2315
2461
  });
2316
-
2462
+
2317
2463
  /* jshint maxstatements: 17, maxlen: 117 */
2318
-
2464
+
2319
2465
  // Composite View
2320
2466
  // --------------
2321
-
2467
+
2322
2468
  // Used for rendering a branch-leaf, hierarchical structure.
2323
2469
  // Extends directly from CollectionView and also renders an
2324
2470
  // a child view as `modelView`, for the top leaf
2325
2471
  Marionette.CompositeView = Marionette.CollectionView.extend({
2326
-
2472
+
2327
2473
  // Setting up the inheritance chain which allows changes to
2328
2474
  // Marionette.CollectionView.prototype.constructor which allows overriding
2329
2475
  // option to pass '{sort: false}' to prevent the CompositeView from
@@ -2332,55 +2478,56 @@
2332
2478
  constructor: function() {
2333
2479
  Marionette.CollectionView.apply(this, arguments);
2334
2480
  },
2335
-
2481
+
2336
2482
  // Configured the initial events that the composite view
2337
2483
  // binds to. Override this method to prevent the initial
2338
2484
  // events, or to add your own initial events.
2339
2485
  _initialEvents: function() {
2340
-
2486
+
2341
2487
  // Bind only after composite view is rendered to avoid adding child views
2342
2488
  // to nonexistent childViewContainer
2343
- this.once('render', function() {
2344
- if (this.collection) {
2345
- this.listenTo(this.collection, 'add', this._onCollectionAdd);
2346
- this.listenTo(this.collection, 'remove', this._onCollectionRemove);
2347
- this.listenTo(this.collection, 'reset', this._renderChildren);
2348
-
2349
- if (this.sort) {
2350
- this.listenTo(this.collection, 'sort', this._sortViews);
2351
- }
2489
+
2490
+ if (this.collection) {
2491
+ this.listenTo(this.collection, 'add', this._onCollectionAdd);
2492
+ this.listenTo(this.collection, 'remove', this._onCollectionRemove);
2493
+ this.listenTo(this.collection, 'reset', this._renderChildren);
2494
+
2495
+ if (this.sort) {
2496
+ this.listenTo(this.collection, 'sort', this._sortViews);
2352
2497
  }
2353
- });
2354
-
2498
+ }
2355
2499
  },
2356
-
2500
+
2357
2501
  // Retrieve the `childView` to be used when rendering each of
2358
2502
  // the items in the collection. The default is to return
2359
2503
  // `this.childView` or Marionette.CompositeView if no `childView`
2360
2504
  // has been defined
2361
2505
  getChildView: function(child) {
2362
2506
  var childView = this.getOption('childView') || this.constructor;
2363
-
2507
+
2364
2508
  if (!childView) {
2365
- throwError('A "childView" must be specified', 'NoChildViewError');
2509
+ throw new Marionette.Error({
2510
+ name: 'NoChildViewError',
2511
+ message: 'A "childView" must be specified'
2512
+ });
2366
2513
  }
2367
-
2514
+
2368
2515
  return childView;
2369
2516
  },
2370
-
2517
+
2371
2518
  // Serialize the collection for the view.
2372
2519
  // You can override the `serializeData` method in your own view
2373
2520
  // definition, to provide custom serialization for your view's data.
2374
2521
  serializeData: function() {
2375
2522
  var data = {};
2376
-
2523
+
2377
2524
  if (this.model){
2378
2525
  data = _.partial(this.serializeModel, this.model).apply(this, arguments);
2379
2526
  }
2380
-
2527
+
2381
2528
  return data;
2382
2529
  },
2383
-
2530
+
2384
2531
  // Renders the model once, and the collection once. Calling
2385
2532
  // this again will tell the model's view to re-render itself
2386
2533
  // but the collection will not re-render.
@@ -2388,44 +2535,44 @@
2388
2535
  this._ensureViewIsIntact();
2389
2536
  this.isRendered = true;
2390
2537
  this.resetChildViewContainer();
2391
-
2538
+
2392
2539
  this.triggerMethod('before:render', this);
2393
-
2540
+
2394
2541
  this._renderTemplate();
2395
2542
  this._renderChildren();
2396
-
2543
+
2397
2544
  this.triggerMethod('render', this);
2398
2545
  return this;
2399
2546
  },
2400
-
2547
+
2401
2548
  _renderChildren: function() {
2402
2549
  if (this.isRendered) {
2403
2550
  Marionette.CollectionView.prototype._renderChildren.call(this);
2404
2551
  }
2405
2552
  },
2406
-
2553
+
2407
2554
  // Render the root template that the children
2408
2555
  // views are appended to
2409
2556
  _renderTemplate: function() {
2410
2557
  var data = {};
2411
2558
  data = this.serializeData();
2412
2559
  data = this.mixinTemplateHelpers(data);
2413
-
2560
+
2414
2561
  this.triggerMethod('before:render:template');
2415
-
2562
+
2416
2563
  var template = this.getTemplate();
2417
2564
  var html = Marionette.Renderer.render(template, data, this);
2418
2565
  this.attachElContent(html);
2419
-
2566
+
2420
2567
  // the ui bindings is done here and not at the end of render since they
2421
2568
  // will not be available until after the model is rendered, but should be
2422
2569
  // available before the collection is rendered.
2423
2570
  this.bindUIElements();
2424
2571
  this.triggerMethod('render:template');
2425
2572
  },
2426
-
2573
+
2427
2574
  // Attaches the content of the root.
2428
- // This method can be overriden to optimize rendering,
2575
+ // This method can be overridden to optimize rendering,
2429
2576
  // or to render in a non standard way.
2430
2577
  //
2431
2578
  // For example, using `innerHTML` instead of `$el.html`
@@ -2438,16 +2585,16 @@
2438
2585
  // ```
2439
2586
  attachElContent: function(html) {
2440
2587
  this.$el.html(html);
2441
-
2588
+
2442
2589
  return this;
2443
2590
  },
2444
-
2591
+
2445
2592
  // You might need to override this if you've overridden attachHtml
2446
2593
  attachBuffer: function(compositeView, buffer) {
2447
2594
  var $container = this.getChildViewContainer(compositeView);
2448
2595
  $container.append(buffer);
2449
2596
  },
2450
-
2597
+
2451
2598
  // Internal method. Append a view to the end of the $el.
2452
2599
  // Overidden from CollectionView to ensure view is appended to
2453
2600
  // childViewContainer
@@ -2455,39 +2602,41 @@
2455
2602
  var $container = this.getChildViewContainer(this);
2456
2603
  $container.append(childView.el);
2457
2604
  },
2458
-
2605
+
2459
2606
  // Internal method to ensure an `$childViewContainer` exists, for the
2460
2607
  // `attachHtml` method to use.
2461
2608
  getChildViewContainer: function(containerView) {
2462
2609
  if ('$childViewContainer' in containerView) {
2463
2610
  return containerView.$childViewContainer;
2464
2611
  }
2465
-
2612
+
2466
2613
  var container;
2467
2614
  var childViewContainer = Marionette.getOption(containerView, 'childViewContainer');
2468
2615
  if (childViewContainer) {
2469
-
2616
+
2470
2617
  var selector = _.isFunction(childViewContainer) ? childViewContainer.call(containerView) : childViewContainer;
2471
-
2618
+
2472
2619
  if (selector.charAt(0) === '@' && containerView.ui) {
2473
2620
  container = containerView.ui[selector.substr(4)];
2474
2621
  } else {
2475
2622
  container = containerView.$(selector);
2476
2623
  }
2477
-
2624
+
2478
2625
  if (container.length <= 0) {
2479
- throwError('The specified "childViewContainer" was not found: ' +
2480
- containerView.childViewContainer, 'ChildViewContainerMissingError');
2626
+ throw new Marionette.Error({
2627
+ name: 'ChildViewContainerMissingError',
2628
+ message: 'The specified "childViewContainer" was not found: ' + containerView.childViewContainer
2629
+ });
2481
2630
  }
2482
-
2631
+
2483
2632
  } else {
2484
2633
  container = containerView.$el;
2485
2634
  }
2486
-
2635
+
2487
2636
  containerView.$childViewContainer = container;
2488
2637
  return container;
2489
2638
  },
2490
-
2639
+
2491
2640
  // Internal method to reset the `$childViewContainer` on render
2492
2641
  resetChildViewContainer: function() {
2493
2642
  if (this.$childViewContainer) {
@@ -2495,10 +2644,10 @@
2495
2644
  }
2496
2645
  }
2497
2646
  });
2498
-
2647
+
2499
2648
  // LayoutView
2500
2649
  // ----------
2501
-
2650
+
2502
2651
  // Used for managing application layoutViews, nested layoutViews and
2503
2652
  // multiple regions within an application or sub-application.
2504
2653
  //
@@ -2507,25 +2656,25 @@
2507
2656
  // Used for composite view management and sub-application areas.
2508
2657
  Marionette.LayoutView = Marionette.ItemView.extend({
2509
2658
  regionClass: Marionette.Region,
2510
-
2659
+
2511
2660
  // Ensure the regions are available when the `initialize` method
2512
2661
  // is called.
2513
2662
  constructor: function(options) {
2514
2663
  options = options || {};
2515
-
2664
+
2516
2665
  this._firstRender = true;
2517
2666
  this._initializeRegions(options);
2518
-
2667
+
2519
2668
  Marionette.ItemView.call(this, options);
2520
2669
  },
2521
-
2670
+
2522
2671
  // LayoutView's render will use the existing region objects the
2523
2672
  // first time it is called. Subsequent calls will destroy the
2524
2673
  // views that the regions are showing and then reset the `el`
2525
2674
  // for the regions to the newly rendered DOM elements.
2526
2675
  render: function() {
2527
2676
  this._ensureViewIsIntact();
2528
-
2677
+
2529
2678
  if (this._firstRender) {
2530
2679
  // if this is the first render, don't do anything to
2531
2680
  // reset the regions
@@ -2535,88 +2684,90 @@
2535
2684
  // re-initialize the `el` for each region
2536
2685
  this._reInitializeRegions();
2537
2686
  }
2538
-
2687
+
2539
2688
  return Marionette.ItemView.prototype.render.apply(this, arguments);
2540
2689
  },
2541
-
2690
+
2542
2691
  // Handle destroying regions, and then destroy the view itself.
2543
2692
  destroy: function() {
2544
2693
  if (this.isDestroyed) { return this; }
2545
-
2694
+
2546
2695
  this.regionManager.destroy();
2547
2696
  return Marionette.ItemView.prototype.destroy.apply(this, arguments);
2548
2697
  },
2549
-
2698
+
2550
2699
  // Add a single region, by name, to the layoutView
2551
2700
  addRegion: function(name, definition) {
2552
- this.triggerMethod('before:region:add', name);
2553
2701
  var regions = {};
2554
2702
  regions[name] = definition;
2555
2703
  return this._buildRegions(regions)[name];
2556
2704
  },
2557
-
2705
+
2558
2706
  // Add multiple regions as a {name: definition, name2: def2} object literal
2559
2707
  addRegions: function(regions) {
2560
2708
  this.regions = _.extend({}, this.regions, regions);
2561
2709
  return this._buildRegions(regions);
2562
2710
  },
2563
-
2711
+
2564
2712
  // Remove a single region from the LayoutView, by name
2565
2713
  removeRegion: function(name) {
2566
- this.triggerMethod('before:region:remove', name);
2567
2714
  delete this.regions[name];
2568
2715
  return this.regionManager.removeRegion(name);
2569
2716
  },
2570
-
2717
+
2571
2718
  // Provides alternative access to regions
2572
2719
  // Accepts the region name
2573
2720
  // getRegion('main')
2574
2721
  getRegion: function(region) {
2575
2722
  return this.regionManager.get(region);
2576
2723
  },
2577
-
2724
+
2578
2725
  // Get all regions
2579
2726
  getRegions: function(){
2580
2727
  return this.regionManager.getRegions();
2581
2728
  },
2582
-
2729
+
2583
2730
  // internal method to build regions
2584
2731
  _buildRegions: function(regions) {
2585
2732
  var that = this;
2586
-
2733
+
2587
2734
  var defaults = {
2588
2735
  regionClass: this.getOption('regionClass'),
2589
2736
  parentEl: function() { return that.$el; }
2590
2737
  };
2591
-
2738
+
2592
2739
  return this.regionManager.addRegions(regions, defaults);
2593
2740
  },
2594
-
2741
+
2595
2742
  // Internal method to initialize the regions that have been defined in a
2596
2743
  // `regions` attribute on this layoutView.
2597
2744
  _initializeRegions: function(options) {
2598
2745
  var regions;
2599
2746
  this._initRegionManager();
2600
-
2747
+
2601
2748
  if (_.isFunction(this.regions)) {
2602
2749
  regions = this.regions(options);
2603
2750
  } else {
2604
2751
  regions = this.regions || {};
2605
2752
  }
2606
-
2753
+
2607
2754
  // Enable users to define `regions` as instance options.
2608
2755
  var regionOptions = this.getOption.call(options, 'regions');
2609
-
2756
+
2610
2757
  // enable region options to be a function
2611
2758
  if (_.isFunction(regionOptions)) {
2612
2759
  regionOptions = regionOptions.call(this, options);
2613
2760
  }
2614
-
2761
+
2615
2762
  _.extend(regions, regionOptions);
2616
-
2763
+
2764
+ // Normalize region selectors hash to allow
2765
+ // a user to use the @ui. syntax.
2766
+ regions = this.normalizeUIValues(regions);
2767
+
2617
2768
  this.addRegions(regions);
2618
2769
  },
2619
-
2770
+
2620
2771
  // Internal method to re-initialize all of the regions by updating the `el` that
2621
2772
  // they point to
2622
2773
  _reInitializeRegions: function() {
@@ -2625,48 +2776,48 @@
2625
2776
  region.reset();
2626
2777
  });
2627
2778
  },
2628
-
2629
- // Enable easy overiding of the default `RegionManager`
2630
- // for customized region interactions and buisness specific
2779
+
2780
+ // Enable easy overriding of the default `RegionManager`
2781
+ // for customized region interactions and business specific
2631
2782
  // view logic for better control over single regions.
2632
2783
  getRegionManager: function() {
2633
2784
  return new Marionette.RegionManager();
2634
2785
  },
2635
-
2786
+
2636
2787
  // Internal method to initialize the region manager
2637
2788
  // and all regions in it
2638
2789
  _initRegionManager: function() {
2639
2790
  this.regionManager = this.getRegionManager();
2640
-
2791
+
2641
2792
  this.listenTo(this.regionManager, 'before:add:region', function(name) {
2642
2793
  this.triggerMethod('before:add:region', name);
2643
2794
  });
2644
-
2795
+
2645
2796
  this.listenTo(this.regionManager, 'add:region', function(name, region) {
2646
2797
  this[name] = region;
2647
2798
  this.triggerMethod('add:region', name, region);
2648
2799
  });
2649
-
2800
+
2650
2801
  this.listenTo(this.regionManager, 'before:remove:region', function(name) {
2651
2802
  this.triggerMethod('before:remove:region', name);
2652
2803
  });
2653
-
2804
+
2654
2805
  this.listenTo(this.regionManager, 'remove:region', function(name, region) {
2655
2806
  delete this[name];
2656
2807
  this.triggerMethod('remove:region', name, region);
2657
2808
  });
2658
2809
  }
2659
2810
  });
2660
-
2811
+
2661
2812
 
2662
2813
  // Behavior
2663
2814
  // -----------
2664
-
2815
+
2665
2816
  // A Behavior is an isolated set of DOM /
2666
2817
  // user interactions that can be mixed into any View.
2667
2818
  // Behaviors allow you to blackbox View specific interactions
2668
2819
  // into portable logical chunks, keeping your views simple and your code DRY.
2669
-
2820
+
2670
2821
  Marionette.Behavior = (function(_, Backbone) {
2671
2822
  function Behavior(options, view) {
2672
2823
  // Setup reference to the view.
@@ -2676,185 +2827,132 @@
2676
2827
  this.view = view;
2677
2828
  this.defaults = _.result(this, 'defaults') || {};
2678
2829
  this.options = _.extend({}, this.defaults, options);
2679
-
2830
+
2680
2831
  // proxy behavior $ method to the view
2681
2832
  // this is useful for doing jquery DOM lookups
2682
2833
  // scoped to behaviors view.
2683
2834
  this.$ = function() {
2684
2835
  return this.view.$.apply(this.view, arguments);
2685
2836
  };
2686
-
2837
+
2687
2838
  // Call the initialize method passing
2688
2839
  // the arguments from the instance constructor
2689
2840
  this.initialize.apply(this, arguments);
2690
2841
  }
2691
-
2842
+
2692
2843
  _.extend(Behavior.prototype, Backbone.Events, {
2693
2844
  initialize: function() {},
2694
-
2845
+
2695
2846
  // stopListening to behavior `onListen` events.
2696
2847
  destroy: function() {
2697
2848
  this.stopListening();
2698
2849
  },
2699
-
2850
+
2851
+ proxyViewProperties: function (view) {
2852
+ this.$el = view.$el;
2853
+ this.el = view.el;
2854
+ },
2855
+
2700
2856
  // import the `triggerMethod` to trigger events with corresponding
2701
2857
  // methods if the method exists
2702
2858
  triggerMethod: Marionette.triggerMethod,
2703
-
2859
+
2704
2860
  // Proxy `getOption` to enable getting options from this or this.options by name.
2705
2861
  getOption: Marionette.proxyGetOption,
2706
-
2862
+
2707
2863
  // Proxy `unbindEntityEvents` to enable binding view's events from another entity.
2708
2864
  bindEntityEvents: Marionette.proxyBindEntityEvents,
2709
-
2865
+
2710
2866
  // Proxy `unbindEntityEvents` to enable unbinding view's events from another entity.
2711
2867
  unbindEntityEvents: Marionette.proxyUnbindEntityEvents
2712
2868
  });
2713
-
2869
+
2714
2870
  // Borrow Backbones extend implementation
2715
2871
  // this allows us to setup a proper
2716
- // inheritence pattern that follow in suite
2872
+ // inheritance pattern that follows suit
2717
2873
  // with the rest of Marionette views.
2718
2874
  Behavior.extend = Marionette.extend;
2719
-
2875
+
2720
2876
  return Behavior;
2721
2877
  })(_, Backbone);
2722
-
2723
- /* jshint maxlen: 143, nonew: false */
2878
+
2879
+ /* jshint maxlen: 143 */
2724
2880
  // Marionette.Behaviors
2725
2881
  // --------
2726
-
2882
+
2727
2883
  // Behaviors is a utility class that takes care of
2728
- // glueing your behavior instances to their given View.
2884
+ // gluing your behavior instances to their given View.
2729
2885
  // The most important part of this class is that you
2730
2886
  // **MUST** override the class level behaviorsLookup
2731
2887
  // method for things to work properly.
2732
-
2888
+
2733
2889
  Marionette.Behaviors = (function(Marionette, _) {
2734
-
2890
+
2735
2891
  function Behaviors(view, behaviors) {
2892
+
2893
+ if (!_.isObject(view.behaviors)) {
2894
+ return {};
2895
+ }
2896
+
2736
2897
  // Behaviors defined on a view can be a flat object literal
2737
2898
  // or it can be a function that returns an object.
2738
2899
  behaviors = Behaviors.parseBehaviors(view, behaviors || _.result(view, 'behaviors'));
2739
-
2900
+
2740
2901
  // Wraps several of the view's methods
2741
2902
  // calling the methods first on each behavior
2742
2903
  // and then eventually calling the method on the view.
2743
2904
  Behaviors.wrap(view, behaviors, _.keys(methods));
2905
+ return behaviors;
2744
2906
  }
2745
-
2907
+
2746
2908
  var methods = {
2747
- setElement: function(setElement, behaviors) {
2748
- setElement.apply(this, _.tail(arguments, 2));
2749
-
2750
- // proxy behavior $el to the view's $el.
2751
- // This is needed because a view's $el proxy
2752
- // is not set until after setElement is called.
2753
- _.each(behaviors, function(b) {
2754
- b.$el = this.$el;
2755
- b.el = this.el;
2756
- }, this);
2757
-
2758
- return this;
2759
- },
2760
-
2761
- destroy: function(destroy, behaviors) {
2762
- var args = _.tail(arguments, 2);
2763
- destroy.apply(this, args);
2764
-
2765
- // Call destroy on each behavior after
2766
- // destroying the view.
2767
- // This unbinds event listeners
2768
- // that behaviors have registerd for.
2769
- _.invoke(behaviors, 'destroy', args);
2770
- return this;
2771
- },
2772
-
2773
- bindUIElements: function(bindUIElements, behaviors) {
2774
- bindUIElements.apply(this);
2775
- _.invoke(behaviors, bindUIElements);
2776
- },
2777
-
2778
- unbindUIElements: function(unbindUIElements, behaviors) {
2779
- unbindUIElements.apply(this);
2780
- _.invoke(behaviors, unbindUIElements);
2781
- },
2782
-
2783
- triggerMethod: function(triggerMethod, behaviors) {
2784
- var args = _.tail(arguments, 2);
2785
- triggerMethod.apply(this, args);
2786
-
2787
- _.each(behaviors, function(b) {
2788
- triggerMethod.apply(b, args);
2789
- });
2790
- },
2791
-
2792
- delegateEvents: function(delegateEvents, behaviors) {
2793
- var args = _.tail(arguments, 2);
2794
- delegateEvents.apply(this, args);
2795
-
2796
- _.each(behaviors, function(b) {
2797
- Marionette.bindEntityEvents(b, this.model, Marionette.getOption(b, 'modelEvents'));
2798
- Marionette.bindEntityEvents(b, this.collection, Marionette.getOption(b, 'collectionEvents'));
2799
- }, this);
2800
-
2801
- return this;
2909
+ behaviorTriggers: function(behaviorTriggers, behaviors) {
2910
+ var triggerBuilder = new BehaviorTriggersBuilder(this, behaviors);
2911
+ return triggerBuilder.buildBehaviorTriggers();
2802
2912
  },
2803
-
2804
- undelegateEvents: function(undelegateEvents, behaviors) {
2805
- var args = _.tail(arguments, 2);
2806
- undelegateEvents.apply(this, args);
2807
-
2808
- _.each(behaviors, function(b) {
2809
- Marionette.unbindEntityEvents(b, this.model, Marionette.getOption(b, 'modelEvents'));
2810
- Marionette.unbindEntityEvents(b, this.collection, Marionette.getOption(b, 'collectionEvents'));
2811
- }, this);
2812
-
2813
- return this;
2814
- },
2815
-
2913
+
2816
2914
  behaviorEvents: function(behaviorEvents, behaviors) {
2817
2915
  var _behaviorsEvents = {};
2818
2916
  var viewUI = _.result(this, 'ui');
2819
-
2917
+
2820
2918
  _.each(behaviors, function(b, i) {
2821
2919
  var _events = {};
2822
2920
  var behaviorEvents = _.clone(_.result(b, 'events')) || {};
2823
2921
  var behaviorUI = _.result(b, 'ui');
2824
-
2922
+
2825
2923
  // Construct an internal UI hash first using
2826
2924
  // the views UI hash and then the behaviors UI hash.
2827
2925
  // This allows the user to use UI hash elements
2828
2926
  // defined in the parent view as well as those
2829
2927
  // defined in the given behavior.
2830
2928
  var ui = _.extend({}, viewUI, behaviorUI);
2831
-
2929
+
2832
2930
  // Normalize behavior events hash to allow
2833
2931
  // a user to use the @ui. syntax.
2834
2932
  behaviorEvents = Marionette.normalizeUIKeys(behaviorEvents, ui);
2835
-
2933
+
2836
2934
  _.each(_.keys(behaviorEvents), function(key) {
2837
2935
  // Append white-space at the end of each key to prevent behavior key collisions.
2838
2936
  // This is relying on the fact that backbone events considers "click .foo" the same as
2839
2937
  // "click .foo ".
2840
-
2938
+
2841
2939
  // +2 is used because new Array(1) or 0 is "" and not " "
2842
2940
  var whitespace = (new Array(i + 2)).join(' ');
2843
2941
  var eventKey = key + whitespace;
2844
2942
  var handler = _.isFunction(behaviorEvents[key]) ? behaviorEvents[key] : b[behaviorEvents[key]];
2845
-
2943
+
2846
2944
  _events[eventKey] = _.bind(handler, b);
2847
2945
  });
2848
-
2946
+
2849
2947
  _behaviorsEvents = _.extend(_behaviorsEvents, _events);
2850
2948
  });
2851
-
2949
+
2852
2950
  return _behaviorsEvents;
2853
2951
  }
2854
2952
  };
2855
-
2953
+
2856
2954
  _.extend(Behaviors, {
2857
-
2955
+
2858
2956
  // Placeholder method to be extended by the user.
2859
2957
  // The method should define the object that stores the behaviors.
2860
2958
  // i.e.
@@ -2865,11 +2963,12 @@
2865
2963
  // }
2866
2964
  // ```
2867
2965
  behaviorsLookup: function() {
2868
- throw new Error('You must define where your behaviors are stored.' +
2869
- 'See https://github.com/marionettejs/backbone.marionette' +
2870
- '/blob/master/docs/marionette.behaviors.md#behaviorslookup');
2966
+ throw new Marionette.Error({
2967
+ message: 'You must define where your behaviors are stored.',
2968
+ url: 'marionette.behaviors.html#behaviorslookup'
2969
+ });
2871
2970
  },
2872
-
2971
+
2873
2972
  // Takes care of getting the behavior class
2874
2973
  // given options and a key.
2875
2974
  // If a user passes in options.behaviorClass
@@ -2879,24 +2978,24 @@
2879
2978
  if (options.behaviorClass) {
2880
2979
  return options.behaviorClass;
2881
2980
  }
2882
-
2981
+
2883
2982
  // Get behavior class can be either a flat object or a method
2884
2983
  return _.isFunction(Behaviors.behaviorsLookup) ? Behaviors.behaviorsLookup.apply(this, arguments)[key] : Behaviors.behaviorsLookup[key];
2885
2984
  },
2886
-
2985
+
2887
2986
  // Iterate over the behaviors object, for each behavior
2888
2987
  // instantiate it and get its grouped behaviors.
2889
2988
  parseBehaviors: function(view, behaviors) {
2890
2989
  return _.chain(behaviors).map(function(options, key) {
2891
2990
  var BehaviorClass = Behaviors.getBehaviorClass(options, key);
2892
-
2991
+
2893
2992
  var behavior = new BehaviorClass(options, view);
2894
2993
  var nestedBehaviors = Behaviors.parseBehaviors(view, _.result(behavior, 'behaviors'));
2895
-
2994
+
2896
2995
  return [behavior].concat(nestedBehaviors);
2897
2996
  }).flatten().value();
2898
2997
  },
2899
-
2998
+
2900
2999
  // Wrap view internal methods so that they delegate to behaviors. For example,
2901
3000
  // `onDestroy` should trigger destroy on all of the behaviors and then destroy itself.
2902
3001
  // i.e.
@@ -2908,15 +3007,53 @@
2908
3007
  });
2909
3008
  }
2910
3009
  });
2911
-
3010
+
3011
+ // Class to build handlers for `triggers` on behaviors
3012
+ // for views
3013
+ function BehaviorTriggersBuilder(view, behaviors) {
3014
+ this._view = view;
3015
+ this._viewUI = _.result(view, 'ui');
3016
+ this._behaviors = behaviors;
3017
+ this._triggers = {};
3018
+ }
3019
+
3020
+ _.extend(BehaviorTriggersBuilder.prototype, {
3021
+ // Main method to build the triggers hash with event keys and handlers
3022
+ buildBehaviorTriggers: function() {
3023
+ _.each(this._behaviors, this._buildTriggerHandlersForBehavior, this);
3024
+ return this._triggers;
3025
+ },
3026
+
3027
+ // Internal method to build all trigger handlers for a given behavior
3028
+ _buildTriggerHandlersForBehavior: function(behavior, i) {
3029
+ var ui = _.extend({}, this._viewUI, _.result(behavior, 'ui'));
3030
+ var triggersHash = _.clone(_.result(behavior, 'triggers')) || {};
3031
+
3032
+ triggersHash = Marionette.normalizeUIKeys(triggersHash, ui);
3033
+
3034
+ _.each(triggersHash, _.partial(this._setHandlerForBehavior, behavior, i), this);
3035
+ },
3036
+
3037
+ // Internal method to create and assign the trigger handler for a given
3038
+ // behavior
3039
+ _setHandlerForBehavior: function(behavior, i, eventName, trigger) {
3040
+ // Unique identifier for the `this._triggers` hash
3041
+ var triggerKey = trigger.replace(/^\S+/, function(triggerName) {
3042
+ return triggerName + '.' + 'behaviortriggers' + i;
3043
+ });
3044
+
3045
+ this._triggers[triggerKey] = this._view._buildViewTrigger(eventName);
3046
+ }
3047
+ });
3048
+
2912
3049
  return Behaviors;
2913
-
3050
+
2914
3051
  })(Marionette, _);
2915
-
3052
+
2916
3053
 
2917
3054
  // AppRouter
2918
3055
  // ---------
2919
-
3056
+
2920
3057
  // Reduce the boilerplate code of handling route events
2921
3058
  // and then calling a single method on another object.
2922
3059
  // Have your routers configured to call the method on
@@ -2931,102 +3068,108 @@
2931
3068
  // just one giant router and controller.
2932
3069
  //
2933
3070
  // You can also add standard routes to an AppRouter.
2934
-
3071
+
2935
3072
  Marionette.AppRouter = Backbone.Router.extend({
2936
-
3073
+
2937
3074
  constructor: function(options) {
2938
3075
  Backbone.Router.apply(this, arguments);
2939
-
3076
+
2940
3077
  this.options = options || {};
2941
-
3078
+
2942
3079
  var appRoutes = this.getOption('appRoutes');
2943
3080
  var controller = this._getController();
2944
3081
  this.processAppRoutes(controller, appRoutes);
2945
3082
  this.on('route', this._processOnRoute, this);
2946
3083
  },
2947
-
3084
+
2948
3085
  // Similar to route method on a Backbone Router but
2949
3086
  // method is called on the controller
2950
3087
  appRoute: function(route, methodName) {
2951
3088
  var controller = this._getController();
2952
3089
  this._addAppRoute(controller, route, methodName);
2953
3090
  },
2954
-
3091
+
2955
3092
  // process the route event and trigger the onRoute
2956
3093
  // method call, if it exists
2957
3094
  _processOnRoute: function(routeName, routeArgs) {
2958
3095
  // find the path that matched
2959
3096
  var routePath = _.invert(this.getOption('appRoutes'))[routeName];
2960
-
3097
+
2961
3098
  // make sure an onRoute is there, and call it
2962
3099
  if (_.isFunction(this.onRoute)) {
2963
3100
  this.onRoute(routeName, routePath, routeArgs);
2964
3101
  }
2965
3102
  },
2966
-
3103
+
2967
3104
  // Internal method to process the `appRoutes` for the
2968
3105
  // router, and turn them in to routes that trigger the
2969
3106
  // specified method on the specified `controller`.
2970
3107
  processAppRoutes: function(controller, appRoutes) {
2971
3108
  if (!appRoutes) { return; }
2972
-
3109
+
2973
3110
  var routeNames = _.keys(appRoutes).reverse(); // Backbone requires reverted order of routes
2974
-
3111
+
2975
3112
  _.each(routeNames, function(route) {
2976
3113
  this._addAppRoute(controller, route, appRoutes[route]);
2977
3114
  }, this);
2978
3115
  },
2979
-
3116
+
2980
3117
  _getController: function() {
2981
3118
  return this.getOption('controller');
2982
3119
  },
2983
-
3120
+
2984
3121
  _addAppRoute: function(controller, route, methodName) {
2985
3122
  var method = controller[methodName];
2986
-
3123
+
2987
3124
  if (!method) {
2988
- throwError('Method "' + methodName + '" was not found on the controller');
3125
+ throw new Marionette.Error('Method "' + methodName + '" was not found on the controller');
2989
3126
  }
2990
-
3127
+
2991
3128
  this.route(route, methodName, _.bind(method, controller));
2992
3129
  },
2993
-
3130
+
2994
3131
  // Proxy `getOption` to enable getting options from this or this.options by name.
2995
3132
  getOption: Marionette.proxyGetOption
2996
3133
  });
2997
-
3134
+
2998
3135
  // Application
2999
3136
  // -----------
3000
-
3137
+
3001
3138
  // Contain and manage the composite application as a whole.
3002
3139
  // Stores and starts up `Region` objects, includes an
3003
3140
  // event aggregator as `app.vent`
3004
3141
  Marionette.Application = function(options) {
3142
+ this.options = options;
3005
3143
  this._initializeRegions(options);
3006
3144
  this._initCallbacks = new Marionette.Callbacks();
3007
3145
  this.submodules = {};
3008
3146
  _.extend(this, options);
3009
3147
  this._initChannel();
3148
+ this.initialize.apply(this, arguments);
3010
3149
  };
3011
-
3150
+
3012
3151
  _.extend(Marionette.Application.prototype, Backbone.Events, {
3152
+ // Initialize is an empty function by default. Override it with your own
3153
+ // initialization logic.
3154
+ initialize: function() {},
3155
+
3013
3156
  // Command execution, facilitated by Backbone.Wreqr.Commands
3014
3157
  execute: function() {
3015
3158
  this.commands.execute.apply(this.commands, arguments);
3016
3159
  },
3017
-
3160
+
3018
3161
  // Request/response, facilitated by Backbone.Wreqr.RequestResponse
3019
3162
  request: function() {
3020
3163
  return this.reqres.request.apply(this.reqres, arguments);
3021
3164
  },
3022
-
3165
+
3023
3166
  // Add an initializer that is either run at when the `start`
3024
3167
  // method is called, or run immediately if added after `start`
3025
3168
  // has already been called.
3026
3169
  addInitializer: function(initializer) {
3027
3170
  this._initCallbacks.add(initializer);
3028
3171
  },
3029
-
3172
+
3030
3173
  // kick off all of the application's processes.
3031
3174
  // initializes all of the regions that have been added
3032
3175
  // to the app, and runs all of the initializer functions
@@ -3035,7 +3178,7 @@
3035
3178
  this._initCallbacks.run(options, this);
3036
3179
  this.triggerMethod('start', options);
3037
3180
  },
3038
-
3181
+
3039
3182
  // Add regions to your app.
3040
3183
  // Accepts a hash of named strings or Region objects
3041
3184
  // addRegions({something: "#someRegion"})
@@ -3043,99 +3186,99 @@
3043
3186
  addRegions: function(regions) {
3044
3187
  return this._regionManager.addRegions(regions);
3045
3188
  },
3046
-
3189
+
3047
3190
  // Empty all regions in the app, without removing them
3048
3191
  emptyRegions: function() {
3049
3192
  return this._regionManager.emptyRegions();
3050
3193
  },
3051
-
3194
+
3052
3195
  // Removes a region from your app, by name
3053
3196
  // Accepts the regions name
3054
3197
  // removeRegion('myRegion')
3055
3198
  removeRegion: function(region) {
3056
3199
  return this._regionManager.removeRegion(region);
3057
3200
  },
3058
-
3201
+
3059
3202
  // Provides alternative access to regions
3060
3203
  // Accepts the region name
3061
3204
  // getRegion('main')
3062
3205
  getRegion: function(region) {
3063
3206
  return this._regionManager.get(region);
3064
3207
  },
3065
-
3208
+
3066
3209
  // Get all the regions from the region manager
3067
3210
  getRegions: function(){
3068
3211
  return this._regionManager.getRegions();
3069
3212
  },
3070
-
3213
+
3071
3214
  // Create a module, attached to the application
3072
3215
  module: function(moduleNames, moduleDefinition) {
3073
-
3216
+
3074
3217
  // Overwrite the module class if the user specifies one
3075
3218
  var ModuleClass = Marionette.Module.getClass(moduleDefinition);
3076
-
3219
+
3077
3220
  // slice the args, and add this application object as the
3078
3221
  // first argument of the array
3079
3222
  var args = slice.call(arguments);
3080
3223
  args.unshift(this);
3081
-
3224
+
3082
3225
  // see the Marionette.Module object for more information
3083
3226
  return ModuleClass.create.apply(ModuleClass, args);
3084
3227
  },
3085
-
3228
+
3086
3229
  // Enable easy overriding of the default `RegionManager`
3087
3230
  // for customized region interactions and business-specific
3088
3231
  // view logic for better control over single regions.
3089
3232
  getRegionManager: function() {
3090
3233
  return new Marionette.RegionManager();
3091
3234
  },
3092
-
3235
+
3093
3236
  // Internal method to initialize the regions that have been defined in a
3094
3237
  // `regions` attribute on the application instance
3095
3238
  _initializeRegions: function(options) {
3096
3239
  var regions = _.isFunction(this.regions) ? this.regions(options) : this.regions || {};
3097
-
3240
+
3098
3241
  this._initRegionManager();
3099
-
3242
+
3100
3243
  // Enable users to define `regions` in instance options.
3101
3244
  var optionRegions = Marionette.getOption(options, 'regions');
3102
-
3245
+
3103
3246
  // Enable region options to be a function
3104
3247
  if (_.isFunction(optionRegions)) {
3105
3248
  optionRegions = optionRegions.call(this, options);
3106
3249
  }
3107
-
3250
+
3108
3251
  // Overwrite current regions with those passed in options
3109
3252
  _.extend(regions, optionRegions);
3110
-
3253
+
3111
3254
  this.addRegions(regions);
3112
-
3255
+
3113
3256
  return this;
3114
3257
  },
3115
-
3258
+
3116
3259
  // Internal method to set up the region manager
3117
3260
  _initRegionManager: function() {
3118
3261
  this._regionManager = this.getRegionManager();
3119
-
3262
+
3120
3263
  this.listenTo(this._regionManager, 'before:add:region', function(name) {
3121
3264
  this.triggerMethod('before:add:region', name);
3122
3265
  });
3123
-
3266
+
3124
3267
  this.listenTo(this._regionManager, 'add:region', function(name, region) {
3125
3268
  this[name] = region;
3126
3269
  this.triggerMethod('add:region', name, region);
3127
3270
  });
3128
-
3271
+
3129
3272
  this.listenTo(this._regionManager, 'before:remove:region', function(name) {
3130
3273
  this.triggerMethod('before:remove:region', name);
3131
3274
  });
3132
-
3275
+
3133
3276
  this.listenTo(this._regionManager, 'remove:region', function(name, region) {
3134
3277
  delete this[name];
3135
3278
  this.triggerMethod('remove:region', name, region);
3136
3279
  });
3137
3280
  },
3138
-
3281
+
3139
3282
  // Internal method to setup the Wreqr.radio channel
3140
3283
  _initChannel: function() {
3141
3284
  this.channelName = _.result(this, 'channelName') || 'global';
@@ -3144,23 +3287,23 @@
3144
3287
  this.commands = _.result(this, 'commands') || this.channel.commands;
3145
3288
  this.reqres = _.result(this, 'reqres') || this.channel.reqres;
3146
3289
  },
3147
-
3290
+
3148
3291
  // import the `triggerMethod` to trigger events with corresponding
3149
3292
  // methods if the method exists
3150
3293
  triggerMethod: Marionette.triggerMethod,
3151
-
3294
+
3152
3295
  // Proxy `getOption` to enable getting options from this or this.options by name.
3153
3296
  getOption: Marionette.proxyGetOption
3154
3297
  });
3155
-
3298
+
3156
3299
  // Copy the `extend` function used by Backbone's classes
3157
3300
  Marionette.Application.extend = Marionette.extend;
3158
-
3301
+
3159
3302
  /* jshint maxparams: 9 */
3160
-
3303
+
3161
3304
  // Module
3162
3305
  // ------
3163
-
3306
+
3164
3307
  // A simple module system, used to create privacy and encapsulation in
3165
3308
  // Marionette applications
3166
3309
  Marionette.Module = function(moduleName, app, options) {
@@ -3169,52 +3312,52 @@
3169
3312
  // Allow for a user to overide the initialize
3170
3313
  // for a given module instance.
3171
3314
  this.initialize = options.initialize || this.initialize;
3172
-
3315
+
3173
3316
  // Set up an internal store for sub-modules.
3174
3317
  this.submodules = {};
3175
-
3318
+
3176
3319
  this._setupInitializersAndFinalizers();
3177
-
3320
+
3178
3321
  // Set an internal reference to the app
3179
3322
  // within a module.
3180
3323
  this.app = app;
3181
-
3182
- // By default modules start with their parents.
3183
- this.startWithParent = true;
3184
-
3324
+
3185
3325
  if (_.isFunction(this.initialize)) {
3186
3326
  this.initialize(moduleName, app, this.options);
3187
3327
  }
3188
3328
  };
3189
-
3329
+
3190
3330
  Marionette.Module.extend = Marionette.extend;
3191
-
3331
+
3192
3332
  // Extend the Module prototype with events / listenTo, so that the module
3193
3333
  // can be used as an event aggregator or pub/sub.
3194
3334
  _.extend(Marionette.Module.prototype, Backbone.Events, {
3195
-
3335
+
3336
+ // By default modules start with their parents.
3337
+ startWithParent: true,
3338
+
3196
3339
  // Initialize is an empty function by default. Override it with your own
3197
3340
  // initialization logic when extending Marionette.Module.
3198
3341
  initialize: function() {},
3199
-
3342
+
3200
3343
  // Initializer for a specific module. Initializers are run when the
3201
3344
  // module's `start` method is called.
3202
3345
  addInitializer: function(callback) {
3203
3346
  this._initializerCallbacks.add(callback);
3204
3347
  },
3205
-
3348
+
3206
3349
  // Finalizers are run when a module is stopped. They are used to teardown
3207
3350
  // and finalize any variables, references, events and other code that the
3208
3351
  // module had set up.
3209
3352
  addFinalizer: function(callback) {
3210
3353
  this._finalizerCallbacks.add(callback);
3211
3354
  },
3212
-
3355
+
3213
3356
  // Start the module, and run all of its initializers
3214
3357
  start: function(options) {
3215
3358
  // Prevent re-starting a module that is already started
3216
3359
  if (this._isInitialized) { return; }
3217
-
3360
+
3218
3361
  // start the sub-modules (depth-first hierarchy)
3219
3362
  _.each(this.submodules, function(mod) {
3220
3363
  // check to see if we should start the sub-module with this parent
@@ -3222,51 +3365,51 @@
3222
3365
  mod.start(options);
3223
3366
  }
3224
3367
  });
3225
-
3368
+
3226
3369
  // run the callbacks to "start" the current module
3227
3370
  this.triggerMethod('before:start', options);
3228
-
3371
+
3229
3372
  this._initializerCallbacks.run(options, this);
3230
3373
  this._isInitialized = true;
3231
-
3374
+
3232
3375
  this.triggerMethod('start', options);
3233
3376
  },
3234
-
3377
+
3235
3378
  // Stop this module by running its finalizers and then stop all of
3236
3379
  // the sub-modules for this module
3237
3380
  stop: function() {
3238
3381
  // if we are not initialized, don't bother finalizing
3239
3382
  if (!this._isInitialized) { return; }
3240
3383
  this._isInitialized = false;
3241
-
3384
+
3242
3385
  this.triggerMethod('before:stop');
3243
-
3386
+
3244
3387
  // stop the sub-modules; depth-first, to make sure the
3245
3388
  // sub-modules are stopped / finalized before parents
3246
3389
  _.each(this.submodules, function(mod) { mod.stop(); });
3247
-
3390
+
3248
3391
  // run the finalizers
3249
3392
  this._finalizerCallbacks.run(undefined, this);
3250
-
3393
+
3251
3394
  // reset the initializers and finalizers
3252
3395
  this._initializerCallbacks.reset();
3253
3396
  this._finalizerCallbacks.reset();
3254
-
3397
+
3255
3398
  this.triggerMethod('stop');
3256
3399
  },
3257
-
3400
+
3258
3401
  // Configure the module with a definition function and any custom args
3259
3402
  // that are to be passed in to the definition function
3260
3403
  addDefinition: function(moduleDefinition, customArgs) {
3261
3404
  this._runModuleDefinition(moduleDefinition, customArgs);
3262
3405
  },
3263
-
3406
+
3264
3407
  // Internal method: run the module definition function with the correct
3265
3408
  // arguments
3266
3409
  _runModuleDefinition: function(definition, customArgs) {
3267
3410
  // If there is no definition short circut the method.
3268
3411
  if (!definition) { return; }
3269
-
3412
+
3270
3413
  // build the correct list of arguments for the module definition
3271
3414
  var args = _.flatten([
3272
3415
  this,
@@ -3276,10 +3419,10 @@
3276
3419
  Backbone.$, _,
3277
3420
  customArgs
3278
3421
  ]);
3279
-
3422
+
3280
3423
  definition.apply(this, args);
3281
3424
  },
3282
-
3425
+
3283
3426
  // Internal method: set up new copies of initializers and finalizers.
3284
3427
  // Calling this method will wipe out all existing initializers and
3285
3428
  // finalizers.
@@ -3287,52 +3430,52 @@
3287
3430
  this._initializerCallbacks = new Marionette.Callbacks();
3288
3431
  this._finalizerCallbacks = new Marionette.Callbacks();
3289
3432
  },
3290
-
3433
+
3291
3434
  // import the `triggerMethod` to trigger events with corresponding
3292
3435
  // methods if the method exists
3293
3436
  triggerMethod: Marionette.triggerMethod
3294
3437
  });
3295
-
3438
+
3296
3439
  // Class methods to create modules
3297
3440
  _.extend(Marionette.Module, {
3298
-
3441
+
3299
3442
  // Create a module, hanging off the app parameter as the parent object.
3300
3443
  create: function(app, moduleNames, moduleDefinition) {
3301
3444
  var module = app;
3302
-
3445
+
3303
3446
  // get the custom args passed in after the module definition and
3304
3447
  // get rid of the module name and definition function
3305
3448
  var customArgs = slice.call(arguments);
3306
3449
  customArgs.splice(0, 3);
3307
-
3450
+
3308
3451
  // Split the module names and get the number of submodules.
3309
3452
  // i.e. an example module name of `Doge.Wow.Amaze` would
3310
3453
  // then have the potential for 3 module definitions.
3311
3454
  moduleNames = moduleNames.split('.');
3312
3455
  var length = moduleNames.length;
3313
-
3456
+
3314
3457
  // store the module definition for the last module in the chain
3315
3458
  var moduleDefinitions = [];
3316
3459
  moduleDefinitions[length - 1] = moduleDefinition;
3317
-
3460
+
3318
3461
  // Loop through all the parts of the module definition
3319
3462
  _.each(moduleNames, function(moduleName, i) {
3320
3463
  var parentModule = module;
3321
3464
  module = this._getModule(parentModule, moduleName, app, moduleDefinition);
3322
3465
  this._addModuleDefinition(parentModule, module, moduleDefinitions[i], customArgs);
3323
3466
  }, this);
3324
-
3467
+
3325
3468
  // Return the last module in the definition chain
3326
3469
  return module;
3327
3470
  },
3328
-
3471
+
3329
3472
  _getModule: function(parentModule, moduleName, app, def, args) {
3330
3473
  var options = _.extend({}, def);
3331
3474
  var ModuleClass = this.getClass(def);
3332
-
3475
+
3333
3476
  // Get an existing module of this name if we have one
3334
3477
  var module = parentModule[moduleName];
3335
-
3478
+
3336
3479
  if (!module) {
3337
3480
  // Create a new module if we don't have one
3338
3481
  module = new ModuleClass(moduleName, app, options);
@@ -3340,10 +3483,10 @@
3340
3483
  // store the module on the parent
3341
3484
  parentModule.submodules[moduleName] = module;
3342
3485
  }
3343
-
3486
+
3344
3487
  return module;
3345
3488
  },
3346
-
3489
+
3347
3490
  // ## Module Classes
3348
3491
  //
3349
3492
  // Module classes can be used as an alternative to the define pattern.
@@ -3352,71 +3495,71 @@
3352
3495
  // This allows module lifecyle events like `onStart` and `onStop` to be called directly.
3353
3496
  getClass: function(moduleDefinition) {
3354
3497
  var ModuleClass = Marionette.Module;
3355
-
3498
+
3356
3499
  if (!moduleDefinition) {
3357
3500
  return ModuleClass;
3358
3501
  }
3359
-
3502
+
3360
3503
  // If all of the module's functionality is defined inside its class,
3361
3504
  // then the class can be passed in directly. `MyApp.module("Foo", FooModule)`.
3362
3505
  if (moduleDefinition.prototype instanceof ModuleClass) {
3363
3506
  return moduleDefinition;
3364
3507
  }
3365
-
3508
+
3366
3509
  return moduleDefinition.moduleClass || ModuleClass;
3367
3510
  },
3368
-
3511
+
3369
3512
  // Add the module definition and add a startWithParent initializer function.
3370
3513
  // This is complicated because module definitions are heavily overloaded
3371
3514
  // and support an anonymous function, module class, or options object
3372
3515
  _addModuleDefinition: function(parentModule, module, def, args) {
3373
3516
  var fn = this._getDefine(def);
3374
3517
  var startWithParent = this._getStartWithParent(def, module);
3375
-
3518
+
3376
3519
  if (fn) {
3377
3520
  module.addDefinition(fn, args);
3378
3521
  }
3379
-
3522
+
3380
3523
  this._addStartWithParent(parentModule, module, startWithParent);
3381
3524
  },
3382
-
3525
+
3383
3526
  _getStartWithParent: function(def, module) {
3384
3527
  var swp;
3385
-
3528
+
3386
3529
  if (_.isFunction(def) && (def.prototype instanceof Marionette.Module)) {
3387
3530
  swp = module.constructor.prototype.startWithParent;
3388
3531
  return _.isUndefined(swp) ? true : swp;
3389
3532
  }
3390
-
3533
+
3391
3534
  if (_.isObject(def)) {
3392
3535
  swp = def.startWithParent;
3393
3536
  return _.isUndefined(swp) ? true : swp;
3394
3537
  }
3395
-
3538
+
3396
3539
  return true;
3397
3540
  },
3398
-
3541
+
3399
3542
  _getDefine: function(def) {
3400
3543
  if (_.isFunction(def) && !(def.prototype instanceof Marionette.Module)) {
3401
3544
  return def;
3402
3545
  }
3403
-
3546
+
3404
3547
  if (_.isObject(def)) {
3405
3548
  return def.define;
3406
3549
  }
3407
-
3550
+
3408
3551
  return null;
3409
3552
  },
3410
-
3553
+
3411
3554
  _addStartWithParent: function(parentModule, module, startWithParent) {
3412
3555
  module.startWithParent = module.startWithParent && startWithParent;
3413
-
3556
+
3414
3557
  if (!module.startWithParent || !!module.startWithParentIsConfigured) {
3415
3558
  return;
3416
3559
  }
3417
-
3560
+
3418
3561
  module.startWithParentIsConfigured = true;
3419
-
3562
+
3420
3563
  parentModule.addInitializer(function(options) {
3421
3564
  if (module.startWithParent) {
3422
3565
  module.start(options);
@@ -3424,7 +3567,7 @@
3424
3567
  });
3425
3568
  }
3426
3569
  });
3427
-
3570
+
3428
3571
 
3429
3572
  return Marionette;
3430
3573
  }));