marionette.modal 1.0.0.6 → 1.0.0.7

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