marionette.modal 1.0.0.6 → 1.0.0.7

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,6 +1,6 @@
1
1
  // MarionetteJS (Backbone.Marionette)
2
2
  // ----------------------------------
3
- // v1.0.2
3
+ // v1.4.1
4
4
  //
5
5
  // Copyright (c)2013 Derick Bailey, Muted Solutions, LLC.
6
6
  // Distributed under MIT license
@@ -19,7 +19,7 @@
19
19
 
20
20
  // Backbone.BabySitter
21
21
  // -------------------
22
- // v0.0.5
22
+ // v0.0.6
23
23
  //
24
24
  // Copyright (c)2013 Derick Bailey, Muted Solutions, LLC.
25
25
  // Distributed under MIT license
@@ -37,14 +37,13 @@ Backbone.ChildViewContainer = (function(Backbone, _){
37
37
  // Container Constructor
38
38
  // ---------------------
39
39
 
40
- var Container = function(initialViews){
40
+ var Container = function(views){
41
41
  this._views = {};
42
42
  this._indexByModel = {};
43
- this._indexByCollection = {};
44
43
  this._indexByCustom = {};
45
44
  this._updateLength();
46
45
 
47
- this._addInitialViews(initialViews);
46
+ _.each(views, this.add, this);
48
47
  };
49
48
 
50
49
  // Container Methods
@@ -54,7 +53,7 @@ Backbone.ChildViewContainer = (function(Backbone, _){
54
53
 
55
54
  // Add a view to this container. Stores the view
56
55
  // by `cid` and makes it searchable by the model
57
- // and/or collection of the view. Optionally specify
56
+ // cid (and model itself). Optionally specify
58
57
  // a custom key to store an retrieve the view.
59
58
  add: function(view, customIndex){
60
59
  var viewCid = view.cid;
@@ -67,11 +66,6 @@ Backbone.ChildViewContainer = (function(Backbone, _){
67
66
  this._indexByModel[view.model.cid] = viewCid;
68
67
  }
69
68
 
70
- // index it by collection
71
- if (view.collection){
72
- this._indexByCollection[view.collection.cid] = viewCid;
73
- }
74
-
75
69
  // index by custom
76
70
  if (customIndex){
77
71
  this._indexByCustom[customIndex] = viewCid;
@@ -81,18 +75,16 @@ Backbone.ChildViewContainer = (function(Backbone, _){
81
75
  },
82
76
 
83
77
  // Find a view by the model that was attached to
84
- // it. Uses the model's `cid` to find it, and
85
- // retrieves the view by it's `cid` from the result
78
+ // it. Uses the model's `cid` to find it.
86
79
  findByModel: function(model){
87
- var viewCid = this._indexByModel[model.cid];
88
- return this.findByCid(viewCid);
80
+ return this.findByModelCid(model.cid);
89
81
  },
90
82
 
91
- // Find a view by the collection that was attached to
92
- // it. Uses the collection's `cid` to find it, and
93
- // retrieves the view by it's `cid` from the result
94
- findByCollection: function(col){
95
- var viewCid = this._indexByCollection[col.cid];
83
+ // Find a view by the `cid` of the model that was attached to
84
+ // it. Uses the model's `cid` to find the view `cid` and
85
+ // retrieve the view using it.
86
+ findByModelCid: function(modelCid){
87
+ var viewCid = this._indexByModel[modelCid];
96
88
  return this.findByCid(viewCid);
97
89
  },
98
90
 
@@ -122,26 +114,13 @@ Backbone.ChildViewContainer = (function(Backbone, _){
122
114
  delete this._indexByModel[view.model.cid];
123
115
  }
124
116
 
125
- // delete collection index
126
- if (view.collection){
127
- delete this._indexByCollection[view.collection.cid];
128
- }
129
-
130
117
  // delete custom index
131
- var cust;
132
-
133
- for (var key in this._indexByCustom){
134
- if (this._indexByCustom.hasOwnProperty(key)){
135
- if (this._indexByCustom[key] === viewCid){
136
- cust = key;
137
- break;
138
- }
118
+ _.any(this._indexByCustom, function(cid, key) {
119
+ if (cid === viewCid) {
120
+ delete this._indexByCustom[key];
121
+ return true;
139
122
  }
140
- }
141
-
142
- if (cust){
143
- delete this._indexByCustom[cust];
144
- }
123
+ }, this);
145
124
 
146
125
  // remove the view from the container
147
126
  delete this._views[viewCid];
@@ -153,44 +132,24 @@ Backbone.ChildViewContainer = (function(Backbone, _){
153
132
  // Call a method on every view in the container,
154
133
  // passing parameters to the call method one at a
155
134
  // time, like `function.call`.
156
- call: function(method, args){
157
- args = Array.prototype.slice.call(arguments, 1);
158
- this.apply(method, args);
135
+ call: function(method){
136
+ this.apply(method, _.tail(arguments));
159
137
  },
160
138
 
161
139
  // Apply a method on every view in the container,
162
140
  // passing parameters to the call method one at a
163
141
  // time, like `function.apply`.
164
142
  apply: function(method, args){
165
- var view;
166
-
167
- // fix for IE < 9
168
- args = args || [];
169
-
170
- _.each(this._views, function(view, key){
143
+ _.each(this._views, function(view){
171
144
  if (_.isFunction(view[method])){
172
- view[method].apply(view, args);
145
+ view[method].apply(view, args || []);
173
146
  }
174
147
  });
175
-
176
148
  },
177
149
 
178
150
  // Update the `.length` attribute on this container
179
151
  _updateLength: function(){
180
152
  this.length = _.size(this._views);
181
- },
182
-
183
- // set up an initial list of views
184
- _addInitialViews: function(views){
185
- if (!views){ return; }
186
-
187
- var view, i,
188
- length = views.length;
189
-
190
- for (i=0; i<length; i++){
191
- view = views[i];
192
- this.add(view);
193
- }
194
153
  }
195
154
  });
196
155
 
@@ -518,7 +477,7 @@ Marionette.extend = Backbone.Model.extend;
518
477
  // --------------------
519
478
 
520
479
  // Retrieve an object, function or other value from a target
521
- // object or it's `options`, with `options` taking precedence.
480
+ // object or its `options`, with `options` taking precedence.
522
481
  Marionette.getOption = function(target, optionName){
523
482
  if (!target || !optionName){ return; }
524
483
  var value;
@@ -532,21 +491,21 @@ Marionette.getOption = function(target, optionName){
532
491
  return value;
533
492
  };
534
493
 
535
- // Trigger an event and a corresponding method name. Examples:
494
+ // Trigger an event and/or a corresponding method name. Examples:
536
495
  //
537
496
  // `this.triggerMethod("foo")` will trigger the "foo" event and
538
- // call the "onFoo" method.
497
+ // call the "onFoo" method.
539
498
  //
540
499
  // `this.triggerMethod("foo:bar") will trigger the "foo:bar" event and
541
500
  // call the "onFooBar" method.
542
501
  Marionette.triggerMethod = (function(){
543
-
502
+
544
503
  // split the event name on the :
545
504
  var splitter = /(^|:)(\w)/gi;
546
505
 
547
506
  // take the event section ("section1:section2:section3")
548
507
  // and turn it in to uppercase name
549
- function getEventName(match, prefix, eventName) {
508
+ function getEventName(match, prefix, eventName) {
550
509
  return eventName.toUpperCase();
551
510
  }
552
511
 
@@ -556,8 +515,10 @@ Marionette.triggerMethod = (function(){
556
515
  var methodName = 'on' + event.replace(splitter, getEventName);
557
516
  var method = this[methodName];
558
517
 
559
- // trigger the event
560
- this.trigger.apply(this, arguments);
518
+ // trigger the event, if a trigger method exists
519
+ if(_.isFunction(this.trigger)) {
520
+ this.trigger.apply(this, arguments);
521
+ }
561
522
 
562
523
  // call the onMethodName if it exists
563
524
  if (_.isFunction(method)) {
@@ -577,14 +538,14 @@ Marionette.triggerMethod = (function(){
577
538
  // re-rendered.
578
539
 
579
540
  Marionette.MonitorDOMRefresh = (function(){
580
- // track when the view has been rendered
541
+ // track when the view has been shown in the DOM,
542
+ // using a Marionette.Region (or by other means of triggering "show")
581
543
  function handleShow(view){
582
544
  view._isShown = true;
583
545
  triggerDOMRefresh(view);
584
546
  }
585
547
 
586
- // track when the view has been shown in the DOM,
587
- // using a Marionette.Region (or by other means of triggering "show")
548
+ // track when the view has been rendered
588
549
  function handleRender(view){
589
550
  view._isRendered = true;
590
551
  triggerDOMRefresh(view);
@@ -615,8 +576,8 @@ Marionette.MonitorDOMRefresh = (function(){
615
576
  // Marionette.bindEntityEvents & unbindEntityEvents
616
577
  // ---------------------------
617
578
  //
618
- // These methods are used to bind/unbind a backbone "entity" (collection/model)
619
- // to methods on a target object.
579
+ // These methods are used to bind/unbind a backbone "entity" (collection/model)
580
+ // to methods on a target object.
620
581
  //
621
582
  // The first parameter, `target`, must have a `listenTo` method from the
622
583
  // EventBinder object.
@@ -626,7 +587,7 @@ Marionette.MonitorDOMRefresh = (function(){
626
587
  //
627
588
  // The third parameter is a hash of { "event:name": "eventHandler" }
628
589
  // configuration. Multiple handlers can be separated by a space. A
629
- // function can be supplied instead of a string handler name.
590
+ // function can be supplied instead of a string handler name.
630
591
 
631
592
  (function(Marionette){
632
593
  "use strict";
@@ -658,7 +619,7 @@ Marionette.MonitorDOMRefresh = (function(){
658
619
  var methodNames = methods.split(/\s+/);
659
620
 
660
621
  _.each(methodNames,function(methodName) {
661
- var method = target[method];
622
+ var method = target[methodName];
662
623
  target.stopListening(entity, evt, method, target);
663
624
  });
664
625
  }
@@ -668,7 +629,7 @@ Marionette.MonitorDOMRefresh = (function(){
668
629
  target.stopListening(entity, evt, method, target);
669
630
  }
670
631
 
671
-
632
+
672
633
  // generic looping function
673
634
  function iterateEvents(target, entity, bindings, functionCallback, stringCallback){
674
635
  if (!entity || !bindings) { return; }
@@ -681,7 +642,7 @@ Marionette.MonitorDOMRefresh = (function(){
681
642
  // iterate the bindings and bind them
682
643
  _.each(bindings, function(methods, evt){
683
644
 
684
- // allow for a function as the handler,
645
+ // allow for a function as the handler,
685
646
  // or a list of event names as a string
686
647
  if (_.isFunction(methods)){
687
648
  functionCallback(target, entity, evt, methods);
@@ -691,7 +652,7 @@ Marionette.MonitorDOMRefresh = (function(){
691
652
 
692
653
  });
693
654
  }
694
-
655
+
695
656
  // Export Public API
696
657
  Marionette.bindEntityEvents = function(target, entity, bindings){
697
658
  iterateEvents(target, entity, bindings, bindToFunction, bindFromStrings);
@@ -718,7 +679,7 @@ Marionette.Callbacks = function(){
718
679
  _.extend(Marionette.Callbacks.prototype, {
719
680
 
720
681
  // Add a callback to be executed. Callbacks added here are
721
- // guaranteed to execute, even if they are added after the
682
+ // guaranteed to execute, even if they are added after the
722
683
  // `run` method is called.
723
684
  add: function(callback, contextOverride){
724
685
  this._callbacks.push({cb: callback, ctx: contextOverride});
@@ -729,8 +690,8 @@ _.extend(Marionette.Callbacks.prototype, {
729
690
  });
730
691
  },
731
692
 
732
- // Run all registered callbacks with the context specified.
733
- // Additional callbacks can be added after this has been run
693
+ // Run all registered callbacks with the context specified.
694
+ // Additional callbacks can be added after this has been run
734
695
  // and they will still be executed.
735
696
  run: function(options, context){
736
697
  this._deferred.resolve(context, options);
@@ -742,7 +703,7 @@ _.extend(Marionette.Callbacks.prototype, {
742
703
  var callbacks = this._callbacks;
743
704
  this._deferred = Marionette.$.Deferred();
744
705
  this._callbacks = [];
745
-
706
+
746
707
  _.each(callbacks, function(cb){
747
708
  this.add(cb.cb, cb.ctx);
748
709
  }, this);
@@ -779,7 +740,7 @@ _.extend(Marionette.Controller.prototype, Backbone.Events, {
779
740
  }
780
741
  });
781
742
 
782
- // Region
743
+ // Region
783
744
  // ------
784
745
  //
785
746
  // Manage the visual regions of your composite application. See
@@ -823,6 +784,7 @@ _.extend(Marionette.Region, {
823
784
  // ```
824
785
  //
825
786
  buildRegion: function(regionConfig, defaultRegionType){
787
+
826
788
  var regionIsString = (typeof regionConfig === "string");
827
789
  var regionSelectorIsString = (typeof regionConfig.selector === "string");
828
790
  var regionTypeIsUndefined = (typeof regionConfig.regionType === "undefined");
@@ -833,19 +795,19 @@ _.extend(Marionette.Region, {
833
795
  }
834
796
 
835
797
  var selector, RegionType;
836
-
798
+
837
799
  // get the selector for the region
838
-
800
+
839
801
  if (regionIsString) {
840
802
  selector = regionConfig;
841
- }
803
+ }
842
804
 
843
805
  if (regionConfig.selector) {
844
806
  selector = regionConfig.selector;
845
807
  }
846
808
 
847
809
  // get the type for the region
848
-
810
+
849
811
  if (regionIsType){
850
812
  RegionType = regionConfig;
851
813
  }
@@ -857,7 +819,7 @@ _.extend(Marionette.Region, {
857
819
  if (regionConfig.regionType) {
858
820
  RegionType = regionConfig.regionType;
859
821
  }
860
-
822
+
861
823
  // build the region instance
862
824
  var region = new RegionType({
863
825
  el: selector
@@ -899,18 +861,24 @@ _.extend(Marionette.Region.prototype, Backbone.Events, {
899
861
 
900
862
  this.ensureEl();
901
863
 
902
- if (view !== this.currentView) {
864
+ var isViewClosed = view.isClosed || _.isUndefined(view.$el);
865
+
866
+ var isDifferentView = view !== this.currentView;
867
+
868
+ if (isDifferentView) {
903
869
  this.close();
904
- view.render();
905
- this.open(view);
906
- } else {
907
- view.render();
908
870
  }
909
871
 
910
- Marionette.triggerMethod.call(view, "show");
911
- Marionette.triggerMethod.call(this, "show", view);
872
+ view.render();
873
+
874
+ if (isDifferentView || isViewClosed) {
875
+ this.open(view);
876
+ }
912
877
 
913
878
  this.currentView = view;
879
+
880
+ Marionette.triggerMethod.call(this, "show", view);
881
+ Marionette.triggerMethod.call(view, "show");
914
882
  },
915
883
 
916
884
  ensureEl: function(){
@@ -946,8 +914,8 @@ _.extend(Marionette.Region.prototype, Backbone.Events, {
946
914
  delete this.currentView;
947
915
  },
948
916
 
949
- // Attach an existing view to the region. This
950
- // will not call `render` or `onShow` for the new view,
917
+ // Attach an existing view to the region. This
918
+ // will not call `render` or `onShow` for the new view,
951
919
  // and will not replace the current HTML for the `el`
952
920
  // of the region.
953
921
  attachView: function(view){
@@ -1061,14 +1029,20 @@ Marionette.RegionManager = (function(Marionette){
1061
1029
  // internal method to store regions
1062
1030
  _store: function(name, region){
1063
1031
  this._regions[name] = region;
1064
- this.length = _.size(this._regions);
1032
+ this._setLength();
1065
1033
  },
1066
1034
 
1067
1035
  // internal method to remove a region
1068
1036
  _remove: function(name, region){
1069
1037
  region.close();
1070
1038
  delete this._regions[name];
1039
+ this._setLength();
1071
1040
  this.triggerMethod("region:remove", name, region);
1041
+ },
1042
+
1043
+ // set the number of regions current held
1044
+ _setLength: function(){
1045
+ this.length = _.size(this._regions);
1072
1046
  }
1073
1047
 
1074
1048
  });
@@ -1147,7 +1121,7 @@ _.extend(Marionette.TemplateCache, {
1147
1121
  });
1148
1122
 
1149
1123
  // TemplateCache instance methods, allowing each
1150
- // template cache object to manage it's own state
1124
+ // template cache object to manage its own state
1151
1125
  // and know whether or not it has been loaded
1152
1126
  _.extend(Marionette.TemplateCache.prototype, {
1153
1127
 
@@ -1202,7 +1176,20 @@ Marionette.Renderer = {
1202
1176
  // template function. Override this method to provide your own
1203
1177
  // custom rendering and template handling for all of Marionette.
1204
1178
  render: function(template, data){
1205
- var templateFunc = typeof template === 'function' ? template : Marionette.TemplateCache.get(template);
1179
+
1180
+ if (!template) {
1181
+ var error = new Error("Cannot render the template since it's false, null or undefined.");
1182
+ error.name = "TemplateNotFoundError";
1183
+ throw error;
1184
+ }
1185
+
1186
+ var templateFunc;
1187
+ if (typeof template === "function"){
1188
+ templateFunc = template;
1189
+ } else {
1190
+ templateFunc = Marionette.TemplateCache.get(template);
1191
+ }
1192
+
1206
1193
  return templateFunc(data);
1207
1194
  }
1208
1195
  };
@@ -1215,10 +1202,19 @@ Marionette.Renderer = {
1215
1202
  // The core view type that other Marionette views extend from.
1216
1203
  Marionette.View = Backbone.View.extend({
1217
1204
 
1218
- constructor: function(){
1205
+ constructor: function(options){
1219
1206
  _.bindAll(this, "render");
1220
1207
 
1221
1208
  var args = Array.prototype.slice.apply(arguments);
1209
+
1210
+ // this exposes view options to the view initializer
1211
+ // this is a backfill since backbone removed the assignment
1212
+ // of this.options
1213
+ // at some point however this may be removed
1214
+ this.options = _.extend({}, this.options, options);
1215
+
1216
+ // parses out the @ui DSL for events
1217
+ this.events = this.normalizeUIKeys(_.result(this, 'events'));
1222
1218
  Backbone.View.prototype.constructor.apply(this, args);
1223
1219
 
1224
1220
  Marionette.MonitorDOMRefresh(this);
@@ -1226,7 +1222,7 @@ Marionette.View = Backbone.View.extend({
1226
1222
  },
1227
1223
 
1228
1224
  // import the "triggerMethod" to trigger events with corresponding
1229
- // methods if the method exists
1225
+ // methods if the method exists
1230
1226
  triggerMethod: Marionette.triggerMethod,
1231
1227
 
1232
1228
  // Get the template for this view
@@ -1244,13 +1240,32 @@ Marionette.View = Backbone.View.extend({
1244
1240
  // are copies to the object passed in.
1245
1241
  mixinTemplateHelpers: function(target){
1246
1242
  target = target || {};
1247
- var templateHelpers = this.templateHelpers;
1243
+ var templateHelpers = Marionette.getOption(this, "templateHelpers");
1248
1244
  if (_.isFunction(templateHelpers)){
1249
1245
  templateHelpers = templateHelpers.call(this);
1250
1246
  }
1251
1247
  return _.extend(target, templateHelpers);
1252
1248
  },
1253
1249
 
1250
+ // allows for the use of the @ui. syntax within
1251
+ // a given key for triggers and events
1252
+ // swaps the @ui with the associated selector
1253
+ normalizeUIKeys: function(hash) {
1254
+ if (typeof(hash) === "undefined") {
1255
+ return;
1256
+ }
1257
+
1258
+ _.each(_.keys(hash), function(v) {
1259
+ var split = v.split("@ui.");
1260
+ if (split.length === 2) {
1261
+ hash[split[0]+this.ui[split[1]]] = hash[v];
1262
+ delete hash[v];
1263
+ }
1264
+ }, this);
1265
+
1266
+ return hash;
1267
+ },
1268
+
1254
1269
  // Configure `triggers` to forward DOM events to view
1255
1270
  // events. `triggers: {"click .foo": "do:foo"}`
1256
1271
  configureTriggers: function(){
@@ -1259,18 +1274,29 @@ Marionette.View = Backbone.View.extend({
1259
1274
  var triggerEvents = {};
1260
1275
 
1261
1276
  // Allow `triggers` to be configured as a function
1262
- var triggers = _.result(this, "triggers");
1277
+ var triggers = this.normalizeUIKeys(_.result(this, "triggers"));
1263
1278
 
1264
1279
  // Configure the triggers, prevent default
1265
1280
  // action and stop propagation of DOM events
1266
1281
  _.each(triggers, function(value, key){
1267
1282
 
1283
+ var hasOptions = _.isObject(value);
1284
+ var eventName = hasOptions ? value.event : value;
1285
+
1268
1286
  // build the event handler function for the DOM event
1269
1287
  triggerEvents[key] = function(e){
1270
1288
 
1271
- // stop the event in it's tracks
1272
- if (e && e.preventDefault){ e.preventDefault(); }
1273
- if (e && e.stopPropagation){ e.stopPropagation(); }
1289
+ // stop the event in its tracks
1290
+ if (e) {
1291
+ var prevent = e.preventDefault;
1292
+ var stop = e.stopPropagation;
1293
+
1294
+ var shouldPrevent = hasOptions ? value.preventDefault : prevent;
1295
+ var shouldStop = hasOptions ? value.stopPropagation : stop;
1296
+
1297
+ if (shouldPrevent && prevent) { prevent.apply(e); }
1298
+ if (shouldStop && stop) { stop.apply(e); }
1299
+ }
1274
1300
 
1275
1301
  // build the args for the event
1276
1302
  var args = {
@@ -1280,7 +1306,7 @@ Marionette.View = Backbone.View.extend({
1280
1306
  };
1281
1307
 
1282
1308
  // trigger the event
1283
- this.triggerMethod(value, args);
1309
+ this.triggerMethod(eventName, args);
1284
1310
  };
1285
1311
 
1286
1312
  }, this);
@@ -1288,7 +1314,7 @@ Marionette.View = Backbone.View.extend({
1288
1314
  return triggerEvents;
1289
1315
  },
1290
1316
 
1291
- // Overriding Backbone.View's delegateEvents to handle
1317
+ // Overriding Backbone.View's delegateEvents to handle
1292
1318
  // the `triggers`, `modelEvents`, and `collectionEvents` configuration
1293
1319
  delegateEvents: function(events){
1294
1320
  this._delegateDOMEvents(events);
@@ -1374,7 +1400,7 @@ Marionette.View = Backbone.View.extend({
1374
1400
 
1375
1401
  // This method unbinds the elements specified in the "ui" hash
1376
1402
  unbindUIElements: function(){
1377
- if (!this.ui){ return; }
1403
+ if (!this.ui || !this._uiBindings){ return; }
1378
1404
 
1379
1405
  // delete all of the existing ui bindings
1380
1406
  _.each(this.ui, function($el, name){
@@ -1393,7 +1419,10 @@ Marionette.View = Backbone.View.extend({
1393
1419
  // A single item view implementation that contains code for rendering
1394
1420
  // with underscore.js templates, serializing the view's model or collection,
1395
1421
  // and calling several methods on extended views, such as `onRender`.
1396
- Marionette.ItemView = Marionette.View.extend({
1422
+ Marionette.ItemView = Marionette.View.extend({
1423
+
1424
+ // Setting up the inheritance chain which allows changes to
1425
+ // Marionette.View.prototype.constructor which allows overriding
1397
1426
  constructor: function(){
1398
1427
  Marionette.View.prototype.constructor.apply(this, slice(arguments));
1399
1428
  },
@@ -1433,6 +1462,7 @@ Marionette.ItemView = Marionette.View.extend({
1433
1462
 
1434
1463
  var template = this.getTemplate();
1435
1464
  var html = Marionette.Renderer.render(template, data);
1465
+
1436
1466
  this.$el.html(html);
1437
1467
  this.bindUIElements();
1438
1468
 
@@ -1472,6 +1502,25 @@ Marionette.CollectionView = Marionette.View.extend({
1472
1502
  Marionette.View.prototype.constructor.apply(this, slice(arguments));
1473
1503
 
1474
1504
  this._initialEvents();
1505
+ this.initRenderBuffer();
1506
+ },
1507
+
1508
+ // Instead of inserting elements one by one into the page,
1509
+ // it's much more performant to insert elements into a document
1510
+ // fragment and then insert that document fragment into the page
1511
+ initRenderBuffer: function() {
1512
+ this.elBuffer = document.createDocumentFragment();
1513
+ },
1514
+
1515
+ startBuffering: function() {
1516
+ this.initRenderBuffer();
1517
+ this.isBuffering = true;
1518
+ },
1519
+
1520
+ endBuffering: function() {
1521
+ this.appendBuffer(this, this.elBuffer);
1522
+ this.initRenderBuffer();
1523
+ this.isBuffering = false;
1475
1524
  },
1476
1525
 
1477
1526
  // Configured the initial events that the collection view
@@ -1530,6 +1579,8 @@ Marionette.CollectionView = Marionette.View.extend({
1530
1579
  // more control over events being triggered, around the rendering
1531
1580
  // process
1532
1581
  _renderChildren: function(){
1582
+ this.startBuffering();
1583
+
1533
1584
  this.closeEmptyView();
1534
1585
  this.closeChildren();
1535
1586
 
@@ -1538,6 +1589,8 @@ Marionette.CollectionView = Marionette.View.extend({
1538
1589
  } else {
1539
1590
  this.showEmptyView();
1540
1591
  }
1592
+
1593
+ this.endBuffering();
1541
1594
  },
1542
1595
 
1543
1596
  // Internal method to loop through each item in the
@@ -1554,7 +1607,7 @@ Marionette.CollectionView = Marionette.View.extend({
1554
1607
  // a collection of item views, when the collection is
1555
1608
  // empty
1556
1609
  showEmptyView: function(){
1557
- var EmptyView = Marionette.getOption(this, "emptyView");
1610
+ var EmptyView = this.getEmptyView();
1558
1611
 
1559
1612
  if (EmptyView && !this._showingEmptyView){
1560
1613
  this._showingEmptyView = true;
@@ -1573,6 +1626,11 @@ Marionette.CollectionView = Marionette.View.extend({
1573
1626
  }
1574
1627
  },
1575
1628
 
1629
+ // Retrieve the empty view type
1630
+ getEmptyView: function(){
1631
+ return Marionette.getOption(this, "emptyView");
1632
+ },
1633
+
1576
1634
  // Retrieve the itemView type, either from `this.options.itemView`
1577
1635
  // or from the `itemView` in the object definition. The "options"
1578
1636
  // takes precedence.
@@ -1595,9 +1653,9 @@ Marionette.CollectionView = Marionette.View.extend({
1595
1653
  itemViewOptions = itemViewOptions.call(this, item, index);
1596
1654
  }
1597
1655
 
1598
- // build the view
1656
+ // build the view
1599
1657
  var view = this.buildItemView(item, ItemView, itemViewOptions);
1600
-
1658
+
1601
1659
  // set up the child view event forwarding
1602
1660
  this.addChildViewEventForwarding(view);
1603
1661
 
@@ -1683,11 +1741,26 @@ Marionette.CollectionView = Marionette.View.extend({
1683
1741
  }
1684
1742
  },
1685
1743
 
1744
+ // You might need to override this if you've overridden appendHtml
1745
+ appendBuffer: function(collectionView, buffer) {
1746
+ collectionView.$el.append(buffer);
1747
+ },
1748
+
1686
1749
  // Append the HTML to the collection's `el`.
1687
1750
  // Override this method to do something other
1688
1751
  // then `.append`.
1689
1752
  appendHtml: function(collectionView, itemView, index){
1690
- collectionView.$el.append(itemView.el);
1753
+ if (collectionView.isBuffering) {
1754
+ // buffering happens on reset events and initial renders
1755
+ // in order to reduce the number of inserts into the
1756
+ // document, which are expensive.
1757
+ collectionView.elBuffer.appendChild(itemView.el);
1758
+ }
1759
+ else {
1760
+ // If we've already rendered the main collection, just
1761
+ // append the new items directly into the element.
1762
+ collectionView.$el.append(itemView.el);
1763
+ }
1691
1764
  },
1692
1765
 
1693
1766
  // Internal method to set up the `children` object for
@@ -1726,21 +1799,28 @@ Marionette.CollectionView = Marionette.View.extend({
1726
1799
  // Extends directly from CollectionView and also renders an
1727
1800
  // an item view as `modelView`, for the top leaf
1728
1801
  Marionette.CompositeView = Marionette.CollectionView.extend({
1729
- constructor: function(options){
1730
- Marionette.CollectionView.apply(this, slice(arguments));
1731
1802
 
1732
- this.itemView = this.getItemView();
1803
+ // Setting up the inheritance chain which allows changes to
1804
+ // Marionette.CollectionView.prototype.constructor which allows overriding
1805
+ constructor: function(){
1806
+ Marionette.CollectionView.prototype.constructor.apply(this, slice(arguments));
1733
1807
  },
1734
1808
 
1735
1809
  // Configured the initial events that the composite view
1736
1810
  // binds to. Override this method to prevent the initial
1737
1811
  // events, or to add your own initial events.
1738
1812
  _initialEvents: function(){
1739
- if (this.collection){
1740
- this.listenTo(this.collection, "add", this.addChildView, this);
1741
- this.listenTo(this.collection, "remove", this.removeItemView, this);
1742
- this.listenTo(this.collection, "reset", this._renderChildren, this);
1743
- }
1813
+
1814
+ // Bind only after composite view in rendered to avoid adding child views
1815
+ // to unexisting itemViewContainer
1816
+ this.once('render', function () {
1817
+ if (this.collection){
1818
+ this.listenTo(this.collection, "add", this.addChildView, this);
1819
+ this.listenTo(this.collection, "remove", this.removeItemView, this);
1820
+ this.listenTo(this.collection, "reset", this._renderChildren, this);
1821
+ }
1822
+ });
1823
+
1744
1824
  },
1745
1825
 
1746
1826
  // Retrieve the `itemView` to be used when rendering each of
@@ -1757,7 +1837,7 @@ Marionette.CompositeView = Marionette.CollectionView.extend({
1757
1837
  return itemView;
1758
1838
  },
1759
1839
 
1760
- // Serialize the collection for the view.
1840
+ // Serialize the collection for the view.
1761
1841
  // You can override the `serializeData` method in your own view
1762
1842
  // definition, to provide custom serialization for your view's data.
1763
1843
  serializeData: function(){
@@ -1781,7 +1861,7 @@ Marionette.CompositeView = Marionette.CollectionView.extend({
1781
1861
  this.triggerBeforeRender();
1782
1862
  var html = this.renderModel();
1783
1863
  this.$el.html(html);
1784
- // the ui bindings is done here and not at the end of render since they
1864
+ // the ui bindings is done here and not at the end of render since they
1785
1865
  // will not be available until after the model is rendered, but should be
1786
1866
  // available before the collection is rendered.
1787
1867
  this.bindUIElements();
@@ -1813,15 +1893,30 @@ Marionette.CompositeView = Marionette.CollectionView.extend({
1813
1893
  return Marionette.Renderer.render(template, data);
1814
1894
  },
1815
1895
 
1896
+
1897
+ // You might need to override this if you've overridden appendHtml
1898
+ appendBuffer: function(compositeView, buffer) {
1899
+ var $container = this.getItemViewContainer(compositeView);
1900
+ $container.append(buffer);
1901
+ },
1902
+
1816
1903
  // Appends the `el` of itemView instances to the specified
1817
1904
  // `itemViewContainer` (a jQuery selector). Override this method to
1818
1905
  // provide custom logic of how the child item view instances have their
1819
1906
  // HTML appended to the composite view instance.
1820
- appendHtml: function(cv, iv){
1821
- var $container = this.getItemViewContainer(cv);
1822
- $container.append(iv.el);
1907
+ appendHtml: function(compositeView, itemView, index){
1908
+ if (compositeView.isBuffering) {
1909
+ compositeView.elBuffer.appendChild(itemView.el);
1910
+ }
1911
+ else {
1912
+ // If we've already rendered the main collection, just
1913
+ // append the new items directly into the element.
1914
+ var $container = this.getItemViewContainer(compositeView);
1915
+ $container.append(itemView.el);
1916
+ }
1823
1917
  },
1824
1918
 
1919
+
1825
1920
  // Internal method to ensure an `$itemViewContainer` exists, for the
1826
1921
  // `appendHtml` method to use.
1827
1922
  getItemViewContainer: function(containerView){
@@ -1830,9 +1925,10 @@ Marionette.CompositeView = Marionette.CollectionView.extend({
1830
1925
  }
1831
1926
 
1832
1927
  var container;
1833
- if (containerView.itemViewContainer){
1928
+ var itemViewContainer = Marionette.getOption(containerView, "itemViewContainer");
1929
+ if (itemViewContainer){
1834
1930
 
1835
- var selector = _.result(containerView, "itemViewContainer");
1931
+ var selector = _.isFunction(itemViewContainer) ? itemViewContainer() : itemViewContainer;
1836
1932
  container = containerView.$(selector);
1837
1933
  if (container.length <= 0) {
1838
1934
  throwError("The specified `itemViewContainer` was not found: " + containerView.itemViewContainer, "ItemViewContainerMissingError");
@@ -1866,7 +1962,7 @@ Marionette.CompositeView = Marionette.CollectionView.extend({
1866
1962
  // Used for composite view management and sub-application areas.
1867
1963
  Marionette.Layout = Marionette.ItemView.extend({
1868
1964
  regionType: Marionette.Region,
1869
-
1965
+
1870
1966
  // Ensure the regions are available when the `initialize` method
1871
1967
  // is called.
1872
1968
  constructor: function (options) {
@@ -1874,8 +1970,8 @@ Marionette.Layout = Marionette.ItemView.extend({
1874
1970
 
1875
1971
  this._firstRender = true;
1876
1972
  this._initializeRegions(options);
1877
-
1878
- Marionette.ItemView.call(this, options);
1973
+
1974
+ Marionette.ItemView.prototype.constructor.call(this, options);
1879
1975
  },
1880
1976
 
1881
1977
  // Layout's render will use the existing region objects the
@@ -1884,16 +1980,17 @@ Marionette.Layout = Marionette.ItemView.extend({
1884
1980
  // for the regions to the newly rendered DOM elements.
1885
1981
  render: function(){
1886
1982
 
1887
- if (this._firstRender){
1983
+ if (this.isClosed){
1984
+ // a previously closed layout means we need to
1985
+ // completely re-initialize the regions
1986
+ this._initializeRegions();
1987
+ }
1988
+ if (this._firstRender) {
1888
1989
  // if this is the first render, don't do anything to
1889
1990
  // reset the regions
1890
1991
  this._firstRender = false;
1891
- } else if (this.isClosed){
1892
- // a previously closed layout means we need to
1893
- // completely re-initialize the regions
1894
- this._initializeRegions();
1895
- } else {
1896
- // If this is not the first render call, then we need to
1992
+ } else if (!this.isClosed){
1993
+ // If this is not the first render call, then we need to
1897
1994
  // re-initializing the `el` for each region
1898
1995
  this._reInitializeRegions();
1899
1996
  }
@@ -1916,17 +2013,18 @@ Marionette.Layout = Marionette.ItemView.extend({
1916
2013
  addRegion: function(name, definition){
1917
2014
  var regions = {};
1918
2015
  regions[name] = definition;
1919
- return this.addRegions(regions)[name];
2016
+ return this._buildRegions(regions)[name];
1920
2017
  },
1921
2018
 
1922
2019
  // Add multiple regions as a {name: definition, name2: def2} object literal
1923
2020
  addRegions: function(regions){
1924
- this.regions = _.extend(this.regions || {}, regions);
2021
+ this.regions = _.extend({}, this.regions, regions);
1925
2022
  return this._buildRegions(regions);
1926
2023
  },
1927
2024
 
1928
2025
  // Remove a single region from the Layout, by name
1929
2026
  removeRegion: function(name){
2027
+ delete this.regions[name];
1930
2028
  return this.regionManager.removeRegion(name);
1931
2029
  },
1932
2030
 
@@ -1935,6 +2033,7 @@ Marionette.Layout = Marionette.ItemView.extend({
1935
2033
  var that = this;
1936
2034
 
1937
2035
  var defaults = {
2036
+ regionType: Marionette.getOption(this, "regionType"),
1938
2037
  parentEl: function(){ return that.$el; }
1939
2038
  };
1940
2039
 
@@ -1942,7 +2041,7 @@ Marionette.Layout = Marionette.ItemView.extend({
1942
2041
  },
1943
2042
 
1944
2043
  // Internal method to initialize the regions that have been defined in a
1945
- // `regions` attribute on this layout.
2044
+ // `regions` attribute on this layout.
1946
2045
  _initializeRegions: function (options) {
1947
2046
  var regions;
1948
2047
  this._initRegionManager();
@@ -2005,31 +2104,46 @@ Marionette.AppRouter = Backbone.Router.extend({
2005
2104
 
2006
2105
  constructor: function(options){
2007
2106
  Backbone.Router.prototype.constructor.apply(this, slice(arguments));
2107
+
2108
+ this.options = options || {};
2008
2109
 
2009
- this.options = options;
2110
+ var appRoutes = Marionette.getOption(this, "appRoutes");
2111
+ var controller = this._getController();
2112
+ this.processAppRoutes(controller, appRoutes);
2113
+ },
2010
2114
 
2011
- if (this.appRoutes){
2012
- var controller = Marionette.getOption(this, "controller");
2013
- this.processAppRoutes(controller, this.appRoutes);
2014
- }
2115
+ // Similar to route method on a Backbone Router but
2116
+ // method is called on the controller
2117
+ appRoute: function(route, methodName) {
2118
+ var controller = this._getController();
2119
+ this._addAppRoute(controller, route, methodName);
2015
2120
  },
2016
2121
 
2017
2122
  // Internal method to process the `appRoutes` for the
2018
2123
  // router, and turn them in to routes that trigger the
2019
2124
  // specified method on the specified `controller`.
2020
2125
  processAppRoutes: function(controller, appRoutes) {
2126
+ if (!appRoutes){ return; }
2127
+
2021
2128
  var routeNames = _.keys(appRoutes).reverse(); // Backbone requires reverted order of routes
2022
2129
 
2023
2130
  _.each(routeNames, function(route) {
2024
- var methodName = appRoutes[route];
2025
- var method = controller[methodName];
2131
+ this._addAppRoute(controller, route, appRoutes[route]);
2132
+ }, this);
2133
+ },
2026
2134
 
2027
- if (!method) {
2028
- throw new Error("Method '" + methodName + "' was not found on the controller");
2029
- }
2135
+ _getController: function(){
2136
+ return Marionette.getOption(this, "controller");
2137
+ },
2030
2138
 
2031
- this.route(route, methodName, _.bind(method, controller));
2032
- }, this);
2139
+ _addAppRoute: function(controller, route, methodName){
2140
+ var method = controller[methodName];
2141
+
2142
+ if (!method) {
2143
+ throw new Error("Method '" + methodName + "' was not found on the controller");
2144
+ }
2145
+
2146
+ this.route(route, methodName, _.bind(method, controller));
2033
2147
  }
2034
2148
  });
2035
2149
 
@@ -2084,7 +2198,7 @@ _.extend(Marionette.Application.prototype, Backbone.Events, {
2084
2198
  this.triggerMethod("start", options);
2085
2199
  },
2086
2200
 
2087
- // Add regions to your app.
2201
+ // Add regions to your app.
2088
2202
  // Accepts a hash of named strings or Region objects
2089
2203
  // addRegions({something: "#someRegion"})
2090
2204
  // addRegions({something: Region.extend({el: "#someRegion"}) });
@@ -2092,13 +2206,25 @@ _.extend(Marionette.Application.prototype, Backbone.Events, {
2092
2206
  return this._regionManager.addRegions(regions);
2093
2207
  },
2094
2208
 
2095
- // Removes a region from your app.
2209
+ // Close all regions in the app, without removing them
2210
+ closeRegions: function(){
2211
+ this._regionManager.closeRegions();
2212
+ },
2213
+
2214
+ // Removes a region from your app, by name
2096
2215
  // Accepts the regions name
2097
2216
  // removeRegion('myRegion')
2098
2217
  removeRegion: function(region) {
2099
2218
  this._regionManager.removeRegion(region);
2100
2219
  },
2101
2220
 
2221
+ // Provides alternative access to regions
2222
+ // Accepts the region name
2223
+ // getRegion('main')
2224
+ getRegion: function(region) {
2225
+ return this._regionManager.get(region);
2226
+ },
2227
+
2102
2228
  // Create a module, attached to the application
2103
2229
  module: function(moduleNames, moduleDefinition){
2104
2230
  // slice the args, and add this application object as the
@@ -2289,7 +2415,7 @@ _.extend(Marionette.Module, {
2289
2415
  },
2290
2416
 
2291
2417
  _addModuleDefinition: function(parentModule, module, def, args){
2292
- var fn;
2418
+ var fn;
2293
2419
  var startWithParent;
2294
2420
 
2295
2421
  if (_.isFunction(def)){
@@ -2301,7 +2427,7 @@ _.extend(Marionette.Module, {
2301
2427
  // if an object is supplied
2302
2428
  fn = def.define;
2303
2429
  startWithParent = def.startWithParent;
2304
-
2430
+
2305
2431
  } else {
2306
2432
  // if nothing is supplied
2307
2433
  startWithParent = true;
@@ -2337,4 +2463,4 @@ _.extend(Marionette.Module, {
2337
2463
 
2338
2464
 
2339
2465
  return Marionette;
2340
- })(this, Backbone, _);
2466
+ })(this, Backbone, _);