marionette-rails 0.9.3 → 0.9.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- // Backbone.Marionette v0.9.3
1
+ // Backbone.Marionette v0.9.4
2
2
  //
3
3
  // Copyright (C)2012 Derick Bailey, Muted Solutions, LLC
4
4
  // Distributed Under MIT License
@@ -9,27 +9,27 @@
9
9
  Backbone.Marionette = (function(Backbone, _, $){
10
10
  var Marionette = {};
11
11
 
12
- // BindTo: Event Binding
13
- // ---------------------
12
+ // EventBinder
13
+ // -----------
14
14
 
15
- // BindTo facilitates the binding and unbinding of events
15
+ // The event binder facilitates the binding and unbinding of events
16
16
  // from objects that extend `Backbone.Events`. It makes
17
17
  // unbinding events, even with anonymous callback functions,
18
18
  // easy.
19
19
  //
20
- // Thanks to Johnny Oshika for this code.
21
- // http://stackoverflow.com/questions/7567404/backbone-js-repopulate-or-recreate-the-view/7607853#7607853
20
+ // Inspired by [Johnny Oshika](http://stackoverflow.com/questions/7567404/backbone-js-repopulate-or-recreate-the-view/7607853#7607853)
22
21
 
23
- Marionette.BindTo = {
22
+ Marionette.EventBinder = function(){
23
+ this._eventBindings = [];
24
+ };
24
25
 
26
+ _.extend(Marionette.EventBinder.prototype, {
25
27
  // Store the event binding in array so it can be unbound
26
28
  // easily, at a later point in time.
27
29
  bindTo: function (obj, eventName, callback, context) {
28
30
  context = context || this;
29
31
  obj.on(eventName, callback, context);
30
32
 
31
- if (!this.bindings) { this.bindings = []; }
32
-
33
33
  var binding = {
34
34
  obj: obj,
35
35
  eventName: eventName,
@@ -37,7 +37,7 @@ Marionette.BindTo = {
37
37
  context: context
38
38
  }
39
39
 
40
- this.bindings.push(binding);
40
+ this._eventBindings.push(binding);
41
41
 
42
42
  return binding;
43
43
  },
@@ -46,7 +46,7 @@ Marionette.BindTo = {
46
46
  // returned from the `bindTo` method call.
47
47
  unbindFrom: function(binding){
48
48
  binding.obj.off(binding.eventName, binding.callback, binding.context);
49
- this.bindings = _.reject(this.bindings, function(bind){return bind === binding});
49
+ this._eventBindings = _.reject(this._eventBindings, function(bind){return bind === binding});
50
50
  },
51
51
 
52
52
  // Unbind all of the events that we have stored.
@@ -55,13 +55,15 @@ Marionette.BindTo = {
55
55
 
56
56
  // The `unbindFrom` call removes elements from the array
57
57
  // while it is being iterated, so clone it first.
58
- var bindings = _.map(this.bindings, _.identity);
58
+ var bindings = _.map(this._eventBindings, _.identity);
59
59
  _.each(bindings, function (binding, index) {
60
60
  that.unbindFrom(binding);
61
61
  });
62
62
  }
63
- };
63
+ });
64
64
 
65
+ // Copy the `extend` function used by Backbone's classes
66
+ Marionette.EventBinder.extend = Backbone.View.extend;
65
67
 
66
68
  // Marionette.View
67
69
  // ---------------
@@ -70,6 +72,10 @@ Marionette.BindTo = {
70
72
  Marionette.View = Backbone.View.extend({
71
73
  constructor: function(){
72
74
  Backbone.View.prototype.constructor.apply(this, arguments);
75
+
76
+ var eventBinder = new Marionette.EventBinder();
77
+ _.extend(this, eventBinder);
78
+
73
79
  this.bindTo(this, "show", this.onShowCalled, this);
74
80
  },
75
81
 
@@ -182,11 +188,31 @@ Marionette.View = Backbone.View.extend({
182
188
  this.trigger('close');
183
189
  this.unbindAll();
184
190
  this.unbind();
191
+ },
192
+
193
+ // This method binds the elements specified in the "ui" hash inside the view's code with
194
+ // the associated jQuery selectors.
195
+ bindUIElements: function(){
196
+ if (!this.ui) { return; }
197
+
198
+ var that = this;
199
+
200
+ if (!this.uiBindings) {
201
+ // We want to store the ui hash in uiBindings, since afterwards the values in the ui hash
202
+ // will be overridden with jQuery selectors.
203
+ this.uiBindings = this.ui;
204
+ }
205
+
206
+ // refreshing the associated selectors since they should point to the newly rendered elements.
207
+ this.ui = {};
208
+ _.each(_.keys(this.uiBindings), function(key) {
209
+ var selector = that.uiBindings[key];
210
+ that.ui[key] = that.$(selector);
211
+ });
185
212
  }
213
+
186
214
  });
187
215
 
188
- // Copy the features of `BindTo`
189
- _.extend(Marionette.View.prototype, Marionette.BindTo);
190
216
 
191
217
  // Item View
192
218
  // ---------
@@ -223,6 +249,7 @@ Marionette.ItemView = Marionette.View.extend({
223
249
  var template = this.getTemplate();
224
250
  var html = Marionette.Renderer.render(template, data);
225
251
  this.$el.html(html);
252
+ this.bindUIElements();
226
253
 
227
254
  if (this.onRender){ this.onRender(); }
228
255
  this.trigger("render", this);
@@ -297,6 +324,7 @@ Marionette.CollectionView = Marionette.View.extend({
297
324
  // the collection view.
298
325
  render: function(){
299
326
  this.triggerBeforeRender();
327
+ this.closeEmptyView();
300
328
  this.closeChildren();
301
329
 
302
330
  if (this.collection && this.collection.length > 0) {
@@ -324,8 +352,8 @@ Marionette.CollectionView = Marionette.View.extend({
324
352
  // empty
325
353
  showEmptyView: function(){
326
354
  var EmptyView = this.options.emptyView || this.emptyView;
327
- if (EmptyView){
328
- this.showingEmptyView = true;
355
+ if (EmptyView && !this._showingEmptyView){
356
+ this._showingEmptyView = true;
329
357
  var model = new Backbone.Model();
330
358
  this.addItemView(model, EmptyView, 0);
331
359
  }
@@ -335,9 +363,9 @@ Marionette.CollectionView = Marionette.View.extend({
335
363
  // if one exists. Called when a collection view has been
336
364
  // rendered empty, and then an item is added to the collection.
337
365
  closeEmptyView: function(){
338
- if (this.showingEmptyView){
366
+ if (this._showingEmptyView){
339
367
  this.closeChildren();
340
- delete this.showingEmptyView;
368
+ delete this._showingEmptyView;
341
369
  }
342
370
  },
343
371
 
@@ -422,7 +450,7 @@ Marionette.CollectionView = Marionette.View.extend({
422
450
  delete this.children[item.cid];
423
451
  }
424
452
 
425
- if (this.collection.length === 0){
453
+ if (!this.collection || this.collection.length === 0){
426
454
  this.showEmptyView();
427
455
  }
428
456
 
@@ -511,6 +539,9 @@ Marionette.CompositeView = Marionette.CollectionView.extend({
511
539
 
512
540
  var html = this.renderModel();
513
541
  this.$el.html(html);
542
+ // the ui bindings is done here and not at the end of render since they should be
543
+ // available before the collection is rendered.
544
+ this.bindUIElements();
514
545
  this.trigger("composite:model:rendered");
515
546
  this.trigger("render");
516
547
 
@@ -554,6 +585,12 @@ Marionette.CompositeView = Marionette.CollectionView.extend({
554
585
  } else {
555
586
  if (containerView.itemViewContainer){
556
587
  container = containerView.$(_.result(containerView, "itemViewContainer"));
588
+
589
+ if (container.length <= 0) {
590
+ var err = new Error("Missing `itemViewContainer`");
591
+ err.name = "ItemViewContainerMissingError";
592
+ throw err;
593
+ }
557
594
  } else {
558
595
  container = containerView.$el;
559
596
  }
@@ -579,7 +616,8 @@ Marionette.CompositeView = Marionette.CollectionView.extend({
579
616
  Marionette.Region = function(options){
580
617
  this.options = options || {};
581
618
 
582
- _.extend(this, options);
619
+ var eventBinder = new Marionette.EventBinder();
620
+ _.extend(this, eventBinder, options);
583
621
 
584
622
  if (!this.el){
585
623
  var err = new Error("An 'el' must be specified");
@@ -600,7 +638,6 @@ _.extend(Marionette.Region.prototype, Backbone.Events, {
600
638
  // `onShow` and `close` method on your view, just after showing
601
639
  // or just before closing the view, respectively.
602
640
  show: function(view){
603
- var that = this;
604
641
 
605
642
  this.ensureEl();
606
643
  this.close();
@@ -659,9 +696,6 @@ _.extend(Marionette.Region.prototype, Backbone.Events, {
659
696
  // Copy the `extend` function used by Backbone's classes
660
697
  Marionette.Region.extend = Backbone.View.extend;
661
698
 
662
- // Copy the features of `BindTo`
663
- _.extend(Marionette.Region.prototype, Marionette.BindTo);
664
-
665
699
  // Layout
666
700
  // ------
667
701
 
@@ -672,6 +706,8 @@ _.extend(Marionette.Region.prototype, Marionette.BindTo);
672
706
  // attaches `Region` instances to the specified `regions`.
673
707
  // Used for composite view management and sub-application areas.
674
708
  Marionette.Layout = Marionette.ItemView.extend({
709
+ regionType: Backbone.Marionette.Region,
710
+
675
711
  constructor: function () {
676
712
  Backbone.Marionette.ItemView.apply(this, arguments);
677
713
  this.initializeRegions();
@@ -716,9 +752,18 @@ Marionette.Layout = Marionette.ItemView.extend({
716
752
  }
717
753
 
718
754
  var that = this;
719
- _.each(this.regions, function (selector, name) {
755
+ _.each(this.regions, function (region, name) {
756
+ if ( typeof region != 'string'
757
+ && typeof region.selector != 'string' ) {
758
+ throw new Exception('Region must be specified as a selector ' +
759
+ 'string or an object with selector property');
760
+ }
720
761
 
721
- var regionManager = new Backbone.Marionette.Region({
762
+ selector = typeof region === 'string' ? region : region.selector;
763
+ var regionType = typeof region.regionType === 'undefined'
764
+ ? that.regionType : region.regionType
765
+
766
+ var regionManager = new regionType({
722
767
  el: selector,
723
768
  getEl: function(selector){
724
769
  return that.$(selector);
@@ -734,9 +779,13 @@ Marionette.Layout = Marionette.ItemView.extend({
734
779
  // Re-initialize all of the regions by updating the `el` that
735
780
  // they point to
736
781
  reInitializeRegions: function(){
737
- _.each(this.regionManagers, function(region){
738
- delete region.$el;
739
- });
782
+ if (this.regionManagers && _.size(this.regionManagers)===0){
783
+ this.initializeRegions();
784
+ } else {
785
+ _.each(this.regionManagers, function(region){
786
+ delete region.$el;
787
+ });
788
+ }
740
789
  },
741
790
 
742
791
  // Close all of the regions that have been opened by
@@ -770,7 +819,10 @@ Marionette.Layout = Marionette.ItemView.extend({
770
819
  Marionette.Application = function(options){
771
820
  this.initCallbacks = new Marionette.Callbacks();
772
821
  this.vent = new Marionette.EventAggregator();
773
- _.extend(this, options);
822
+ this.submodules = {};
823
+
824
+ var eventBinder = new Marionette.EventBinder();
825
+ _.extend(this, eventBinder, options);
774
826
  };
775
827
 
776
828
  _.extend(Marionette.Application.prototype, Backbone.Events, {
@@ -816,19 +868,29 @@ _.extend(Marionette.Application.prototype, Backbone.Events, {
816
868
  }
817
869
  },
818
870
 
871
+ // Removes a region from your app.
872
+ // Accepts the regions name
873
+ // removeRegion('myRegion')
874
+ removeRegion: function(region) {
875
+ this[region].close();
876
+ delete this[region]
877
+ },
878
+
819
879
  // Create a module, attached to the application
820
- module: function(){
880
+ module: function(moduleNames, moduleDefinition){
881
+ // slice the args, and add this application object as the
882
+ // first argument of the array
883
+ var args = slice.call(arguments);
884
+ args.unshift(this);
885
+
821
886
  // see the Marionette.Module object for more information
822
- return Marionette.Module.create.apply(this, arguments);
887
+ return Marionette.Module.create.apply(Marionette.Module, args);
823
888
  }
824
889
  });
825
890
 
826
891
  // Copy the `extend` function used by Backbone's classes
827
892
  Marionette.Application.extend = Backbone.View.extend;
828
893
 
829
- // Copy the features of `BindTo`
830
- _.extend(Marionette.Application.prototype, Marionette.BindTo);
831
-
832
894
  // AppRouter
833
895
  // ---------
834
896
 
@@ -901,72 +963,187 @@ Marionette.AppRouter = Backbone.Router.extend({
901
963
 
902
964
  // A simple module system, used to create privacy and encapsulation in
903
965
  // Marionette applications
904
- Marionette.Module = function(){};
966
+ Marionette.Module = function(moduleName, app, customArgs){
967
+ this.moduleName = moduleName;
968
+
969
+ // store sub-modules
970
+ this.submodules = {};
971
+
972
+ // callbacks for initializers and finalizers
973
+ this._initializerCallbacks = new Marionette.Callbacks();
974
+ this._finalizerCallbacks = new Marionette.Callbacks();
975
+
976
+ // store the configuration for this module
977
+ this._config = {};
978
+ this._config.app = app;
979
+ this._config.customArgs = customArgs;
980
+ this._config.definitions = [];
981
+
982
+ // extend this module with an event binder
983
+ var eventBinder = new Marionette.EventBinder();
984
+ _.extend(this, eventBinder);
985
+ };
905
986
 
906
987
  // Extend the Module prototype with events / bindTo, so that the module
907
988
  // can be used as an event aggregator or pub/sub.
908
- _.extend(Marionette.Module.prototype, Backbone.Events, Marionette.BindTo);
989
+ _.extend(Marionette.Module.prototype, Backbone.Events, {
990
+
991
+ // Initializer for a specific module. Initializers are run when the
992
+ // module's `start` method is called.
993
+ addInitializer: function(callback){
994
+ this._initializerCallbacks.add(callback);
995
+ },
996
+
997
+ // Finalizers are run when a module is stopped. They are used to teardown
998
+ // and finalize any variables, references, events and other code that the
999
+ // module had set up.
1000
+ addFinalizer: function(callback){
1001
+ this._finalizerCallbacks.add(callback);
1002
+ },
1003
+
1004
+ // Start the module, and run all of it's initializers
1005
+ start: function(options){
1006
+ this._runModuleDefinition();
1007
+ this._initializerCallbacks.run(options, this);
1008
+ this._isInitialized = true;
1009
+
1010
+ // start the sub-modules
1011
+ if (this.submodules){
1012
+ _.each(this.submodules, function(mod){
1013
+ mod.start();
1014
+ });
1015
+ }
1016
+ },
1017
+
1018
+ // Stop this module by running its finalizers and then stop all of
1019
+ // the sub-modules for this module
1020
+ stop: function(){
1021
+ // if we are not initialized, don't bother finalizing
1022
+ if (!this._isInitialized){ return; }
1023
+ this._isInitialized = false;
1024
+
1025
+ this._finalizerCallbacks.run();
1026
+ // stop the sub-modules
1027
+ _.each(this.submodules, function(mod){ mod.stop(); });
1028
+ },
1029
+
1030
+ // Configure the module with a definition function and any custom args
1031
+ // that are to be passed in to the definition function
1032
+ addDefinition: function(moduleDefinition){
1033
+ this._config.definitions.push(moduleDefinition);
1034
+ },
1035
+
1036
+ // Internal method: run the module definition function with the correct
1037
+ // arguments
1038
+ _runModuleDefinition: function(){
1039
+ if (this._config.definitions.length === 0) { return; }
1040
+
1041
+ // build the correct list of arguments for the module definition
1042
+ var args = _.flatten([
1043
+ this,
1044
+ this._config.app,
1045
+ Backbone,
1046
+ Marionette,
1047
+ $, _,
1048
+ this._config.customArgs
1049
+ ]);
1050
+
1051
+ // run the module definition function with the correct args
1052
+ var definitionCount = this._config.definitions.length-1;
1053
+ for(var i=0; i <= definitionCount; i++){
1054
+
1055
+ var definition = this._config.definitions[i];
1056
+ definition.apply(this, args);
1057
+
1058
+ }
1059
+ }
1060
+ });
909
1061
 
910
1062
  // Function level methods to create modules
911
1063
  _.extend(Marionette.Module, {
912
1064
 
913
- // Create a module, hanging off 'this' as the parent object. This
914
- // method must be called with .apply or .create
915
- create: function(moduleNames, moduleDefinition){
916
- var moduleName, module, moduleOverride;
917
- var parentObject = this;
918
- var parentModule = this;
1065
+ // Create a module, hanging off the app parameter as the parent object.
1066
+ create: function(app, moduleNames, moduleDefinition){
1067
+ var that = this;
1068
+ var parentModule = app;
919
1069
  var moduleNames = moduleNames.split(".");
920
1070
 
1071
+ // get the custom args passed in after the module definition and
1072
+ // get rid of the module name and definition function
1073
+ var customArgs = slice.apply(arguments);
1074
+ customArgs.splice(0, 3);
1075
+
921
1076
  // Loop through all the parts of the module definition
922
1077
  var length = moduleNames.length;
923
- for(var i = 0; i < length; i++){
1078
+ _.each(moduleNames, function(moduleName, i){
924
1079
  var isLastModuleInChain = (i === length-1);
925
1080
 
926
- // Get the module name, and check if it exists on
927
- // the current parent already
928
- moduleName = moduleNames[i];
929
- module = parentModule[moduleName];
930
-
931
- // Create a new module if we don't have one already
1081
+ // Get an existing module of this name if we have one
1082
+ var module = parentModule[moduleName];
932
1083
  if (!module){
933
- module = new Marionette.Module();
934
- }
935
-
936
- // Check to see if we need to run the definition
937
- // for the module. Only run the definition if one
938
- // is supplied, and if we're at the last segment
939
- // of the "Module.Name" chain.
940
- if (isLastModuleInChain && moduleDefinition){
941
- // get the custom args passed in after the module definition and
942
- // get rid of the module name and definition function
943
- var customArgs = slice.apply(arguments);
944
- customArgs.shift();
945
- customArgs.shift();
946
-
947
- // final arguments list for the module definition
948
- var argsArray = [module, parentObject, Backbone, Marionette, jQuery, _, customArgs];
949
-
950
- // flatten the nested array
951
- var args = _.flatten(argsArray);
952
-
953
- // ensure the module definition's `this` is the module itself
954
- moduleDefinition.apply(module, args);
1084
+ // Create a new module if we don't have one
1085
+ module = new Marionette.Module(moduleName, app, customArgs);
1086
+ parentModule[moduleName] = module;
1087
+ // store the module on the parent
1088
+ parentModule.submodules[moduleName] = module;
955
1089
  }
956
1090
 
957
- // If the defined module is not what we are
958
- // currently storing as the module, replace it
959
- if (parentModule[moduleName] !== module){
960
- parentModule[moduleName] = module;
1091
+ // Only add a module definition and initializer when this is
1092
+ // the last module in a "parent.child.grandchild" hierarchy of
1093
+ // module names
1094
+ if (isLastModuleInChain ){
1095
+ that._createModuleDefinition(module, moduleDefinition, app);
961
1096
  }
962
1097
 
963
1098
  // Reset the parent module so that the next child
964
1099
  // in the list will be added to the correct parent
965
1100
  parentModule = module;
966
- }
1101
+ });
967
1102
 
968
1103
  // Return the last module in the definition chain
969
- return module;
1104
+ return parentModule;
1105
+ },
1106
+
1107
+ _createModuleDefinition: function(module, moduleDefinition, app){
1108
+ var moduleOptions = this._getModuleDefinitionOptions(moduleDefinition);
1109
+
1110
+ // add the module definition
1111
+ if (moduleOptions.definition){
1112
+ module.addDefinition(moduleOptions.definition);
1113
+ }
1114
+
1115
+ if (moduleOptions.startWithApp){
1116
+ // start the module when the app starts
1117
+ app.addInitializer(function(options){
1118
+ module.start(options);
1119
+ });
1120
+ }
1121
+ },
1122
+
1123
+ _getModuleDefinitionOptions: function(moduleDefinition){
1124
+ // default to starting the module with the app
1125
+ var options = { startWithApp: true };
1126
+
1127
+ // short circuit if we don't have a module definition
1128
+ if (!moduleDefinition){ return options; }
1129
+
1130
+ if (_.isFunction(moduleDefinition)){
1131
+ // if the definition is a function, assign it directly
1132
+ // and use the defaults
1133
+ options.definition = moduleDefinition;
1134
+
1135
+ } else {
1136
+
1137
+ // the definition is an object. grab the "define" attribute
1138
+ // and the "startWithApp" attribute, as set the options
1139
+ // appropriately
1140
+ options.definition = moduleDefinition.define;
1141
+ if (moduleDefinition.hasOwnProperty("startWithApp")){
1142
+ options.startWithApp = moduleDefinition.startWithApp;
1143
+ }
1144
+ }
1145
+
1146
+ return options;
970
1147
  }
971
1148
  });
972
1149
 
@@ -1124,18 +1301,27 @@ _.extend(Marionette.Callbacks.prototype, {
1124
1301
 
1125
1302
  // A pub-sub object that can be used to decouple various parts
1126
1303
  // of an application through event-driven architecture.
1127
- Marionette.EventAggregator = function(options){
1128
- _.extend(this, options);
1129
- };
1304
+ Marionette.EventAggregator = Marionette.EventBinder.extend({
1305
+
1306
+ // Extend any provided options directly on to the event binder
1307
+ constructor: function(options){
1308
+ Marionette.EventBinder.apply(this, arguments);
1309
+ _.extend(this, options);
1310
+ },
1130
1311
 
1131
- _.extend(Marionette.EventAggregator.prototype, Backbone.Events, Marionette.BindTo, {
1132
- // Assumes the event aggregator itself is the
1133
- // object being bound to.
1312
+ // Override the `bindTo` method to ensure that the event aggregator
1313
+ // is used as the event binding storage
1134
1314
  bindTo: function(eventName, callback, context){
1135
- return Marionette.BindTo.bindTo.call(this, this, eventName, callback, context);
1315
+ return Marionette.EventBinder.prototype.bindTo.call(this, this, eventName, callback, context);
1136
1316
  }
1137
1317
  });
1138
1318
 
1319
+ // Copy the basic Backbone.Events on to the event aggregator
1320
+ _.extend(Marionette.EventAggregator.prototype, Backbone.Events);
1321
+
1322
+ // Copy the `extend` function used by Backbone's classes
1323
+ Marionette.EventAggregator.extend = Backbone.View.extend;
1324
+
1139
1325
 
1140
1326
  // Helpers
1141
1327
  // -------
@@ -1143,6 +1329,7 @@ _.extend(Marionette.EventAggregator.prototype, Backbone.Events, Marionette.BindT
1143
1329
  // For slicing `arguments` in functions
1144
1330
  var slice = Array.prototype.slice;
1145
1331
 
1332
+
1146
1333
  return Marionette;
1147
1334
  })(Backbone, _, window.jQuery || window.Zepto || window.ender);
1148
1335
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: marionette-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.3
4
+ version: 0.9.4.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-06-28 00:00:00.000000000 Z
12
+ date: 2012-08-02 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails