marionette-rails 0.9.3 → 0.9.4.1

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