knockoutjs-rails 3.4.0.1 → 3.5.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 778c4f6852e5539bec494d03b5d42cbb5ef75f5b
4
- data.tar.gz: 84664132a9af519f368ebdb08dc48fc7108f54c0
2
+ SHA256:
3
+ metadata.gz: c80fd2c172e365df64e874fcd409b69190dc7309f5b3a1989691db726583f40a
4
+ data.tar.gz: 320a5df4dfcca7859014148c3dfefab1f38a6e6865d7acda802643e54e680d64
5
5
  SHA512:
6
- metadata.gz: fd4191e2823b6da87685b7b2125db1bdff86405008674d7fea0939c12cbc579eae349af1e35a3141318fd37e9ee91649e48768b73291a88fa66fde8b765cab7c
7
- data.tar.gz: acf146a9ae1743e4ae63ab6e71dcc6d6a5830133bb7c5ea6894dac55af8691ab5f4e067eceaeed81eddadcc1a907fa091523c9051de657dd95f682d4cc6eeff9
6
+ metadata.gz: e93090ba07bc76f7d773b8c8ddc4855a21def6f09ad97676e8888303a1ca8fb68e907103cc7b91011de51157c20e60c8f9b37fb445aaff5d40e08ba7e8a7ad74
7
+ data.tar.gz: 48b0eec9a8b53aac33ce85a26c66a3fbb51c5ee1216e97d282e409fdb887c2bc904b073e43548376307501cd344dd8441607459eace53419f055635bdf8d6419
@@ -1,5 +1,5 @@
1
1
  module Knockoutjs
2
2
  module Rails
3
- VERSION = "3.4.0.1"
3
+ VERSION = "3.5.0"
4
4
  end
5
5
  end
@@ -1,6 +1,6 @@
1
1
  /*!
2
- * Knockout JavaScript library v3.4.0
3
- * (c) Steven Sanderson - http://knockoutjs.com/
2
+ * Knockout JavaScript library v3.5.0
3
+ * (c) The Knockout.js team - http://knockoutjs.com/
4
4
  * License: MIT (http://www.opensource.org/licenses/mit-license.php)
5
5
  */
6
6
 
@@ -14,6 +14,10 @@ var DEBUG=true;
14
14
  navigator = window['navigator'],
15
15
  jQueryInstance = window["jQuery"],
16
16
  JSON = window["JSON"];
17
+
18
+ if (!jQueryInstance && typeof jQuery !== "undefined") {
19
+ jQueryInstance = jQuery;
20
+ }
17
21
  (function(factory) {
18
22
  // Support three module loading scenarios
19
23
  if (typeof define === 'function' && define['amd']) {
@@ -45,20 +49,23 @@ ko.exportSymbol = function(koPath, object) {
45
49
  ko.exportProperty = function(owner, publicName, object) {
46
50
  owner[publicName] = object;
47
51
  };
48
- ko.version = "3.4.0";
52
+ ko.version = "3.5.0";
49
53
 
50
54
  ko.exportSymbol('version', ko.version);
51
55
  // For any options that may affect various areas of Knockout and aren't directly associated with data binding.
52
56
  ko.options = {
53
57
  'deferUpdates': false,
54
- 'useOnlyNativeEvents': false
58
+ 'useOnlyNativeEvents': false,
59
+ 'foreachHidesDestroyed': false
55
60
  };
56
61
 
57
62
  //ko.exportSymbol('options', ko.options); // 'options' isn't minified
58
63
  ko.utils = (function () {
64
+ var hasOwnProperty = Object.prototype.hasOwnProperty;
65
+
59
66
  function objectForEach(obj, action) {
60
67
  for (var prop in obj) {
61
- if (obj.hasOwnProperty(prop)) {
68
+ if (hasOwnProperty.call(obj, prop)) {
62
69
  action(prop, obj[prop]);
63
70
  }
64
71
  }
@@ -67,7 +74,7 @@ ko.utils = (function () {
67
74
  function extend(target, source) {
68
75
  if (source) {
69
76
  for(var prop in source) {
70
- if(source.hasOwnProperty(prop)) {
77
+ if(hasOwnProperty.call(source, prop)) {
71
78
  target[prop] = source[prop];
72
79
  }
73
80
  }
@@ -124,6 +131,8 @@ ko.utils = (function () {
124
131
  // see: https://github.com/knockout/knockout/issues/1597
125
132
  var cssClassNameRegex = /\S+/g;
126
133
 
134
+ var jQueryEventAttachName;
135
+
127
136
  function toggleDomNodeCssClass(node, classNames, shouldHaveClass) {
128
137
  var addOrRemoveFn;
129
138
  if (classNames) {
@@ -154,25 +163,30 @@ ko.utils = (function () {
154
163
  return {
155
164
  fieldsIncludedWithJsonPost: ['authenticity_token', /^__RequestVerificationToken(_.*)?$/],
156
165
 
157
- arrayForEach: function (array, action) {
158
- for (var i = 0, j = array.length; i < j; i++)
159
- action(array[i], i);
166
+ arrayForEach: function (array, action, actionOwner) {
167
+ for (var i = 0, j = array.length; i < j; i++) {
168
+ action.call(actionOwner, array[i], i, array);
169
+ }
160
170
  },
161
171
 
162
- arrayIndexOf: function (array, item) {
163
- if (typeof Array.prototype.indexOf == "function")
172
+ arrayIndexOf: typeof Array.prototype.indexOf == "function"
173
+ ? function (array, item) {
164
174
  return Array.prototype.indexOf.call(array, item);
165
- for (var i = 0, j = array.length; i < j; i++)
166
- if (array[i] === item)
167
- return i;
168
- return -1;
169
- },
175
+ }
176
+ : function (array, item) {
177
+ for (var i = 0, j = array.length; i < j; i++) {
178
+ if (array[i] === item)
179
+ return i;
180
+ }
181
+ return -1;
182
+ },
170
183
 
171
184
  arrayFirst: function (array, predicate, predicateOwner) {
172
- for (var i = 0, j = array.length; i < j; i++)
173
- if (predicate.call(predicateOwner, array[i], i))
185
+ for (var i = 0, j = array.length; i < j; i++) {
186
+ if (predicate.call(predicateOwner, array[i], i, array))
174
187
  return array[i];
175
- return null;
188
+ }
189
+ return undefined;
176
190
  },
177
191
 
178
192
  arrayRemoveItem: function (array, itemToRemove) {
@@ -186,29 +200,32 @@ ko.utils = (function () {
186
200
  },
187
201
 
188
202
  arrayGetDistinctValues: function (array) {
189
- array = array || [];
190
203
  var result = [];
191
- for (var i = 0, j = array.length; i < j; i++) {
192
- if (ko.utils.arrayIndexOf(result, array[i]) < 0)
193
- result.push(array[i]);
204
+ if (array) {
205
+ ko.utils.arrayForEach(array, function(item) {
206
+ if (ko.utils.arrayIndexOf(result, item) < 0)
207
+ result.push(item);
208
+ });
194
209
  }
195
210
  return result;
196
211
  },
197
212
 
198
- arrayMap: function (array, mapping) {
199
- array = array || [];
213
+ arrayMap: function (array, mapping, mappingOwner) {
200
214
  var result = [];
201
- for (var i = 0, j = array.length; i < j; i++)
202
- result.push(mapping(array[i], i));
215
+ if (array) {
216
+ for (var i = 0, j = array.length; i < j; i++)
217
+ result.push(mapping.call(mappingOwner, array[i], i));
218
+ }
203
219
  return result;
204
220
  },
205
221
 
206
- arrayFilter: function (array, predicate) {
207
- array = array || [];
222
+ arrayFilter: function (array, predicate, predicateOwner) {
208
223
  var result = [];
209
- for (var i = 0, j = array.length; i < j; i++)
210
- if (predicate(array[i], i))
211
- result.push(array[i]);
224
+ if (array) {
225
+ for (var i = 0, j = array.length; i < j; i++)
226
+ if (predicate.call(predicateOwner, array[i], i))
227
+ result.push(array[i]);
228
+ }
212
229
  return result;
213
230
  },
214
231
 
@@ -242,13 +259,13 @@ ko.utils = (function () {
242
259
 
243
260
  objectForEach: objectForEach,
244
261
 
245
- objectMap: function(source, mapping) {
262
+ objectMap: function(source, mapping, mappingOwner) {
246
263
  if (!source)
247
264
  return source;
248
265
  var target = {};
249
266
  for (var prop in source) {
250
- if (source.hasOwnProperty(prop)) {
251
- target[prop] = mapping(source[prop], prop, source);
267
+ if (hasOwnProperty.call(source, prop)) {
268
+ target[prop] = mapping.call(mappingOwner, source[prop], prop, source);
252
269
  }
253
270
  }
254
271
  return target;
@@ -374,7 +391,7 @@ ko.utils = (function () {
374
391
  if (node.nodeType === 11)
375
392
  return false; // Fixes issue #1162 - can't use node.contains for document fragments on IE8
376
393
  if (containedByNode.contains)
377
- return containedByNode.contains(node.nodeType === 3 ? node.parentNode : node);
394
+ return containedByNode.contains(node.nodeType !== 1 ? node.parentNode : node);
378
395
  if (containedByNode.compareDocumentPosition)
379
396
  return (containedByNode.compareDocumentPosition(node) & 16) == 16;
380
397
  while (node && node != containedByNode) {
@@ -423,9 +440,12 @@ ko.utils = (function () {
423
440
  registerEventHandler: function (element, eventType, handler) {
424
441
  var wrappedHandler = ko.utils.catchFunctionErrors(handler);
425
442
 
426
- var mustUseAttachEvent = ieVersion && eventsThatMustBeRegisteredUsingAttachEvent[eventType];
443
+ var mustUseAttachEvent = eventsThatMustBeRegisteredUsingAttachEvent[eventType];
427
444
  if (!ko.options['useOnlyNativeEvents'] && !mustUseAttachEvent && jQueryInstance) {
428
- jQueryInstance(element)['bind'](eventType, wrappedHandler);
445
+ if (!jQueryEventAttachName) {
446
+ jQueryEventAttachName = (typeof jQueryInstance(element)['on'] == 'function') ? 'on' : 'bind';
447
+ }
448
+ jQueryInstance(element)[jQueryEventAttachName](eventType, wrappedHandler);
429
449
  } else if (!mustUseAttachEvent && typeof element.addEventListener == "function")
430
450
  element.addEventListener(eventType, wrappedHandler, false);
431
451
  else if (typeof element.attachEvent != "undefined") {
@@ -508,7 +528,8 @@ ko.utils = (function () {
508
528
  // - http://www.matts411.com/post/setting_the_name_attribute_in_ie_dom/
509
529
  if (ieVersion <= 7) {
510
530
  try {
511
- element.mergeAttributes(document.createElement("<input name='" + element.name + "'/>"), false);
531
+ var escapedName = element.name.replace(/[&<>'"]/g, function(r){ return "&#" + r.charCodeAt(0) + ";"; });
532
+ element.mergeAttributes(document.createElement("<input name='" + escapedName + "'/>"), false);
512
533
  }
513
534
  catch(e) {} // For IE9 with doc mode "IE9 Standards" and browser mode "IE9 Compatibility View"
514
535
  }
@@ -644,9 +665,12 @@ ko.exportSymbol('utils.arrayIndexOf', ko.utils.arrayIndexOf);
644
665
  ko.exportSymbol('utils.arrayMap', ko.utils.arrayMap);
645
666
  ko.exportSymbol('utils.arrayPushAll', ko.utils.arrayPushAll);
646
667
  ko.exportSymbol('utils.arrayRemoveItem', ko.utils.arrayRemoveItem);
668
+ ko.exportSymbol('utils.cloneNodes', ko.utils.cloneNodes);
669
+ ko.exportSymbol('utils.createSymbolOrString', ko.utils.createSymbolOrString);
647
670
  ko.exportSymbol('utils.extend', ko.utils.extend);
648
671
  ko.exportSymbol('utils.fieldsIncludedWithJsonPost', ko.utils.fieldsIncludedWithJsonPost);
649
672
  ko.exportSymbol('utils.getFormFields', ko.utils.getFormFields);
673
+ ko.exportSymbol('utils.objectMap', ko.utils.objectMap);
650
674
  ko.exportSymbol('utils.peekObservable', ko.utils.peekObservable);
651
675
  ko.exportSymbol('utils.postJson', ko.utils.postJson);
652
676
  ko.exportSymbol('utils.parseJson', ko.utils.parseJson);
@@ -686,33 +710,40 @@ ko.utils.domData = new (function () {
686
710
  var dataStoreKeyExpandoPropertyName = "__ko__" + (new Date).getTime();
687
711
  var dataStore = {};
688
712
 
689
- function getAll(node, createIfNotFound) {
690
- var dataStoreKey = node[dataStoreKeyExpandoPropertyName];
691
- var hasExistingDataStore = dataStoreKey && (dataStoreKey !== "null") && dataStore[dataStoreKey];
692
- if (!hasExistingDataStore) {
693
- if (!createIfNotFound)
694
- return undefined;
695
- dataStoreKey = node[dataStoreKeyExpandoPropertyName] = "ko" + uniqueId++;
696
- dataStore[dataStoreKey] = {};
697
- }
698
- return dataStore[dataStoreKey];
699
- }
700
-
701
- return {
702
- get: function (node, key) {
703
- var allDataForNode = getAll(node, false);
704
- return allDataForNode === undefined ? undefined : allDataForNode[key];
705
- },
706
- set: function (node, key, value) {
707
- if (value === undefined) {
708
- // Make sure we don't actually create a new domData key if we are actually deleting a value
709
- if (getAll(node, false) === undefined)
710
- return;
713
+ var getDataForNode, clear;
714
+ if (!ko.utils.ieVersion) {
715
+ // We considered using WeakMap, but it has a problem in IE 11 and Edge that prevents using
716
+ // it cross-window, so instead we just store the data directly on the node.
717
+ // See https://github.com/knockout/knockout/issues/2141
718
+ getDataForNode = function (node, createIfNotFound) {
719
+ var dataForNode = node[dataStoreKeyExpandoPropertyName];
720
+ if (!dataForNode && createIfNotFound) {
721
+ dataForNode = node[dataStoreKeyExpandoPropertyName] = {};
722
+ }
723
+ return dataForNode;
724
+ };
725
+ clear = function (node) {
726
+ if (node[dataStoreKeyExpandoPropertyName]) {
727
+ delete node[dataStoreKeyExpandoPropertyName];
728
+ return true; // Exposing "did clean" flag purely so specs can infer whether things have been cleaned up as intended
711
729
  }
712
- var allDataForNode = getAll(node, true);
713
- allDataForNode[key] = value;
714
- },
715
- clear: function (node) {
730
+ return false;
731
+ };
732
+ } else {
733
+ // Old IE versions have memory issues if you store objects on the node, so we use a
734
+ // separate data storage and link to it from the node using a string key.
735
+ getDataForNode = function (node, createIfNotFound) {
736
+ var dataStoreKey = node[dataStoreKeyExpandoPropertyName];
737
+ var hasExistingDataStore = dataStoreKey && (dataStoreKey !== "null") && dataStore[dataStoreKey];
738
+ if (!hasExistingDataStore) {
739
+ if (!createIfNotFound)
740
+ return undefined;
741
+ dataStoreKey = node[dataStoreKeyExpandoPropertyName] = "ko" + uniqueId++;
742
+ dataStore[dataStoreKey] = {};
743
+ }
744
+ return dataStore[dataStoreKey];
745
+ };
746
+ clear = function (node) {
716
747
  var dataStoreKey = node[dataStoreKeyExpandoPropertyName];
717
748
  if (dataStoreKey) {
718
749
  delete dataStore[dataStoreKey];
@@ -720,7 +751,24 @@ ko.utils.domData = new (function () {
720
751
  return true; // Exposing "did clean" flag purely so specs can infer whether things have been cleaned up as intended
721
752
  }
722
753
  return false;
754
+ };
755
+ }
756
+
757
+ return {
758
+ get: function (node, key) {
759
+ var dataForNode = getDataForNode(node, false);
760
+ return dataForNode && dataForNode[key];
761
+ },
762
+ set: function (node, key, value) {
763
+ // Make sure we don't actually create a new domData key if we are actually deleting a value
764
+ var dataForNode = getDataForNode(node, value !== undefined /* createIfNotFound */);
765
+ dataForNode && (dataForNode[key] = value);
766
+ },
767
+ getOrSet: function (node, key, value) {
768
+ var dataForNode = getDataForNode(node, true /* createIfNotFound */);
769
+ return dataForNode[key] || (dataForNode[key] = value);
723
770
  },
771
+ clear: clear,
724
772
 
725
773
  nextKey: function () {
726
774
  return (uniqueId++) + dataStoreKeyExpandoPropertyName;
@@ -765,16 +813,20 @@ ko.utils.domNodeDisposal = new (function () {
765
813
 
766
814
  // Clear any immediate-child comment nodes, as these wouldn't have been found by
767
815
  // node.getElementsByTagName("*") in cleanNode() (comment nodes aren't elements)
768
- if (cleanableNodeTypesWithDescendants[node.nodeType])
769
- cleanImmediateCommentTypeChildren(node);
816
+ if (cleanableNodeTypesWithDescendants[node.nodeType]) {
817
+ cleanNodesInList(node.childNodes, true/*onlyComments*/);
818
+ }
770
819
  }
771
820
 
772
- function cleanImmediateCommentTypeChildren(nodeWithChildren) {
773
- var child, nextChild = nodeWithChildren.firstChild;
774
- while (child = nextChild) {
775
- nextChild = child.nextSibling;
776
- if (child.nodeType === 8)
777
- cleanSingleNode(child);
821
+ function cleanNodesInList(nodeList, onlyComments) {
822
+ var cleanedNodes = [], lastCleanedNode;
823
+ for (var i = 0; i < nodeList.length; i++) {
824
+ if (!onlyComments || nodeList[i].nodeType === 8) {
825
+ cleanSingleNode(cleanedNodes[cleanedNodes.length] = lastCleanedNode = nodeList[i]);
826
+ if (nodeList[i] !== lastCleanedNode) {
827
+ while (i-- && ko.utils.arrayIndexOf(cleanedNodes, nodeList[i]) == -1) {}
828
+ }
829
+ }
778
830
  }
779
831
  }
780
832
 
@@ -801,11 +853,7 @@ ko.utils.domNodeDisposal = new (function () {
801
853
 
802
854
  // ... then its descendants, where applicable
803
855
  if (cleanableNodeTypesWithDescendants[node.nodeType]) {
804
- // Clone the descendants list in case it changes during iteration
805
- var descendants = [];
806
- ko.utils.arrayPushAll(descendants, node.getElementsByTagName("*"));
807
- for (var i = 0, j = descendants.length; i < j; i++)
808
- cleanSingleNode(descendants[i]);
856
+ cleanNodesInList(node.getElementsByTagName("*"));
809
857
  }
810
858
  }
811
859
  return node;
@@ -854,7 +902,7 @@ ko.exportSymbol('utils.domNodeDisposal.removeDisposeCallback', ko.utils.domNodeD
854
902
  mayRequireCreateElementHack = ko.utils.ieVersion <= 8;
855
903
 
856
904
  function getWrap(tags) {
857
- var m = tags.match(/^<([a-z]+)[ >]/);
905
+ var m = tags.match(/^(?:<!--.*?-->\s*?)*?<([a-z]+)[\s>]/);
858
906
  return (m && lookup[m[1]]) || none;
859
907
  }
860
908
 
@@ -887,7 +935,7 @@ ko.exportSymbol('utils.domNodeDisposal.removeDisposeCallback', ko.utils.domNodeD
887
935
  if (mayRequireCreateElementHack) {
888
936
  // The document.createElement('my-element') trick to enable custom elements in IE6-8
889
937
  // only works if we assign innerHTML on an element associated with that document.
890
- documentContext.appendChild(div);
938
+ documentContext.body.appendChild(div);
891
939
  }
892
940
 
893
941
  div.innerHTML = markup;
@@ -935,6 +983,11 @@ ko.exportSymbol('utils.domNodeDisposal.removeDisposeCallback', ko.utils.domNodeD
935
983
  simpleHtmlParse(html, documentContext); // ... otherwise, this simple logic will do in most common cases.
936
984
  };
937
985
 
986
+ ko.utils.parseHtmlForTemplateNodes = function(html, documentContext) {
987
+ var nodes = ko.utils.parseHtmlFragment(html, documentContext);
988
+ return (nodes.length && nodes[0].parentElement) || ko.utils.moveCleanedNodesToContainerElement(nodes);
989
+ };
990
+
938
991
  ko.utils.setHtml = function(node, html) {
939
992
  ko.utils.emptyDomNode(node);
940
993
 
@@ -1175,9 +1228,9 @@ ko.extenders = {
1175
1228
  // rateLimit supersedes deferred updates
1176
1229
  target._deferUpdates = false;
1177
1230
 
1178
- limitFunction = method == 'notifyWhenChangesStop' ? debounce : throttle;
1231
+ limitFunction = typeof method == 'function' ? method : method == 'notifyWhenChangesStop' ? debounce : throttle;
1179
1232
  target.limit(function(callback) {
1180
- return limitFunction(callback, timeout);
1233
+ return limitFunction(callback, timeout, options);
1181
1234
  });
1182
1235
  },
1183
1236
 
@@ -1189,11 +1242,20 @@ ko.extenders = {
1189
1242
  if (!target._deferUpdates) {
1190
1243
  target._deferUpdates = true;
1191
1244
  target.limit(function (callback) {
1192
- var handle;
1245
+ var handle,
1246
+ ignoreUpdates = false;
1193
1247
  return function () {
1194
- ko.tasks.cancel(handle);
1195
- handle = ko.tasks.schedule(callback);
1196
- target['notifySubscribers'](undefined, 'dirty');
1248
+ if (!ignoreUpdates) {
1249
+ ko.tasks.cancel(handle);
1250
+ handle = ko.tasks.schedule(callback);
1251
+
1252
+ try {
1253
+ ignoreUpdates = true;
1254
+ target['notifySubscribers'](undefined, 'dirty');
1255
+ } finally {
1256
+ ignoreUpdates = false;
1257
+ }
1258
+ }
1197
1259
  };
1198
1260
  });
1199
1261
  }
@@ -1249,14 +1311,29 @@ ko.exportSymbol('extenders', ko.extenders);
1249
1311
 
1250
1312
  ko.subscription = function (target, callback, disposeCallback) {
1251
1313
  this._target = target;
1252
- this.callback = callback;
1253
- this.disposeCallback = disposeCallback;
1254
- this.isDisposed = false;
1314
+ this._callback = callback;
1315
+ this._disposeCallback = disposeCallback;
1316
+ this._isDisposed = false;
1317
+ this._node = null;
1318
+ this._domNodeDisposalCallback = null;
1255
1319
  ko.exportProperty(this, 'dispose', this.dispose);
1320
+ ko.exportProperty(this, 'disposeWhenNodeIsRemoved', this.disposeWhenNodeIsRemoved);
1256
1321
  };
1257
1322
  ko.subscription.prototype.dispose = function () {
1258
- this.isDisposed = true;
1259
- this.disposeCallback();
1323
+ var self = this;
1324
+ if (!self._isDisposed) {
1325
+ if (self._domNodeDisposalCallback) {
1326
+ ko.utils.domNodeDisposal.removeDisposeCallback(self._node, self._domNodeDisposalCallback);
1327
+ }
1328
+ self._isDisposed = true;
1329
+ self._disposeCallback();
1330
+
1331
+ self._target = self._callback = self._disposeCallback = self._node = self._domNodeDisposalCallback = null;
1332
+ }
1333
+ };
1334
+ ko.subscription.prototype.disposeWhenNodeIsRemoved = function (node) {
1335
+ this._node = node;
1336
+ ko.utils.domNodeDisposal.addDisposeCallback(node, this._domNodeDisposalCallback = this.dispose.bind(this));
1260
1337
  };
1261
1338
 
1262
1339
  ko.subscribable = function () {
@@ -1279,7 +1356,7 @@ function limitNotifySubscribers(value, event) {
1279
1356
 
1280
1357
  var ko_subscribable_fn = {
1281
1358
  init: function(instance) {
1282
- instance._subscriptions = {};
1359
+ instance._subscriptions = { "change": [] };
1283
1360
  instance._versionNumber = 1;
1284
1361
  },
1285
1362
 
@@ -1311,13 +1388,14 @@ var ko_subscribable_fn = {
1311
1388
  this.updateVersion();
1312
1389
  }
1313
1390
  if (this.hasSubscriptionsForEvent(event)) {
1391
+ var subs = event === defaultEvent && this._changeSubscriptions || this._subscriptions[event].slice(0);
1314
1392
  try {
1315
1393
  ko.dependencyDetection.begin(); // Begin suppressing dependency detection (by setting the top frame to undefined)
1316
- for (var a = this._subscriptions[event].slice(0), i = 0, subscription; subscription = a[i]; ++i) {
1394
+ for (var i = 0, subscription; subscription = subs[i]; ++i) {
1317
1395
  // In case a subscription was disposed during the arrayForEach cycle, check
1318
1396
  // for isDisposed on each subscription before invoking its callback
1319
- if (!subscription.isDisposed)
1320
- subscription.callback(valueToNotify);
1397
+ if (!subscription._isDisposed)
1398
+ subscription._callback(valueToNotify);
1321
1399
  }
1322
1400
  } finally {
1323
1401
  ko.dependencyDetection.end(); // End suppressing dependency detection
@@ -1339,7 +1417,8 @@ var ko_subscribable_fn = {
1339
1417
 
1340
1418
  limit: function(limitFunction) {
1341
1419
  var self = this, selfIsObservable = ko.isObservable(self),
1342
- ignoreBeforeChange, previousValue, pendingValue, beforeChange = 'beforeChange';
1420
+ ignoreBeforeChange, notifyNextChange, previousValue, pendingValue, didUpdate,
1421
+ beforeChange = 'beforeChange';
1343
1422
 
1344
1423
  if (!self._origNotifySubscribers) {
1345
1424
  self._origNotifySubscribers = self["notifySubscribers"];
@@ -1352,15 +1431,22 @@ var ko_subscribable_fn = {
1352
1431
  // If an observable provided a reference to itself, access it to get the latest value.
1353
1432
  // This allows computed observables to delay calculating their value until needed.
1354
1433
  if (selfIsObservable && pendingValue === self) {
1355
- pendingValue = self();
1434
+ pendingValue = self._evalIfChanged ? self._evalIfChanged() : self();
1356
1435
  }
1357
- ignoreBeforeChange = false;
1358
- if (self.isDifferent(previousValue, pendingValue)) {
1436
+ var shouldNotify = notifyNextChange || (didUpdate && self.isDifferent(previousValue, pendingValue));
1437
+
1438
+ didUpdate = notifyNextChange = ignoreBeforeChange = false;
1439
+
1440
+ if (shouldNotify) {
1359
1441
  self._origNotifySubscribers(previousValue = pendingValue);
1360
1442
  }
1361
1443
  });
1362
1444
 
1363
- self._limitChange = function(value) {
1445
+ self._limitChange = function(value, isDirty) {
1446
+ if (!isDirty || !self._notificationIsPending) {
1447
+ didUpdate = !isDirty;
1448
+ }
1449
+ self._changeSubscriptions = self._subscriptions[defaultEvent].slice(0);
1364
1450
  self._notificationIsPending = ignoreBeforeChange = true;
1365
1451
  pendingValue = value;
1366
1452
  finish();
@@ -1371,6 +1457,14 @@ var ko_subscribable_fn = {
1371
1457
  self._origNotifySubscribers(value, beforeChange);
1372
1458
  }
1373
1459
  };
1460
+ self._recordUpdate = function() {
1461
+ didUpdate = true;
1462
+ };
1463
+ self._notifyNextChangeIfValueIsDifferent = function() {
1464
+ if (self.isDifferent(previousValue, self.peek(true /*evaluate*/))) {
1465
+ notifyNextChange = true;
1466
+ }
1467
+ };
1374
1468
  },
1375
1469
 
1376
1470
  hasSubscriptionsForEvent: function(event) {
@@ -1394,9 +1488,14 @@ var ko_subscribable_fn = {
1394
1488
  return !this['equalityComparer'] || !this['equalityComparer'](oldValue, newValue);
1395
1489
  },
1396
1490
 
1491
+ toString: function() {
1492
+ return '[object Object]'
1493
+ },
1494
+
1397
1495
  extend: applyExtenders
1398
1496
  };
1399
1497
 
1498
+ ko.exportProperty(ko_subscribable_fn, 'init', ko_subscribable_fn.init);
1400
1499
  ko.exportProperty(ko_subscribable_fn, 'subscribe', ko_subscribable_fn.subscribe);
1401
1500
  ko.exportProperty(ko_subscribable_fn, 'extend', ko_subscribable_fn.extend);
1402
1501
  ko.exportProperty(ko_subscribable_fn, 'getSubscriptionsCount', ko_subscribable_fn.getSubscriptionsCount);
@@ -1469,16 +1568,28 @@ ko.computedContext = ko.dependencyDetection = (function () {
1469
1568
  return currentFrame.computed.getDependenciesCount();
1470
1569
  },
1471
1570
 
1571
+ getDependencies: function () {
1572
+ if (currentFrame)
1573
+ return currentFrame.computed.getDependencies();
1574
+ },
1575
+
1472
1576
  isInitial: function() {
1473
1577
  if (currentFrame)
1474
1578
  return currentFrame.isInitial;
1579
+ },
1580
+
1581
+ computed: function() {
1582
+ if (currentFrame)
1583
+ return currentFrame.computed;
1475
1584
  }
1476
1585
  };
1477
1586
  })();
1478
1587
 
1479
1588
  ko.exportSymbol('computedContext', ko.computedContext);
1480
1589
  ko.exportSymbol('computedContext.getDependenciesCount', ko.computedContext.getDependenciesCount);
1590
+ ko.exportSymbol('computedContext.getDependencies', ko.computedContext.getDependencies);
1481
1591
  ko.exportSymbol('computedContext.isInitial', ko.computedContext.isInitial);
1592
+ ko.exportSymbol('computedContext.registerDependency', ko.computedContext.registerDependency);
1482
1593
 
1483
1594
  ko.exportSymbol('ignoreDependencies', ko.ignoreDependencies = ko.dependencyDetection.ignore);
1484
1595
  var observableLatestValue = ko.utils.createSymbolOrString('_latestValue');
@@ -1526,7 +1637,10 @@ ko.observable = function (initialValue) {
1526
1637
  var observableFn = {
1527
1638
  'equalityComparer': valuesArePrimitiveAndEqual,
1528
1639
  peek: function() { return this[observableLatestValue]; },
1529
- valueHasMutated: function () { this['notifySubscribers'](this[observableLatestValue]); },
1640
+ valueHasMutated: function () {
1641
+ this['notifySubscribers'](this[observableLatestValue], 'spectate');
1642
+ this['notifySubscribers'](this[observableLatestValue]);
1643
+ },
1530
1644
  valueWillMutate: function () { this['notifySubscribers'](this[observableLatestValue], 'beforeChange'); }
1531
1645
  };
1532
1646
 
@@ -1539,25 +1653,19 @@ if (ko.utils.canSetPrototype) {
1539
1653
  var protoProperty = ko.observable.protoProperty = '__ko_proto__';
1540
1654
  observableFn[protoProperty] = ko.observable;
1541
1655
 
1542
- ko.hasPrototype = function(instance, prototype) {
1543
- if ((instance === null) || (instance === undefined) || (instance[protoProperty] === undefined)) return false;
1544
- if (instance[protoProperty] === prototype) return true;
1545
- return ko.hasPrototype(instance[protoProperty], prototype); // Walk the prototype chain
1656
+ ko.isObservable = function (instance) {
1657
+ var proto = typeof instance == 'function' && instance[protoProperty];
1658
+ if (proto && proto !== observableFn[protoProperty] && proto !== ko.computed['fn'][protoProperty]) {
1659
+ throw Error("Invalid object that looks like an observable; possibly from another Knockout instance");
1660
+ }
1661
+ return !!proto;
1546
1662
  };
1547
1663
 
1548
- ko.isObservable = function (instance) {
1549
- return ko.hasPrototype(instance, ko.observable);
1550
- }
1551
1664
  ko.isWriteableObservable = function (instance) {
1552
- // Observable
1553
- if ((typeof instance == 'function') && instance[protoProperty] === ko.observable)
1554
- return true;
1555
- // Writeable dependent observable
1556
- if ((typeof instance == 'function') && (instance[protoProperty] === ko.dependentObservable) && (instance.hasWriteFunction))
1557
- return true;
1558
- // Anything else
1559
- return false;
1560
- }
1665
+ return (typeof instance == 'function' && (
1666
+ (instance[protoProperty] === observableFn[protoProperty]) || // Observable
1667
+ (instance[protoProperty] === ko.computed['fn'][protoProperty] && instance.hasWriteFunction))); // Writable computed observable
1668
+ };
1561
1669
 
1562
1670
  ko.exportSymbol('observable', ko.observable);
1563
1671
  ko.exportSymbol('isObservable', ko.isObservable);
@@ -1589,6 +1697,9 @@ ko.observableArray['fn'] = {
1589
1697
  if (removedValues.length === 0) {
1590
1698
  this.valueWillMutate();
1591
1699
  }
1700
+ if (underlyingArray[i] !== value) {
1701
+ throw Error("Array modified during remove; cannot remove item");
1702
+ }
1592
1703
  removedValues.push(value);
1593
1704
  underlyingArray.splice(i, 1);
1594
1705
  i--;
@@ -1625,7 +1736,7 @@ ko.observableArray['fn'] = {
1625
1736
  for (var i = underlyingArray.length - 1; i >= 0; i--) {
1626
1737
  var value = underlyingArray[i];
1627
1738
  if (predicate(value))
1628
- underlyingArray[i]["_destroy"] = true;
1739
+ value["_destroy"] = true;
1629
1740
  }
1630
1741
  this.valueHasMutated();
1631
1742
  },
@@ -1655,6 +1766,15 @@ ko.observableArray['fn'] = {
1655
1766
  this.peek()[index] = newItem;
1656
1767
  this.valueHasMutated();
1657
1768
  }
1769
+ },
1770
+
1771
+ 'sorted': function (compareFunction) {
1772
+ var arrayCopy = this().slice(0);
1773
+ return compareFunction ? arrayCopy.sort(compareFunction) : arrayCopy.sort();
1774
+ },
1775
+
1776
+ 'reversed': function () {
1777
+ return this().slice(0).reverse();
1658
1778
  }
1659
1779
  };
1660
1780
 
@@ -1689,7 +1809,14 @@ ko.utils.arrayForEach(["slice"], function (methodName) {
1689
1809
  };
1690
1810
  });
1691
1811
 
1812
+ ko.isObservableArray = function (instance) {
1813
+ return ko.isObservable(instance)
1814
+ && typeof instance["remove"] == "function"
1815
+ && typeof instance["push"] == "function";
1816
+ };
1817
+
1692
1818
  ko.exportSymbol('observableArray', ko.observableArray);
1819
+ ko.exportSymbol('isObservableArray', ko.isObservableArray);
1693
1820
  var arrayChangeEventName = 'arrayChange';
1694
1821
  ko.extenders['trackArrayChanges'] = function(target, options) {
1695
1822
  // Use the provided options--each call to trackArrayChanges overwrites the previously set options
@@ -1707,6 +1834,8 @@ ko.extenders['trackArrayChanges'] = function(target, options) {
1707
1834
  cachedDiff = null,
1708
1835
  arrayChangeSubscription,
1709
1836
  pendingNotifications = 0,
1837
+ previousContents,
1838
+ underlyingNotifySubscribersFunction,
1710
1839
  underlyingBeforeSubscriptionAddFunction = target.beforeSubscriptionAdd,
1711
1840
  underlyingAfterSubscriptionRemoveFunction = target.afterSubscriptionRemove;
1712
1841
 
@@ -1723,21 +1852,31 @@ ko.extenders['trackArrayChanges'] = function(target, options) {
1723
1852
  if (underlyingAfterSubscriptionRemoveFunction)
1724
1853
  underlyingAfterSubscriptionRemoveFunction.call(target, event);
1725
1854
  if (event === arrayChangeEventName && !target.hasSubscriptionsForEvent(arrayChangeEventName)) {
1726
- arrayChangeSubscription.dispose();
1855
+ if (underlyingNotifySubscribersFunction) {
1856
+ target['notifySubscribers'] = underlyingNotifySubscribersFunction;
1857
+ underlyingNotifySubscribersFunction = undefined;
1858
+ }
1859
+ if (arrayChangeSubscription) {
1860
+ arrayChangeSubscription.dispose();
1861
+ }
1862
+ arrayChangeSubscription = null;
1727
1863
  trackingChanges = false;
1864
+ previousContents = undefined;
1728
1865
  }
1729
1866
  };
1730
1867
 
1731
1868
  function trackChanges() {
1732
- // Calling 'trackChanges' multiple times is the same as calling it once
1733
1869
  if (trackingChanges) {
1870
+ // Whenever there's a new subscription and there are pending notifications, make sure all previous
1871
+ // subscriptions are notified of the change so that all subscriptions are in sync.
1872
+ notifyChanges();
1734
1873
  return;
1735
1874
  }
1736
1875
 
1737
1876
  trackingChanges = true;
1738
1877
 
1739
1878
  // Intercept "notifySubscribers" to track how many times it was called.
1740
- var underlyingNotifySubscribersFunction = target['notifySubscribers'];
1879
+ underlyingNotifySubscribersFunction = target['notifySubscribers'];
1741
1880
  target['notifySubscribers'] = function(valueToNotify, event) {
1742
1881
  if (!event || event === defaultEvent) {
1743
1882
  ++pendingNotifications;
@@ -1747,26 +1886,30 @@ ko.extenders['trackArrayChanges'] = function(target, options) {
1747
1886
 
1748
1887
  // Each time the array changes value, capture a clone so that on the next
1749
1888
  // change it's possible to produce a diff
1750
- var previousContents = [].concat(target.peek() || []);
1889
+ previousContents = [].concat(target.peek() || []);
1751
1890
  cachedDiff = null;
1752
- arrayChangeSubscription = target.subscribe(function(currentContents) {
1753
- // Make a copy of the current contents and ensure it's an array
1754
- currentContents = [].concat(currentContents || []);
1891
+ arrayChangeSubscription = target.subscribe(notifyChanges);
1755
1892
 
1756
- // Compute the diff and issue notifications, but only if someone is listening
1757
- if (target.hasSubscriptionsForEvent(arrayChangeEventName)) {
1758
- var changes = getChanges(previousContents, currentContents);
1759
- }
1893
+ function notifyChanges() {
1894
+ if (pendingNotifications) {
1895
+ // Make a copy of the current contents and ensure it's an array
1896
+ var currentContents = [].concat(target.peek() || []);
1760
1897
 
1761
- // Eliminate references to the old, removed items, so they can be GCed
1762
- previousContents = currentContents;
1763
- cachedDiff = null;
1764
- pendingNotifications = 0;
1898
+ // Compute the diff and issue notifications, but only if someone is listening
1899
+ if (target.hasSubscriptionsForEvent(arrayChangeEventName)) {
1900
+ var changes = getChanges(previousContents, currentContents);
1901
+ }
1765
1902
 
1766
- if (changes && changes.length) {
1767
- target['notifySubscribers'](changes, arrayChangeEventName);
1903
+ // Eliminate references to the old, removed items, so they can be GCed
1904
+ previousContents = currentContents;
1905
+ cachedDiff = null;
1906
+ pendingNotifications = 0;
1907
+
1908
+ if (changes && changes.length) {
1909
+ target['notifySubscribers'](changes, arrayChangeEventName);
1910
+ }
1768
1911
  }
1769
- });
1912
+ }
1770
1913
  }
1771
1914
 
1772
1915
  function getChanges(previousContents, currentContents) {
@@ -1855,6 +1998,7 @@ ko.computed = ko.dependentObservable = function (evaluatorFunctionOrOptions, eva
1855
1998
  var state = {
1856
1999
  latestValue: undefined,
1857
2000
  isStale: true,
2001
+ isDirty: true,
1858
2002
  isBeingEvaluated: false,
1859
2003
  suppressDisposalUntilDisposeWhenReturnsFalse: false,
1860
2004
  isDisposed: false,
@@ -1881,8 +2025,10 @@ ko.computed = ko.dependentObservable = function (evaluatorFunctionOrOptions, eva
1881
2025
  return this; // Permits chained assignments
1882
2026
  } else {
1883
2027
  // Reading the value
1884
- ko.dependencyDetection.registerDependency(computedObservable);
1885
- if (state.isStale || (state.isSleeping && computedObservable.haveDependenciesChanged())) {
2028
+ if (!state.isDisposed) {
2029
+ ko.dependencyDetection.registerDependency(computedObservable);
2030
+ }
2031
+ if (state.isDirty || (state.isSleeping && computedObservable.haveDependenciesChanged())) {
1886
2032
  computedObservable.evaluateImmediate();
1887
2033
  }
1888
2034
  return state.latestValue;
@@ -1972,6 +2118,10 @@ function computedBeginDependencyDetectionCallback(subscribable, id) {
1972
2118
  // Brand new subscription - add it
1973
2119
  computedObservable.addDependencyTracking(id, subscribable, state.isSleeping ? { _target: subscribable } : computedObservable.subscribeToDependency(subscribable));
1974
2120
  }
2121
+ // If the observable we've accessed has a pending notification, ensure we get notified of the actual final value (bypass equality checks)
2122
+ if (subscribable._notificationIsPending) {
2123
+ subscribable._notifyNextChangeIfValueIsDifferent();
2124
+ }
1975
2125
  }
1976
2126
  }
1977
2127
 
@@ -1980,6 +2130,27 @@ var computedFn = {
1980
2130
  getDependenciesCount: function () {
1981
2131
  return this[computedState].dependenciesCount;
1982
2132
  },
2133
+ getDependencies: function () {
2134
+ var dependencyTracking = this[computedState].dependencyTracking, dependentObservables = [];
2135
+
2136
+ ko.utils.objectForEach(dependencyTracking, function (id, dependency) {
2137
+ dependentObservables[dependency._order] = dependency._target;
2138
+ });
2139
+
2140
+ return dependentObservables;
2141
+ },
2142
+ hasAncestorDependency: function (obs) {
2143
+ if (!this[computedState].dependenciesCount) {
2144
+ return false;
2145
+ }
2146
+ var dependencies = this.getDependencies();
2147
+ if (ko.utils.arrayIndexOf(dependencies, obs) !== -1) {
2148
+ return true;
2149
+ }
2150
+ return !!ko.utils.arrayFirst(dependencies, function (dep) {
2151
+ return dep.hasAncestorDependency && dep.hasAncestorDependency(obs);
2152
+ });
2153
+ },
1983
2154
  addDependencyTracking: function (id, target, trackingObj) {
1984
2155
  if (this[computedState].pure && target === this) {
1985
2156
  throw Error("A 'pure' computed must not be called recursively");
@@ -1992,9 +2163,9 @@ var computedFn = {
1992
2163
  haveDependenciesChanged: function () {
1993
2164
  var id, dependency, dependencyTracking = this[computedState].dependencyTracking;
1994
2165
  for (id in dependencyTracking) {
1995
- if (dependencyTracking.hasOwnProperty(id)) {
2166
+ if (Object.prototype.hasOwnProperty.call(dependencyTracking, id)) {
1996
2167
  dependency = dependencyTracking[id];
1997
- if (dependency._target.hasChanged(dependency._version)) {
2168
+ if ((this._evalDelayed && dependency._target._notificationIsPending) || dependency._target.hasChanged(dependency._version)) {
1998
2169
  return true;
1999
2170
  }
2000
2171
  }
@@ -2003,20 +2174,23 @@ var computedFn = {
2003
2174
  markDirty: function () {
2004
2175
  // Process "dirty" events if we can handle delayed notifications
2005
2176
  if (this._evalDelayed && !this[computedState].isBeingEvaluated) {
2006
- this._evalDelayed();
2177
+ this._evalDelayed(false /*isChange*/);
2007
2178
  }
2008
2179
  },
2009
2180
  isActive: function () {
2010
- return this[computedState].isStale || this[computedState].dependenciesCount > 0;
2181
+ var state = this[computedState];
2182
+ return state.isDirty || state.dependenciesCount > 0;
2011
2183
  },
2012
2184
  respondToChange: function () {
2013
2185
  // Ignore "change" events if we've already scheduled a delayed notification
2014
2186
  if (!this._notificationIsPending) {
2015
2187
  this.evaluatePossiblyAsync();
2188
+ } else if (this[computedState].isDirty) {
2189
+ this[computedState].isStale = true;
2016
2190
  }
2017
2191
  },
2018
2192
  subscribeToDependency: function (target) {
2019
- if (target._deferUpdates && !this[computedState].disposeWhenNodeIsRemoved) {
2193
+ if (target._deferUpdates) {
2020
2194
  var dirtySub = target.subscribe(this.markDirty, this, 'dirty'),
2021
2195
  changeSub = target.subscribe(this.respondToChange, this);
2022
2196
  return {
@@ -2039,7 +2213,7 @@ var computedFn = {
2039
2213
  computedObservable.evaluateImmediate(true /*notifyChange*/);
2040
2214
  }, throttleEvaluationTimeout);
2041
2215
  } else if (computedObservable._evalDelayed) {
2042
- computedObservable._evalDelayed();
2216
+ computedObservable._evalDelayed(true /*isChange*/);
2043
2217
  } else {
2044
2218
  computedObservable.evaluateImmediate(true /*notifyChange*/);
2045
2219
  }
@@ -2047,7 +2221,8 @@ var computedFn = {
2047
2221
  evaluateImmediate: function (notifyChange) {
2048
2222
  var computedObservable = this,
2049
2223
  state = computedObservable[computedState],
2050
- disposeWhen = state.disposeWhen;
2224
+ disposeWhen = state.disposeWhen,
2225
+ changed = false;
2051
2226
 
2052
2227
  if (state.isBeingEvaluated) {
2053
2228
  // If the evaluation of a ko.computed causes side effects, it's possible that it will trigger its own re-evaluation.
@@ -2075,14 +2250,12 @@ var computedFn = {
2075
2250
 
2076
2251
  state.isBeingEvaluated = true;
2077
2252
  try {
2078
- this.evaluateImmediate_CallReadWithDependencyDetection(notifyChange);
2253
+ changed = this.evaluateImmediate_CallReadWithDependencyDetection(notifyChange);
2079
2254
  } finally {
2080
2255
  state.isBeingEvaluated = false;
2081
2256
  }
2082
2257
 
2083
- if (!state.dependenciesCount) {
2084
- computedObservable.dispose();
2085
- }
2258
+ return changed;
2086
2259
  },
2087
2260
  evaluateImmediate_CallReadWithDependencyDetection: function (notifyChange) {
2088
2261
  // This function is really just part of the evaluateImmediate logic. You would never call it from anywhere else.
@@ -2090,7 +2263,8 @@ var computedFn = {
2090
2263
  // which contributes to saving about 40% off the CPU overhead of computed evaluation (on V8 at least).
2091
2264
 
2092
2265
  var computedObservable = this,
2093
- state = computedObservable[computedState];
2266
+ state = computedObservable[computedState],
2267
+ changed = false;
2094
2268
 
2095
2269
  // Initially, we assume that none of the subscriptions are still being used (i.e., all are candidates for disposal).
2096
2270
  // Then, during evaluation, we cross off any that are in fact still being used.
@@ -2113,23 +2287,38 @@ var computedFn = {
2113
2287
 
2114
2288
  var newValue = this.evaluateImmediate_CallReadThenEndDependencyDetection(state, dependencyDetectionContext);
2115
2289
 
2116
- if (computedObservable.isDifferent(state.latestValue, newValue)) {
2290
+ if (!state.dependenciesCount) {
2291
+ computedObservable.dispose();
2292
+ changed = true; // When evaluation causes a disposal, make sure all dependent computeds get notified so they'll see the new state
2293
+ } else {
2294
+ changed = computedObservable.isDifferent(state.latestValue, newValue);
2295
+ }
2296
+
2297
+ if (changed) {
2117
2298
  if (!state.isSleeping) {
2118
2299
  computedObservable["notifySubscribers"](state.latestValue, "beforeChange");
2300
+ } else {
2301
+ computedObservable.updateVersion();
2119
2302
  }
2120
2303
 
2121
2304
  state.latestValue = newValue;
2305
+ if (DEBUG) computedObservable._latestValue = newValue;
2122
2306
 
2123
- if (state.isSleeping) {
2124
- computedObservable.updateVersion();
2125
- } else if (notifyChange) {
2307
+ computedObservable["notifySubscribers"](state.latestValue, "spectate");
2308
+
2309
+ if (!state.isSleeping && notifyChange) {
2126
2310
  computedObservable["notifySubscribers"](state.latestValue);
2127
2311
  }
2312
+ if (computedObservable._recordUpdate) {
2313
+ computedObservable._recordUpdate();
2314
+ }
2128
2315
  }
2129
2316
 
2130
2317
  if (isInitial) {
2131
2318
  computedObservable["notifySubscribers"](state.latestValue, "awake");
2132
2319
  }
2320
+
2321
+ return changed;
2133
2322
  },
2134
2323
  evaluateImmediate_CallReadThenEndDependencyDetection: function (state, dependencyDetectionContext) {
2135
2324
  // This function is really part of the evaluateImmediate_CallReadWithDependencyDetection logic.
@@ -2148,13 +2337,14 @@ var computedFn = {
2148
2337
  ko.utils.objectForEach(dependencyDetectionContext.disposalCandidates, computedDisposeDependencyCallback);
2149
2338
  }
2150
2339
 
2151
- state.isStale = false;
2340
+ state.isStale = state.isDirty = false;
2152
2341
  }
2153
2342
  },
2154
- peek: function () {
2155
- // Peek won't re-evaluate, except while the computed is sleeping or to get the initial value when "deferEvaluation" is set.
2343
+ peek: function (evaluate) {
2344
+ // By default, peek won't re-evaluate, except while the computed is sleeping or to get the initial value when "deferEvaluation" is set.
2345
+ // Pass in true to evaluate if needed.
2156
2346
  var state = this[computedState];
2157
- if ((state.isStale && !state.dependenciesCount) || (state.isSleeping && this.haveDependenciesChanged())) {
2347
+ if ((state.isDirty && (evaluate || !state.dependenciesCount)) || (state.isSleeping && this.haveDependenciesChanged())) {
2158
2348
  this.evaluateImmediate();
2159
2349
  }
2160
2350
  return state.latestValue;
@@ -2162,15 +2352,29 @@ var computedFn = {
2162
2352
  limit: function (limitFunction) {
2163
2353
  // Override the limit function with one that delays evaluation as well
2164
2354
  ko.subscribable['fn'].limit.call(this, limitFunction);
2165
- this._evalDelayed = function () {
2355
+ this._evalIfChanged = function () {
2356
+ if (!this[computedState].isSleeping) {
2357
+ if (this[computedState].isStale) {
2358
+ this.evaluateImmediate();
2359
+ } else {
2360
+ this[computedState].isDirty = false;
2361
+ }
2362
+ }
2363
+ return this[computedState].latestValue;
2364
+ };
2365
+ this._evalDelayed = function (isChange) {
2166
2366
  this._limitBeforeChange(this[computedState].latestValue);
2167
2367
 
2168
- this[computedState].isStale = true; // Mark as dirty
2368
+ // Mark as dirty
2369
+ this[computedState].isDirty = true;
2370
+ if (isChange) {
2371
+ this[computedState].isStale = true;
2372
+ }
2169
2373
 
2170
- // Pass the observable to the "limit" code, which will access it when
2374
+ // Pass the observable to the "limit" code, which will evaluate it when
2171
2375
  // it's time to do the notification.
2172
- this._limitChange(this);
2173
- }
2376
+ this._limitChange(this, !isChange /* isDirty */);
2377
+ };
2174
2378
  },
2175
2379
  dispose: function () {
2176
2380
  var state = this[computedState];
@@ -2183,12 +2387,18 @@ var computedFn = {
2183
2387
  if (state.disposeWhenNodeIsRemoved && state.domNodeDisposalCallback) {
2184
2388
  ko.utils.domNodeDisposal.removeDisposeCallback(state.disposeWhenNodeIsRemoved, state.domNodeDisposalCallback);
2185
2389
  }
2186
- state.dependencyTracking = null;
2390
+ state.dependencyTracking = undefined;
2187
2391
  state.dependenciesCount = 0;
2188
2392
  state.isDisposed = true;
2189
2393
  state.isStale = false;
2394
+ state.isDirty = false;
2190
2395
  state.isSleeping = false;
2191
- state.disposeWhenNodeIsRemoved = null;
2396
+ state.disposeWhenNodeIsRemoved = undefined;
2397
+ state.disposeWhen = undefined;
2398
+ state.readFunction = undefined;
2399
+ if (!this.hasWriteFunction) {
2400
+ state.evaluatorFunctionTarget = undefined;
2401
+ }
2192
2402
  }
2193
2403
  };
2194
2404
 
@@ -2202,23 +2412,31 @@ var pureComputedOverrides = {
2202
2412
  if (state.isStale || computedObservable.haveDependenciesChanged()) {
2203
2413
  state.dependencyTracking = null;
2204
2414
  state.dependenciesCount = 0;
2205
- state.isStale = true;
2206
- computedObservable.evaluateImmediate();
2415
+ if (computedObservable.evaluateImmediate()) {
2416
+ computedObservable.updateVersion();
2417
+ }
2207
2418
  } else {
2208
2419
  // First put the dependencies in order
2209
- var dependeciesOrder = [];
2420
+ var dependenciesOrder = [];
2210
2421
  ko.utils.objectForEach(state.dependencyTracking, function (id, dependency) {
2211
- dependeciesOrder[dependency._order] = id;
2422
+ dependenciesOrder[dependency._order] = id;
2212
2423
  });
2213
2424
  // Next, subscribe to each one
2214
- ko.utils.arrayForEach(dependeciesOrder, function (id, order) {
2425
+ ko.utils.arrayForEach(dependenciesOrder, function (id, order) {
2215
2426
  var dependency = state.dependencyTracking[id],
2216
2427
  subscription = computedObservable.subscribeToDependency(dependency._target);
2217
2428
  subscription._order = order;
2218
2429
  subscription._version = dependency._version;
2219
2430
  state.dependencyTracking[id] = subscription;
2220
2431
  });
2432
+ // Waking dependencies may have triggered effects
2433
+ if (computedObservable.haveDependenciesChanged()) {
2434
+ if (computedObservable.evaluateImmediate()) {
2435
+ computedObservable.updateVersion();
2436
+ }
2437
+ }
2221
2438
  }
2439
+
2222
2440
  if (!state.isDisposed) { // test since evaluating could trigger disposal
2223
2441
  computedObservable["notifySubscribers"](state.latestValue, "awake");
2224
2442
  }
@@ -2268,18 +2486,16 @@ if (ko.utils.canSetPrototype) {
2268
2486
  ko.utils.setPrototypeOf(computedFn, ko.subscribable['fn']);
2269
2487
  }
2270
2488
 
2271
- // Set the proto chain values for ko.hasPrototype
2489
+ // Set the proto values for ko.computed
2272
2490
  var protoProp = ko.observable.protoProperty; // == "__ko_proto__"
2273
- ko.computed[protoProp] = ko.observable;
2274
2491
  computedFn[protoProp] = ko.computed;
2275
2492
 
2276
2493
  ko.isComputed = function (instance) {
2277
- return ko.hasPrototype(instance, ko.computed);
2494
+ return (typeof instance == 'function' && instance[protoProp] === computedFn[protoProp]);
2278
2495
  };
2279
2496
 
2280
2497
  ko.isPureComputed = function (instance) {
2281
- return ko.hasPrototype(instance, ko.computed)
2282
- && instance[computedState] && instance[computedState].pure;
2498
+ return ko.isComputed(instance) && instance[computedState] && instance[computedState].pure;
2283
2499
  };
2284
2500
 
2285
2501
  ko.exportSymbol('computed', ko.computed);
@@ -2291,6 +2507,7 @@ ko.exportProperty(computedFn, 'peek', computedFn.peek);
2291
2507
  ko.exportProperty(computedFn, 'dispose', computedFn.dispose);
2292
2508
  ko.exportProperty(computedFn, 'isActive', computedFn.isActive);
2293
2509
  ko.exportProperty(computedFn, 'getDependenciesCount', computedFn.getDependenciesCount);
2510
+ ko.exportProperty(computedFn, 'getDependencies', computedFn.getDependencies);
2294
2511
 
2295
2512
  ko.pureComputed = function (evaluatorFunctionOrOptions, evaluatorFunctionTarget) {
2296
2513
  if (typeof evaluatorFunctionOrOptions === 'function') {
@@ -2304,7 +2521,7 @@ ko.pureComputed = function (evaluatorFunctionOrOptions, evaluatorFunctionTarget)
2304
2521
  ko.exportSymbol('pureComputed', ko.pureComputed);
2305
2522
 
2306
2523
  (function() {
2307
- var maxNestedObservableDepth = 10; // Escape the (unlikely) pathalogical case where an observable's current value is itself (or similar reference cycle)
2524
+ var maxNestedObservableDepth = 10; // Escape the (unlikely) pathological case where an observable's current value is itself (or similar reference cycle)
2308
2525
 
2309
2526
  ko.toJS = function(rootObject) {
2310
2527
  if (arguments.length == 0)
@@ -2398,6 +2615,28 @@ ko.exportSymbol('pureComputed', ko.pureComputed);
2398
2615
 
2399
2616
  ko.exportSymbol('toJS', ko.toJS);
2400
2617
  ko.exportSymbol('toJSON', ko.toJSON);
2618
+ ko.when = function(predicate, callback, context) {
2619
+ function kowhen (resolve) {
2620
+ var observable = ko.pureComputed(predicate, context).extend({notify:'always'});
2621
+ var subscription = observable.subscribe(function(value) {
2622
+ if (value) {
2623
+ subscription.dispose();
2624
+ resolve(value);
2625
+ }
2626
+ });
2627
+ // In case the initial value is true, process it right away
2628
+ observable['notifySubscribers'](observable.peek());
2629
+
2630
+ return subscription;
2631
+ }
2632
+ if (typeof Promise === "function" && !callback) {
2633
+ return new Promise(kowhen);
2634
+ } else {
2635
+ return kowhen(callback.bind(context));
2636
+ }
2637
+ };
2638
+
2639
+ ko.exportSymbol('when', ko.when);
2401
2640
  (function () {
2402
2641
  var hasDomDataExpandoProperty = '__ko__hasDomDataOptionValue__';
2403
2642
 
@@ -2423,22 +2662,20 @@ ko.exportSymbol('toJSON', ko.toJSON);
2423
2662
  writeValue: function(element, value, allowUnset) {
2424
2663
  switch (ko.utils.tagNameLower(element)) {
2425
2664
  case 'option':
2426
- switch(typeof value) {
2427
- case "string":
2428
- ko.utils.domData.set(element, ko.bindingHandlers.options.optionValueDomDataKey, undefined);
2429
- if (hasDomDataExpandoProperty in element) { // IE <= 8 throws errors if you delete non-existent properties from a DOM node
2430
- delete element[hasDomDataExpandoProperty];
2431
- }
2432
- element.value = value;
2433
- break;
2434
- default:
2435
- // Store arbitrary object using DomData
2436
- ko.utils.domData.set(element, ko.bindingHandlers.options.optionValueDomDataKey, value);
2437
- element[hasDomDataExpandoProperty] = true;
2665
+ if (typeof value === "string") {
2666
+ ko.utils.domData.set(element, ko.bindingHandlers.options.optionValueDomDataKey, undefined);
2667
+ if (hasDomDataExpandoProperty in element) { // IE <= 8 throws errors if you delete non-existent properties from a DOM node
2668
+ delete element[hasDomDataExpandoProperty];
2669
+ }
2670
+ element.value = value;
2671
+ }
2672
+ else {
2673
+ // Store arbitrary object using DomData
2674
+ ko.utils.domData.set(element, ko.bindingHandlers.options.optionValueDomDataKey, value);
2675
+ element[hasDomDataExpandoProperty] = true;
2438
2676
 
2439
- // Special treatment of numbers is just for backward compatibility. KO 1.2.1 wrote numerical values to element.value.
2440
- element.value = typeof value === "number" ? value : "";
2441
- break;
2677
+ // Special treatment of numbers is just for backward compatibility. KO 1.2.1 wrote numerical values to element.value.
2678
+ element.value = typeof value === "number" ? value : "";
2442
2679
  }
2443
2680
  break;
2444
2681
  case 'select':
@@ -2448,13 +2685,21 @@ ko.exportSymbol('toJSON', ko.toJSON);
2448
2685
  for (var i = 0, n = element.options.length, optionValue; i < n; ++i) {
2449
2686
  optionValue = ko.selectExtensions.readValue(element.options[i]);
2450
2687
  // Include special check to handle selecting a caption with a blank string value
2451
- if (optionValue == value || (optionValue == "" && value === undefined)) {
2688
+ if (optionValue == value || (optionValue === "" && value === undefined)) {
2452
2689
  selection = i;
2453
2690
  break;
2454
2691
  }
2455
2692
  }
2456
2693
  if (allowUnset || selection >= 0 || (value === undefined && element.size > 1)) {
2457
2694
  element.selectedIndex = selection;
2695
+ if (ko.utils.ieVersion === 6) {
2696
+ // Workaround for IE6 bug: It won't reliably apply values to SELECT nodes during the same execution thread
2697
+ // right after you've changed the set of OPTION nodes on it. So for that node type, we'll schedule a second thread
2698
+ // to apply the value as well.
2699
+ ko.utils.setTimeout(function () {
2700
+ element.selectedIndex = selection;
2701
+ }, 0);
2702
+ }
2458
2703
  }
2459
2704
  break;
2460
2705
  default:
@@ -2487,26 +2732,29 @@ ko.expressionRewriting = (function () {
2487
2732
 
2488
2733
  // The following regular expressions will be used to split an object-literal string into tokens
2489
2734
 
2490
- // These two match strings, either with double quotes or single quotes
2491
- var stringDouble = '"(?:[^"\\\\]|\\\\.)*"',
2492
- stringSingle = "'(?:[^'\\\\]|\\\\.)*'",
2493
- // Matches a regular expression (text enclosed by slashes), but will also match sets of divisions
2494
- // as a regular expression (this is handled by the parsing loop below).
2495
- stringRegexp = '/(?:[^/\\\\]|\\\\.)*/\w*',
2496
- // These characters have special meaning to the parser and must not appear in the middle of a
2497
- // token, except as part of a string.
2498
- specials = ',"\'{}()/:[\\]',
2499
- // Match text (at least two characters) that does not contain any of the above special characters,
2500
- // although some of the special characters are allowed to start it (all but the colon and comma).
2501
- // The text can contain spaces, but leading or trailing spaces are skipped.
2502
- everyThingElse = '[^\\s:,/][^' + specials + ']*[^\\s' + specials + ']',
2503
- // Match any non-space character not matched already. This will match colons and commas, since they're
2504
- // not matched by "everyThingElse", but will also match any other single character that wasn't already
2505
- // matched (for example: in "a: 1, b: 2", each of the non-space characters will be matched by oneNotSpace).
2506
- oneNotSpace = '[^\\s]',
2507
-
2508
- // Create the actual regular expression by or-ing the above strings. The order is important.
2509
- bindingToken = RegExp(stringDouble + '|' + stringSingle + '|' + stringRegexp + '|' + everyThingElse + '|' + oneNotSpace, 'g'),
2735
+ var specials = ',"\'`{}()/:[\\]', // These characters have special meaning to the parser and must not appear in the middle of a token, except as part of a string.
2736
+ // Create the actual regular expression by or-ing the following regex strings. The order is important.
2737
+ bindingToken = RegExp([
2738
+ // These match strings, either with double quotes, single quotes, or backticks
2739
+ '"(?:\\\\.|[^"])*"',
2740
+ "'(?:\\\\.|[^'])*'",
2741
+ "`(?:\\\\.|[^`])*`",
2742
+ // Match C style comments
2743
+ "/\\*(?:[^*]|\\*+[^*/])*\\*+/",
2744
+ // Match C++ style comments
2745
+ "//.*\n",
2746
+ // Match a regular expression (text enclosed by slashes), but will also match sets of divisions
2747
+ // as a regular expression (this is handled by the parsing loop below).
2748
+ '/(?:\\\\.|[^/])+/\w*',
2749
+ // Match text (at least two characters) that does not contain any of the above special characters,
2750
+ // although some of the special characters are allowed to start it (all but the colon and comma).
2751
+ // The text can contain spaces, but leading or trailing spaces are skipped.
2752
+ '[^\\s:,/][^' + specials + ']*[^\\s' + specials + ']',
2753
+ // Match any non-space character not matched already. This will match colons and commas, since they're
2754
+ // not matched by "everyThingElse", but will also match any other single character that wasn't already
2755
+ // matched (for example: in "a: 1, b: 2", each of the non-space characters will be matched by oneNotSpace).
2756
+ '[^\\s]'
2757
+ ].join('|'), 'g'),
2510
2758
 
2511
2759
  // Match end of previous token to determine whether a slash is a division or regex.
2512
2760
  divisionLookBehind = /[\])"'A-Za-z0-9_$]+$/,
@@ -2519,13 +2767,14 @@ ko.expressionRewriting = (function () {
2519
2767
  // Trim braces '{' surrounding the whole object literal
2520
2768
  if (str.charCodeAt(0) === 123) str = str.slice(1, -1);
2521
2769
 
2770
+ // Add a newline to correctly match a C++ style comment at the end of the string and
2771
+ // add a comma so that we don't need a separate code block to deal with the last item
2772
+ str += "\n,";
2773
+
2522
2774
  // Split into tokens
2523
2775
  var result = [], toks = str.match(bindingToken), key, values = [], depth = 0;
2524
2776
 
2525
- if (toks) {
2526
- // Append a comma so that we don't need a separate code block to deal with the last item
2527
- toks.push(',');
2528
-
2777
+ if (toks.length > 1) {
2529
2778
  for (var i = 0, tok; tok = toks[i]; ++i) {
2530
2779
  var c = tok.charCodeAt(0);
2531
2780
  // A comma signals the end of a key/value pair if depth is zero
@@ -2542,6 +2791,9 @@ ko.expressionRewriting = (function () {
2542
2791
  key = values.pop();
2543
2792
  continue;
2544
2793
  }
2794
+ // Comments: skip them
2795
+ } else if (c === 47 && tok.length > 1 && (tok.charCodeAt(1) === 47 || tok.charCodeAt(1) === 42)) { // "//" or "/*"
2796
+ continue;
2545
2797
  // A set of slashes is initially matched as a regular expression, but could be division
2546
2798
  } else if (c === 47 && i && tok.length > 1) { // "/"
2547
2799
  // Look at the end of the previous token to determine if the slash is actually division
@@ -2550,7 +2802,6 @@ ko.expressionRewriting = (function () {
2550
2802
  // The slash is actually a division punctuator; re-parse the remainder of the string (not including the slash)
2551
2803
  str = str.substr(str.indexOf(tok) + 1);
2552
2804
  toks = str.match(bindingToken);
2553
- toks.push(',');
2554
2805
  i = -1;
2555
2806
  // Continue with just the slash
2556
2807
  tok = '/';
@@ -2566,6 +2817,9 @@ ko.expressionRewriting = (function () {
2566
2817
  }
2567
2818
  values.push(tok);
2568
2819
  }
2820
+ if (depth > 0) {
2821
+ throw Error("Unbalanced parentheses, braces, or brackets");
2822
+ }
2569
2823
  }
2570
2824
  return result;
2571
2825
  }
@@ -2588,7 +2842,8 @@ ko.expressionRewriting = (function () {
2588
2842
  if (twoWayBindings[key] && (writableVal = getWriteableValue(val))) {
2589
2843
  // For two-way bindings, provide a write method in case the value
2590
2844
  // isn't a writable observable.
2591
- propertyAccessorResultStrings.push("'" + key + "':function(_z){" + writableVal + "=_z}");
2845
+ var writeKey = typeof twoWayBindings[key] == 'string' ? twoWayBindings[key] : key;
2846
+ propertyAccessorResultStrings.push("'" + writeKey + "':function(_z){" + writableVal + "=_z}");
2592
2847
  }
2593
2848
  }
2594
2849
  // Values are wrapped in a function so that each value can be accessed independently
@@ -2696,12 +2951,19 @@ ko.exportSymbol('jsonExpressionRewriting.insertPropertyAccessorsIntoJson', ko.ex
2696
2951
  return (node.nodeType == 8) && endCommentRegex.test(commentNodesHaveTextProperty ? node.text : node.nodeValue);
2697
2952
  }
2698
2953
 
2954
+ function isUnmatchedEndComment(node) {
2955
+ return isEndComment(node) && !(ko.utils.domData.get(node, matchedEndCommentDataKey));
2956
+ }
2957
+
2958
+ var matchedEndCommentDataKey = "__ko_matchedEndComment__"
2959
+
2699
2960
  function getVirtualChildren(startComment, allowUnbalanced) {
2700
2961
  var currentNode = startComment;
2701
2962
  var depth = 1;
2702
2963
  var children = [];
2703
2964
  while (currentNode = currentNode.nextSibling) {
2704
2965
  if (isEndComment(currentNode)) {
2966
+ ko.utils.domData.set(currentNode, matchedEndCommentDataKey, true);
2705
2967
  depth--;
2706
2968
  if (depth === 0)
2707
2969
  return children;
@@ -2805,19 +3067,32 @@ ko.exportSymbol('jsonExpressionRewriting.insertPropertyAccessorsIntoJson', ko.ex
2805
3067
  },
2806
3068
 
2807
3069
  firstChild: function(node) {
2808
- if (!isStartComment(node))
3070
+ if (!isStartComment(node)) {
3071
+ if (node.firstChild && isEndComment(node.firstChild)) {
3072
+ throw new Error("Found invalid end comment, as the first child of " + node);
3073
+ }
2809
3074
  return node.firstChild;
2810
- if (!node.nextSibling || isEndComment(node.nextSibling))
3075
+ } else if (!node.nextSibling || isEndComment(node.nextSibling)) {
2811
3076
  return null;
2812
- return node.nextSibling;
3077
+ } else {
3078
+ return node.nextSibling;
3079
+ }
2813
3080
  },
2814
3081
 
2815
3082
  nextSibling: function(node) {
2816
- if (isStartComment(node))
3083
+ if (isStartComment(node)) {
2817
3084
  node = getMatchingEndComment(node);
2818
- if (node.nextSibling && isEndComment(node.nextSibling))
2819
- return null;
2820
- return node.nextSibling;
3085
+ }
3086
+
3087
+ if (node.nextSibling && isEndComment(node.nextSibling)) {
3088
+ if (isUnmatchedEndComment(node.nextSibling)) {
3089
+ throw Error("Found end comment without a matching opening comment, as child of " + node);
3090
+ } else {
3091
+ return null;
3092
+ }
3093
+ } else {
3094
+ return node.nextSibling;
3095
+ }
2821
3096
  },
2822
3097
 
2823
3098
  hasBindingValue: isStartComment,
@@ -2939,6 +3214,11 @@ ko.exportSymbol('virtualElements.setDomNodeChildren', ko.virtualElements.setDomN
2939
3214
 
2940
3215
  ko.exportSymbol('bindingProvider', ko.bindingProvider);
2941
3216
  (function () {
3217
+ // Hide or don't minify context properties, see https://github.com/knockout/knockout/issues/2294
3218
+ var contextSubscribable = ko.utils.createSymbolOrString('_subscribable');
3219
+ var contextAncestorBindingInfo = ko.utils.createSymbolOrString('_ancestorBindingInfo');
3220
+ var contextDataDependency = ko.utils.createSymbolOrString('_dataDependency');
3221
+
2942
3222
  ko.bindingHandlers = {};
2943
3223
 
2944
3224
  // The following element types will not be recursed into during binding.
@@ -2953,14 +3233,16 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
2953
3233
  'template': true
2954
3234
  };
2955
3235
 
2956
- // Use an overridable method for retrieving binding handlers so that a plugins may support dynamically created handlers
3236
+ // Use an overridable method for retrieving binding handlers so that plugins may support dynamically created handlers
2957
3237
  ko['getBindingHandler'] = function(bindingKey) {
2958
3238
  return ko.bindingHandlers[bindingKey];
2959
3239
  };
2960
3240
 
3241
+ var inheritParentVm = {};
3242
+
2961
3243
  // The ko.bindingContext constructor is only called directly to create the root context. For child
2962
3244
  // contexts, use bindingContext.createChildContext or bindingContext.extend.
2963
- ko.bindingContext = function(dataItemOrAccessor, parentContext, dataItemAlias, extendCallback) {
3245
+ ko.bindingContext = function(dataItemOrAccessor, parentContext, dataItemAlias, extendCallback, options) {
2964
3246
 
2965
3247
  // The binding context object includes static properties for the current, parent, and root view models.
2966
3248
  // If a view model is actually stored in an observable, the corresponding binding context object, and
@@ -2970,22 +3252,16 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
2970
3252
  // we call the function to retrieve the view model. If the function accesses any observables or returns
2971
3253
  // an observable, the dependency is tracked, and those observables can later cause the binding
2972
3254
  // context to be updated.
2973
- var dataItemOrObservable = isFunc ? dataItemOrAccessor() : dataItemOrAccessor,
3255
+ var dataItemOrObservable = isFunc ? realDataItemOrAccessor() : realDataItemOrAccessor,
2974
3256
  dataItem = ko.utils.unwrapObservable(dataItemOrObservable);
2975
3257
 
2976
3258
  if (parentContext) {
2977
- // When a "parent" context is given, register a dependency on the parent context. Thus whenever the
2978
- // parent context is updated, this context will also be updated.
2979
- if (parentContext._subscribable)
2980
- parentContext._subscribable();
2981
-
2982
3259
  // Copy $root and any custom properties from the parent context
2983
3260
  ko.utils.extend(self, parentContext);
2984
3261
 
2985
- // Because the above copy overwrites our own properties, we need to reset them.
2986
- // During the first execution, "subscribable" isn't set, so don't bother doing the update then.
2987
- if (subscribable) {
2988
- self._subscribable = subscribable;
3262
+ // Copy Symbol properties
3263
+ if (contextAncestorBindingInfo in parentContext) {
3264
+ self[contextAncestorBindingInfo] = parentContext[contextAncestorBindingInfo];
2989
3265
  }
2990
3266
  } else {
2991
3267
  self['$parents'] = [];
@@ -2996,8 +3272,16 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
2996
3272
  // See https://github.com/SteveSanderson/knockout/issues/490
2997
3273
  self['ko'] = ko;
2998
3274
  }
2999
- self['$rawData'] = dataItemOrObservable;
3000
- self['$data'] = dataItem;
3275
+
3276
+ self[contextSubscribable] = subscribable;
3277
+
3278
+ if (shouldInheritData) {
3279
+ dataItem = self['$data'];
3280
+ } else {
3281
+ self['$rawData'] = dataItemOrObservable;
3282
+ self['$data'] = dataItem;
3283
+ }
3284
+
3001
3285
  if (dataItemAlias)
3002
3286
  self[dataItemAlias] = dataItem;
3003
3287
 
@@ -3007,44 +3291,45 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
3007
3291
  if (extendCallback)
3008
3292
  extendCallback(self, parentContext, dataItem);
3009
3293
 
3294
+ // When a "parent" context is given and we don't already have a dependency on its context, register a dependency on it.
3295
+ // Thus whenever the parent context is updated, this context will also be updated.
3296
+ if (parentContext && parentContext[contextSubscribable] && !ko.computedContext.computed().hasAncestorDependency(parentContext[contextSubscribable])) {
3297
+ parentContext[contextSubscribable]();
3298
+ }
3299
+
3300
+ if (dataDependency) {
3301
+ self[contextDataDependency] = dataDependency;
3302
+ }
3303
+
3010
3304
  return self['$data'];
3011
3305
  }
3012
- function disposeWhen() {
3013
- return nodes && !ko.utils.anyDomNodeIsAttachedToDocument(nodes);
3014
- }
3015
3306
 
3016
3307
  var self = this,
3017
- isFunc = typeof(dataItemOrAccessor) == "function" && !ko.isObservable(dataItemOrAccessor),
3308
+ shouldInheritData = dataItemOrAccessor === inheritParentVm,
3309
+ realDataItemOrAccessor = shouldInheritData ? undefined : dataItemOrAccessor,
3310
+ isFunc = typeof(realDataItemOrAccessor) == "function" && !ko.isObservable(realDataItemOrAccessor),
3018
3311
  nodes,
3019
- subscribable = ko.dependentObservable(updateContext, null, { disposeWhen: disposeWhen, disposeWhenNodeIsRemoved: true });
3020
-
3021
- // At this point, the binding context has been initialized, and the "subscribable" computed observable is
3022
- // subscribed to any observables that were accessed in the process. If there is nothing to track, the
3023
- // computed will be inactive, and we can safely throw it away. If it's active, the computed is stored in
3024
- // the context object.
3025
- if (subscribable.isActive()) {
3026
- self._subscribable = subscribable;
3027
-
3028
- // Always notify because even if the model ($data) hasn't changed, other context properties might have changed
3029
- subscribable['equalityComparer'] = null;
3030
-
3031
- // We need to be able to dispose of this computed observable when it's no longer needed. This would be
3032
- // easy if we had a single node to watch, but binding contexts can be used by many different nodes, and
3033
- // we cannot assume that those nodes have any relation to each other. So instead we track any node that
3034
- // the context is attached to, and dispose the computed when all of those nodes have been cleaned.
3035
-
3036
- // Add properties to *subscribable* instead of *self* because any properties added to *self* may be overwritten on updates
3037
- nodes = [];
3038
- subscribable._addNode = function(node) {
3039
- nodes.push(node);
3040
- ko.utils.domNodeDisposal.addDisposeCallback(node, function(node) {
3041
- ko.utils.arrayRemoveItem(nodes, node);
3042
- if (!nodes.length) {
3043
- subscribable.dispose();
3044
- self._subscribable = subscribable = undefined;
3045
- }
3046
- });
3047
- };
3312
+ subscribable,
3313
+ dataDependency = options && options['dataDependency'];
3314
+
3315
+ if (options && options['exportDependencies']) {
3316
+ // The "exportDependencies" option means that the calling code will track any dependencies and re-create
3317
+ // the binding context when they change.
3318
+ updateContext();
3319
+ } else {
3320
+ subscribable = ko.pureComputed(updateContext);
3321
+ subscribable.peek();
3322
+
3323
+ // At this point, the binding context has been initialized, and the "subscribable" computed observable is
3324
+ // subscribed to any observables that were accessed in the process. If there is nothing to track, the
3325
+ // computed will be inactive, and we can safely throw it away. If it's active, the computed is stored in
3326
+ // the context object.
3327
+ if (subscribable.isActive()) {
3328
+ // Always notify because even if the model ($data) hasn't changed, other context properties might have changed
3329
+ subscribable['equalityComparer'] = null;
3330
+ } else {
3331
+ self[contextSubscribable] = undefined;
3332
+ }
3048
3333
  }
3049
3334
  }
3050
3335
 
@@ -3053,8 +3338,23 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
3053
3338
  // But this does not mean that the $data value of the child context will also get updated. If the child
3054
3339
  // view model also depends on the parent view model, you must provide a function that returns the correct
3055
3340
  // view model on each update.
3056
- ko.bindingContext.prototype['createChildContext'] = function (dataItemOrAccessor, dataItemAlias, extendCallback) {
3057
- return new ko.bindingContext(dataItemOrAccessor, this, dataItemAlias, function(self, parentContext) {
3341
+ ko.bindingContext.prototype['createChildContext'] = function (dataItemOrAccessor, dataItemAlias, extendCallback, options) {
3342
+ if (!options && dataItemAlias && typeof dataItemAlias == "object") {
3343
+ options = dataItemAlias;
3344
+ dataItemAlias = options['as'];
3345
+ extendCallback = options['extend'];
3346
+ }
3347
+
3348
+ if (dataItemAlias && options && options['noChildContext']) {
3349
+ var isFunc = typeof(dataItemOrAccessor) == "function" && !ko.isObservable(dataItemOrAccessor);
3350
+ return new ko.bindingContext(inheritParentVm, this, null, function (self) {
3351
+ if (extendCallback)
3352
+ extendCallback(self);
3353
+ self[dataItemAlias] = isFunc ? dataItemOrAccessor() : dataItemOrAccessor;
3354
+ }, options);
3355
+ }
3356
+
3357
+ return new ko.bindingContext(dataItemOrAccessor, this, dataItemAlias, function (self, parentContext) {
3058
3358
  // Extend the context hierarchy by setting the appropriate pointers
3059
3359
  self['$parentContext'] = parentContext;
3060
3360
  self['$parent'] = parentContext['$data'];
@@ -3062,24 +3362,113 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
3062
3362
  self['$parents'].unshift(self['$parent']);
3063
3363
  if (extendCallback)
3064
3364
  extendCallback(self);
3065
- });
3365
+ }, options);
3066
3366
  };
3067
3367
 
3068
3368
  // Extend the binding context with new custom properties. This doesn't change the context hierarchy.
3069
3369
  // Similarly to "child" contexts, provide a function here to make sure that the correct values are set
3070
3370
  // when an observable view model is updated.
3071
- ko.bindingContext.prototype['extend'] = function(properties) {
3072
- // If the parent context references an observable view model, "_subscribable" will always be the
3073
- // latest view model object. If not, "_subscribable" isn't set, and we can use the static "$data" value.
3074
- return new ko.bindingContext(this._subscribable || this['$data'], this, null, function(self, parentContext) {
3075
- // This "child" context doesn't directly track a parent observable view model,
3076
- // so we need to manually set the $rawData value to match the parent.
3077
- self['$rawData'] = parentContext['$rawData'];
3078
- ko.utils.extend(self, typeof(properties) == "function" ? properties() : properties);
3079
- });
3371
+ ko.bindingContext.prototype['extend'] = function(properties, options) {
3372
+ return new ko.bindingContext(inheritParentVm, this, null, function(self, parentContext) {
3373
+ ko.utils.extend(self, typeof(properties) == "function" ? properties(self) : properties);
3374
+ }, options);
3375
+ };
3376
+
3377
+ var boundElementDomDataKey = ko.utils.domData.nextKey();
3378
+
3379
+ function asyncContextDispose(node) {
3380
+ var bindingInfo = ko.utils.domData.get(node, boundElementDomDataKey),
3381
+ asyncContext = bindingInfo && bindingInfo.asyncContext;
3382
+ if (asyncContext) {
3383
+ bindingInfo.asyncContext = null;
3384
+ asyncContext.notifyAncestor();
3385
+ }
3386
+ }
3387
+ function AsyncCompleteContext(node, bindingInfo, ancestorBindingInfo) {
3388
+ this.node = node;
3389
+ this.bindingInfo = bindingInfo;
3390
+ this.asyncDescendants = [];
3391
+ this.childrenComplete = false;
3392
+
3393
+ if (!bindingInfo.asyncContext) {
3394
+ ko.utils.domNodeDisposal.addDisposeCallback(node, asyncContextDispose);
3395
+ }
3396
+
3397
+ if (ancestorBindingInfo && ancestorBindingInfo.asyncContext) {
3398
+ ancestorBindingInfo.asyncContext.asyncDescendants.push(node);
3399
+ this.ancestorBindingInfo = ancestorBindingInfo;
3400
+ }
3401
+ }
3402
+ AsyncCompleteContext.prototype.notifyAncestor = function () {
3403
+ if (this.ancestorBindingInfo && this.ancestorBindingInfo.asyncContext) {
3404
+ this.ancestorBindingInfo.asyncContext.descendantComplete(this.node);
3405
+ }
3406
+ };
3407
+ AsyncCompleteContext.prototype.descendantComplete = function (node) {
3408
+ ko.utils.arrayRemoveItem(this.asyncDescendants, node);
3409
+ if (!this.asyncDescendants.length && this.childrenComplete) {
3410
+ this.completeChildren();
3411
+ }
3412
+ };
3413
+ AsyncCompleteContext.prototype.completeChildren = function () {
3414
+ this.childrenComplete = true;
3415
+ if (this.bindingInfo.asyncContext && !this.asyncDescendants.length) {
3416
+ this.bindingInfo.asyncContext = null;
3417
+ ko.utils.domNodeDisposal.removeDisposeCallback(this.node, asyncContextDispose);
3418
+ ko.bindingEvent.notify(this.node, ko.bindingEvent.descendantsComplete);
3419
+ this.notifyAncestor();
3420
+ }
3421
+ };
3422
+
3423
+ ko.bindingEvent = {
3424
+ childrenComplete: "childrenComplete",
3425
+ descendantsComplete : "descendantsComplete",
3426
+
3427
+ subscribe: function (node, event, callback, context) {
3428
+ var bindingInfo = ko.utils.domData.getOrSet(node, boundElementDomDataKey, {});
3429
+ if (!bindingInfo.eventSubscribable) {
3430
+ bindingInfo.eventSubscribable = new ko.subscribable;
3431
+ }
3432
+ return bindingInfo.eventSubscribable.subscribe(callback, context, event);
3433
+ },
3434
+
3435
+ notify: function (node, event) {
3436
+ var bindingInfo = ko.utils.domData.get(node, boundElementDomDataKey);
3437
+ if (bindingInfo) {
3438
+ if (bindingInfo.eventSubscribable) {
3439
+ bindingInfo.eventSubscribable['notifySubscribers'](node, event);
3440
+ }
3441
+ if (event == ko.bindingEvent.childrenComplete) {
3442
+ if (bindingInfo.asyncContext) {
3443
+ bindingInfo.asyncContext.completeChildren();
3444
+ } else if (bindingInfo.asyncContext === undefined && bindingInfo.eventSubscribable && bindingInfo.eventSubscribable.hasSubscriptionsForEvent(ko.bindingEvent.descendantsComplete)) {
3445
+ // It's currently an error to register a descendantsComplete handler for a node that was never registered as completing asynchronously.
3446
+ // That's because without the asyncContext, we don't have a way to know that all descendants have completed.
3447
+ throw new Error("descendantsComplete event not supported for bindings on this node");
3448
+ }
3449
+ }
3450
+ }
3451
+ },
3452
+
3453
+ startPossiblyAsyncContentBinding: function (node, bindingContext) {
3454
+ var bindingInfo = ko.utils.domData.getOrSet(node, boundElementDomDataKey, {});
3455
+
3456
+ if (!bindingInfo.asyncContext) {
3457
+ bindingInfo.asyncContext = new AsyncCompleteContext(node, bindingInfo, bindingContext[contextAncestorBindingInfo]);
3458
+ }
3459
+
3460
+ // If the provided context was already extended with this node's binding info, just return the extended context
3461
+ if (bindingContext[contextAncestorBindingInfo] == bindingInfo) {
3462
+ return bindingContext;
3463
+ }
3464
+
3465
+ return bindingContext['extend'](function (ctx) {
3466
+ ctx[contextAncestorBindingInfo] = bindingInfo;
3467
+ });
3468
+ }
3080
3469
  };
3081
3470
 
3082
- // Returns the valueAccesor function for a binding value
3471
+ // Returns the valueAccessor function for a binding value
3083
3472
  function makeValueAccessor(value) {
3084
3473
  return function() {
3085
3474
  return value;
@@ -3125,62 +3514,55 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
3125
3514
  throw new Error("The binding '" + bindingName + "' cannot be used with virtual elements")
3126
3515
  }
3127
3516
 
3128
- function applyBindingsToDescendantsInternal (bindingContext, elementOrVirtualElement, bindingContextsMayDifferFromDomParentElement) {
3129
- var currentChild,
3130
- nextInQueue = ko.virtualElements.firstChild(elementOrVirtualElement),
3131
- provider = ko.bindingProvider['instance'],
3132
- preprocessNode = provider['preprocessNode'];
3517
+ function applyBindingsToDescendantsInternal(bindingContext, elementOrVirtualElement) {
3518
+ var nextInQueue = ko.virtualElements.firstChild(elementOrVirtualElement);
3519
+
3520
+ if (nextInQueue) {
3521
+ var currentChild,
3522
+ provider = ko.bindingProvider['instance'],
3523
+ preprocessNode = provider['preprocessNode'];
3524
+
3525
+ // Preprocessing allows a binding provider to mutate a node before bindings are applied to it. For example it's
3526
+ // possible to insert new siblings after it, and/or replace the node with a different one. This can be used to
3527
+ // implement custom binding syntaxes, such as {{ value }} for string interpolation, or custom element types that
3528
+ // trigger insertion of <template> contents at that point in the document.
3529
+ if (preprocessNode) {
3530
+ while (currentChild = nextInQueue) {
3531
+ nextInQueue = ko.virtualElements.nextSibling(currentChild);
3532
+ preprocessNode.call(provider, currentChild);
3533
+ }
3534
+ // Reset nextInQueue for the next loop
3535
+ nextInQueue = ko.virtualElements.firstChild(elementOrVirtualElement);
3536
+ }
3133
3537
 
3134
- // Preprocessing allows a binding provider to mutate a node before bindings are applied to it. For example it's
3135
- // possible to insert new siblings after it, and/or replace the node with a different one. This can be used to
3136
- // implement custom binding syntaxes, such as {{ value }} for string interpolation, or custom element types that
3137
- // trigger insertion of <template> contents at that point in the document.
3138
- if (preprocessNode) {
3139
3538
  while (currentChild = nextInQueue) {
3539
+ // Keep a record of the next child *before* applying bindings, in case the binding removes the current child from its position
3140
3540
  nextInQueue = ko.virtualElements.nextSibling(currentChild);
3141
- preprocessNode.call(provider, currentChild);
3541
+ applyBindingsToNodeAndDescendantsInternal(bindingContext, currentChild);
3142
3542
  }
3143
- // Reset nextInQueue for the next loop
3144
- nextInQueue = ko.virtualElements.firstChild(elementOrVirtualElement);
3145
- }
3146
-
3147
- while (currentChild = nextInQueue) {
3148
- // Keep a record of the next child *before* applying bindings, in case the binding removes the current child from its position
3149
- nextInQueue = ko.virtualElements.nextSibling(currentChild);
3150
- applyBindingsToNodeAndDescendantsInternal(bindingContext, currentChild, bindingContextsMayDifferFromDomParentElement);
3151
3543
  }
3544
+ ko.bindingEvent.notify(elementOrVirtualElement, ko.bindingEvent.childrenComplete);
3152
3545
  }
3153
3546
 
3154
- function applyBindingsToNodeAndDescendantsInternal (bindingContext, nodeVerified, bindingContextMayDifferFromDomParentElement) {
3155
- var shouldBindDescendants = true;
3547
+ function applyBindingsToNodeAndDescendantsInternal(bindingContext, nodeVerified) {
3548
+ var bindingContextForDescendants = bindingContext;
3156
3549
 
3157
- // Perf optimisation: Apply bindings only if...
3158
- // (1) We need to store the binding context on this node (because it may differ from the DOM parent node's binding context)
3159
- // Note that we can't store binding contexts on non-elements (e.g., text nodes), as IE doesn't allow expando properties for those
3160
- // (2) It might have bindings (e.g., it has a data-bind attribute, or it's a marker for a containerless template)
3161
3550
  var isElement = (nodeVerified.nodeType === 1);
3162
3551
  if (isElement) // Workaround IE <= 8 HTML parsing weirdness
3163
3552
  ko.virtualElements.normaliseVirtualElementDomStructure(nodeVerified);
3164
3553
 
3165
- var shouldApplyBindings = (isElement && bindingContextMayDifferFromDomParentElement) // Case (1)
3166
- || ko.bindingProvider['instance']['nodeHasBindings'](nodeVerified); // Case (2)
3554
+ // Perf optimisation: Apply bindings only if...
3555
+ // (1) We need to store the binding info for the node (all element nodes)
3556
+ // (2) It might have bindings (e.g., it has a data-bind attribute, or it's a marker for a containerless template)
3557
+ var shouldApplyBindings = isElement || ko.bindingProvider['instance']['nodeHasBindings'](nodeVerified);
3167
3558
  if (shouldApplyBindings)
3168
- shouldBindDescendants = applyBindingsToNodeInternal(nodeVerified, null, bindingContext, bindingContextMayDifferFromDomParentElement)['shouldBindDescendants'];
3559
+ bindingContextForDescendants = applyBindingsToNodeInternal(nodeVerified, null, bindingContext)['bindingContextForDescendants'];
3169
3560
 
3170
- if (shouldBindDescendants && !bindingDoesNotRecurseIntoElementTypes[ko.utils.tagNameLower(nodeVerified)]) {
3171
- // We're recursing automatically into (real or virtual) child nodes without changing binding contexts. So,
3172
- // * For children of a *real* element, the binding context is certainly the same as on their DOM .parentNode,
3173
- // hence bindingContextsMayDifferFromDomParentElement is false
3174
- // * For children of a *virtual* element, we can't be sure. Evaluating .parentNode on those children may
3175
- // skip over any number of intermediate virtual elements, any of which might define a custom binding context,
3176
- // hence bindingContextsMayDifferFromDomParentElement is true
3177
- applyBindingsToDescendantsInternal(bindingContext, nodeVerified, /* bindingContextsMayDifferFromDomParentElement: */ !isElement);
3561
+ if (bindingContextForDescendants && !bindingDoesNotRecurseIntoElementTypes[ko.utils.tagNameLower(nodeVerified)]) {
3562
+ applyBindingsToDescendantsInternal(bindingContextForDescendants, nodeVerified);
3178
3563
  }
3179
3564
  }
3180
3565
 
3181
- var boundElementDomDataKey = ko.utils.domData.nextKey();
3182
-
3183
-
3184
3566
  function topologicalSortBindings(bindings) {
3185
3567
  // Depth-first sort
3186
3568
  var result = [], // The list of key/handler pairs that we will return
@@ -3214,21 +3596,20 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
3214
3596
  return result;
3215
3597
  }
3216
3598
 
3217
- function applyBindingsToNodeInternal(node, sourceBindings, bindingContext, bindingContextMayDifferFromDomParentElement) {
3599
+ function applyBindingsToNodeInternal(node, sourceBindings, bindingContext) {
3600
+ var bindingInfo = ko.utils.domData.getOrSet(node, boundElementDomDataKey, {});
3601
+
3218
3602
  // Prevent multiple applyBindings calls for the same node, except when a binding value is specified
3219
- var alreadyBound = ko.utils.domData.get(node, boundElementDomDataKey);
3603
+ var alreadyBound = bindingInfo.alreadyBound;
3220
3604
  if (!sourceBindings) {
3221
3605
  if (alreadyBound) {
3222
3606
  throw Error("You cannot apply bindings multiple times to the same element.");
3223
3607
  }
3224
- ko.utils.domData.set(node, boundElementDomDataKey, true);
3608
+ bindingInfo.alreadyBound = true;
3609
+ }
3610
+ if (!alreadyBound) {
3611
+ bindingInfo.context = bindingContext;
3225
3612
  }
3226
-
3227
- // Optimization: Don't store the binding context on this node if it's definitely the same as on node.parentNode, because
3228
- // we can easily recover it just by scanning up the node's ancestors in the DOM
3229
- // (note: here, parent node means "real DOM parent" not "virtual parent", as there's no O(1) way to find the virtual parent)
3230
- if (!alreadyBound && bindingContextMayDifferFromDomParentElement)
3231
- ko.storedBindingContextForNode(node, bindingContext);
3232
3613
 
3233
3614
  // Use bindings if given, otherwise fall back on asking the bindings provider to give us some bindings
3234
3615
  var bindings;
@@ -3244,8 +3625,14 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
3244
3625
  function() {
3245
3626
  bindings = sourceBindings ? sourceBindings(bindingContext, node) : getBindings.call(provider, node, bindingContext);
3246
3627
  // Register a dependency on the binding context to support observable view models.
3247
- if (bindings && bindingContext._subscribable)
3248
- bindingContext._subscribable();
3628
+ if (bindings) {
3629
+ if (bindingContext[contextSubscribable]) {
3630
+ bindingContext[contextSubscribable]();
3631
+ }
3632
+ if (bindingContext[contextDataDependency]) {
3633
+ bindingContext[contextDataDependency]();
3634
+ }
3635
+ }
3249
3636
  return bindings;
3250
3637
  },
3251
3638
  null, { disposeWhenNodeIsRemoved: node }
@@ -3255,6 +3642,7 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
3255
3642
  bindingsUpdater = null;
3256
3643
  }
3257
3644
 
3645
+ var contextToExtend = bindingContext;
3258
3646
  var bindingHandlerThatControlsDescendantBindings;
3259
3647
  if (bindings) {
3260
3648
  // Return the value accessor for a given binding. When bindings are static (won't be updated because of a binding
@@ -3281,6 +3669,28 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
3281
3669
  return key in bindings;
3282
3670
  };
3283
3671
 
3672
+ if (ko.bindingEvent.childrenComplete in bindings) {
3673
+ ko.bindingEvent.subscribe(node, ko.bindingEvent.childrenComplete, function () {
3674
+ var callback = evaluateValueAccessor(bindings[ko.bindingEvent.childrenComplete]);
3675
+ if (callback) {
3676
+ var nodes = ko.virtualElements.childNodes(node);
3677
+ if (nodes.length) {
3678
+ callback(nodes, ko.dataFor(nodes[0]));
3679
+ }
3680
+ }
3681
+ });
3682
+ }
3683
+
3684
+ if (ko.bindingEvent.descendantsComplete in bindings) {
3685
+ contextToExtend = ko.bindingEvent.startPossiblyAsyncContentBinding(node, bindingContext);
3686
+ ko.bindingEvent.subscribe(node, ko.bindingEvent.descendantsComplete, function () {
3687
+ var callback = evaluateValueAccessor(bindings[ko.bindingEvent.descendantsComplete]);
3688
+ if (callback && ko.virtualElements.firstChild(node)) {
3689
+ callback(node);
3690
+ }
3691
+ });
3692
+ }
3693
+
3284
3694
  // First put the bindings into the right order
3285
3695
  var orderedBindings = topologicalSortBindings(bindings);
3286
3696
 
@@ -3300,7 +3710,7 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
3300
3710
  // Run init, ignoring any dependencies
3301
3711
  if (typeof handlerInitFn == "function") {
3302
3712
  ko.dependencyDetection.ignore(function() {
3303
- var initResult = handlerInitFn(node, getValueAccessor(bindingKey), allBindings, bindingContext['$data'], bindingContext);
3713
+ var initResult = handlerInitFn(node, getValueAccessor(bindingKey), allBindings, contextToExtend['$data'], contextToExtend);
3304
3714
 
3305
3715
  // If this binding handler claims to control descendant bindings, make a note of this
3306
3716
  if (initResult && initResult['controlsDescendantBindings']) {
@@ -3315,7 +3725,7 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
3315
3725
  if (typeof handlerUpdateFn == "function") {
3316
3726
  ko.dependentObservable(
3317
3727
  function() {
3318
- handlerUpdateFn(node, getValueAccessor(bindingKey), allBindings, bindingContext['$data'], bindingContext);
3728
+ handlerUpdateFn(node, getValueAccessor(bindingKey), allBindings, contextToExtend['$data'], contextToExtend);
3319
3729
  },
3320
3730
  null,
3321
3731
  { disposeWhenNodeIsRemoved: node }
@@ -3328,32 +3738,28 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
3328
3738
  });
3329
3739
  }
3330
3740
 
3741
+ var shouldBindDescendants = bindingHandlerThatControlsDescendantBindings === undefined;
3331
3742
  return {
3332
- 'shouldBindDescendants': bindingHandlerThatControlsDescendantBindings === undefined
3743
+ 'shouldBindDescendants': shouldBindDescendants,
3744
+ 'bindingContextForDescendants': shouldBindDescendants && contextToExtend
3333
3745
  };
3334
3746
  };
3335
3747
 
3336
- var storedBindingContextDomDataKey = ko.utils.domData.nextKey();
3337
- ko.storedBindingContextForNode = function (node, bindingContext) {
3338
- if (arguments.length == 2) {
3339
- ko.utils.domData.set(node, storedBindingContextDomDataKey, bindingContext);
3340
- if (bindingContext._subscribable)
3341
- bindingContext._subscribable._addNode(node);
3342
- } else {
3343
- return ko.utils.domData.get(node, storedBindingContextDomDataKey);
3344
- }
3748
+ ko.storedBindingContextForNode = function (node) {
3749
+ var bindingInfo = ko.utils.domData.get(node, boundElementDomDataKey);
3750
+ return bindingInfo && bindingInfo.context;
3345
3751
  }
3346
3752
 
3347
- function getBindingContext(viewModelOrBindingContext) {
3753
+ function getBindingContext(viewModelOrBindingContext, extendContextCallback) {
3348
3754
  return viewModelOrBindingContext && (viewModelOrBindingContext instanceof ko.bindingContext)
3349
3755
  ? viewModelOrBindingContext
3350
- : new ko.bindingContext(viewModelOrBindingContext);
3756
+ : new ko.bindingContext(viewModelOrBindingContext, undefined, undefined, extendContextCallback);
3351
3757
  }
3352
3758
 
3353
3759
  ko.applyBindingAccessorsToNode = function (node, bindings, viewModelOrBindingContext) {
3354
3760
  if (node.nodeType === 1) // If it's an element, workaround IE <= 8 HTML parsing weirdness
3355
3761
  ko.virtualElements.normaliseVirtualElementDomStructure(node);
3356
- return applyBindingsToNodeInternal(node, bindings, getBindingContext(viewModelOrBindingContext), true);
3762
+ return applyBindingsToNodeInternal(node, bindings, getBindingContext(viewModelOrBindingContext));
3357
3763
  };
3358
3764
 
3359
3765
  ko.applyBindingsToNode = function (node, bindings, viewModelOrBindingContext) {
@@ -3363,32 +3769,32 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
3363
3769
 
3364
3770
  ko.applyBindingsToDescendants = function(viewModelOrBindingContext, rootNode) {
3365
3771
  if (rootNode.nodeType === 1 || rootNode.nodeType === 8)
3366
- applyBindingsToDescendantsInternal(getBindingContext(viewModelOrBindingContext), rootNode, true);
3772
+ applyBindingsToDescendantsInternal(getBindingContext(viewModelOrBindingContext), rootNode);
3367
3773
  };
3368
3774
 
3369
- ko.applyBindings = function (viewModelOrBindingContext, rootNode) {
3775
+ ko.applyBindings = function (viewModelOrBindingContext, rootNode, extendContextCallback) {
3370
3776
  // If jQuery is loaded after Knockout, we won't initially have access to it. So save it here.
3371
3777
  if (!jQueryInstance && window['jQuery']) {
3372
3778
  jQueryInstance = window['jQuery'];
3373
3779
  }
3374
3780
 
3375
- if (rootNode && (rootNode.nodeType !== 1) && (rootNode.nodeType !== 8))
3376
- throw new Error("ko.applyBindings: first parameter should be your view model; second parameter should be a DOM node");
3377
- rootNode = rootNode || window.document.body; // Make "rootNode" parameter optional
3781
+ if (arguments.length < 2) {
3782
+ rootNode = document.body;
3783
+ if (!rootNode) {
3784
+ throw Error("ko.applyBindings: could not find document.body; has the document been loaded?");
3785
+ }
3786
+ } else if (!rootNode || (rootNode.nodeType !== 1 && rootNode.nodeType !== 8)) {
3787
+ throw Error("ko.applyBindings: first parameter should be your view model; second parameter should be a DOM node");
3788
+ }
3378
3789
 
3379
- applyBindingsToNodeAndDescendantsInternal(getBindingContext(viewModelOrBindingContext), rootNode, true);
3790
+ applyBindingsToNodeAndDescendantsInternal(getBindingContext(viewModelOrBindingContext, extendContextCallback), rootNode);
3380
3791
  };
3381
3792
 
3382
3793
  // Retrieving binding context from arbitrary nodes
3383
3794
  ko.contextFor = function(node) {
3384
3795
  // We can only do something meaningful for elements and comment nodes (in particular, not text nodes, as IE can't store domdata for them)
3385
- switch (node.nodeType) {
3386
- case 1:
3387
- case 8:
3388
- var context = ko.storedBindingContextForNode(node);
3389
- if (context) return context;
3390
- if (node.parentNode) return ko.contextFor(node.parentNode);
3391
- break;
3796
+ if (node && (node.nodeType === 1 || node.nodeType === 8)) {
3797
+ return ko.storedBindingContextForNode(node);
3392
3798
  }
3393
3799
  return undefined;
3394
3800
  };
@@ -3398,6 +3804,9 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
3398
3804
  };
3399
3805
 
3400
3806
  ko.exportSymbol('bindingHandlers', ko.bindingHandlers);
3807
+ ko.exportSymbol('bindingEvent', ko.bindingEvent);
3808
+ ko.exportSymbol('bindingEvent.subscribe', ko.bindingEvent.subscribe);
3809
+ ko.exportSymbol('bindingEvent.startPossiblyAsyncContentBinding', ko.bindingEvent.startPossiblyAsyncContentBinding);
3401
3810
  ko.exportSymbol('applyBindings', ko.applyBindings);
3402
3811
  ko.exportSymbol('applyBindingsToDescendants', ko.applyBindingsToDescendants);
3403
3812
  ko.exportSymbol('applyBindingAccessorsToNode', ko.applyBindingAccessorsToNode);
@@ -3437,7 +3846,7 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
3437
3846
  };
3438
3847
 
3439
3848
  function getObjectOwnProperty(obj, propName) {
3440
- return obj.hasOwnProperty(propName) ? obj[propName] : undefined;
3849
+ return Object.prototype.hasOwnProperty.call(obj, propName) ? obj[propName] : undefined;
3441
3850
  }
3442
3851
 
3443
3852
  function loadComponentAndNotify(componentName, callback) {
@@ -3574,7 +3983,7 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
3574
3983
  };
3575
3984
 
3576
3985
  ko.components.isRegistered = function(componentName) {
3577
- return defaultConfigRegistry.hasOwnProperty(componentName);
3986
+ return Object.prototype.hasOwnProperty.call(defaultConfigRegistry, componentName);
3578
3987
  };
3579
3988
 
3580
3989
  ko.components.unregister = function(componentName) {
@@ -3584,7 +3993,7 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
3584
3993
 
3585
3994
  ko.components.defaultLoader = {
3586
3995
  'getConfig': function(componentName, callback) {
3587
- var result = defaultConfigRegistry.hasOwnProperty(componentName)
3996
+ var result = ko.components.isRegistered(componentName)
3588
3997
  ? defaultConfigRegistry[componentName]
3589
3998
  : null;
3590
3999
  callback(result);
@@ -3847,7 +4256,7 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
3847
4256
  // Give access to the raw computeds, as long as that wouldn't overwrite any custom param also called '$raw'
3848
4257
  // This is in case the developer wants to react to outer (binding) observability separately from inner
3849
4258
  // (model value) observability, or in case the model value observable has subobservables.
3850
- if (!result.hasOwnProperty('$raw')) {
4259
+ if (!Object.prototype.hasOwnProperty.call(result, '$raw')) {
3851
4260
  result['$raw'] = rawParamComputedValues;
3852
4261
  }
3853
4262
 
@@ -3879,7 +4288,7 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
3879
4288
  var newDocFrag = originalFunction(),
3880
4289
  allComponents = ko.components._allRegisteredComponents;
3881
4290
  for (var componentName in allComponents) {
3882
- if (allComponents.hasOwnProperty(componentName)) {
4291
+ if (Object.prototype.hasOwnProperty.call(allComponents, componentName)) {
3883
4292
  newDocFrag.createElement(componentName);
3884
4293
  }
3885
4294
  }
@@ -3888,24 +4297,29 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
3888
4297
  })(document.createDocumentFragment);
3889
4298
  }
3890
4299
  })();(function(undefined) {
3891
-
3892
4300
  var componentLoadingOperationUniqueId = 0;
3893
4301
 
3894
4302
  ko.bindingHandlers['component'] = {
3895
4303
  'init': function(element, valueAccessor, ignored1, ignored2, bindingContext) {
3896
4304
  var currentViewModel,
3897
4305
  currentLoadingOperationId,
4306
+ afterRenderSub,
3898
4307
  disposeAssociatedComponentViewModel = function () {
3899
4308
  var currentViewModelDispose = currentViewModel && currentViewModel['dispose'];
3900
4309
  if (typeof currentViewModelDispose === 'function') {
3901
4310
  currentViewModelDispose.call(currentViewModel);
3902
4311
  }
4312
+ if (afterRenderSub) {
4313
+ afterRenderSub.dispose();
4314
+ }
4315
+ afterRenderSub = null;
3903
4316
  currentViewModel = null;
3904
4317
  // Any in-flight loading operation is no longer relevant, so make sure we ignore its completion
3905
4318
  currentLoadingOperationId = null;
3906
4319
  },
3907
4320
  originalChildNodes = ko.utils.makeArray(ko.virtualElements.childNodes(element));
3908
4321
 
4322
+ ko.virtualElements.emptyNode(element);
3909
4323
  ko.utils.domNodeDisposal.addDisposeCallback(element, disposeAssociatedComponentViewModel);
3910
4324
 
3911
4325
  ko.computed(function () {
@@ -3923,6 +4337,8 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
3923
4337
  throw new Error('No component name specified');
3924
4338
  }
3925
4339
 
4340
+ var asyncContext = ko.bindingEvent.startPossiblyAsyncContentBinding(element, bindingContext);
4341
+
3926
4342
  var loadingOperationId = currentLoadingOperationId = ++componentLoadingOperationUniqueId;
3927
4343
  ko.components.get(componentName, function(componentDefinition) {
3928
4344
  // If this is not the current load operation for this element, ignore it.
@@ -3938,11 +4354,24 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
3938
4354
  throw new Error('Unknown component \'' + componentName + '\'');
3939
4355
  }
3940
4356
  cloneTemplateIntoElement(componentName, componentDefinition, element);
3941
- var componentViewModel = createViewModel(componentDefinition, element, originalChildNodes, componentParams),
3942
- childBindingContext = bindingContext['createChildContext'](componentViewModel, /* dataItemAlias */ undefined, function(ctx) {
3943
- ctx['$component'] = componentViewModel;
3944
- ctx['$componentTemplateNodes'] = originalChildNodes;
4357
+
4358
+ var componentInfo = {
4359
+ 'element': element,
4360
+ 'templateNodes': originalChildNodes
4361
+ };
4362
+
4363
+ var componentViewModel = createViewModel(componentDefinition, componentParams, componentInfo),
4364
+ childBindingContext = asyncContext['createChildContext'](componentViewModel, {
4365
+ 'extend': function(ctx) {
4366
+ ctx['$component'] = componentViewModel;
4367
+ ctx['$componentTemplateNodes'] = originalChildNodes;
4368
+ }
3945
4369
  });
4370
+
4371
+ if (componentViewModel && componentViewModel['koDescendantsComplete']) {
4372
+ afterRenderSub = ko.bindingEvent.subscribe(element, ko.bindingEvent.descendantsComplete, componentViewModel['koDescendantsComplete'], componentViewModel);
4373
+ }
4374
+
3946
4375
  currentViewModel = componentViewModel;
3947
4376
  ko.applyBindingsToDescendants(childBindingContext, element);
3948
4377
  });
@@ -3964,40 +4393,47 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
3964
4393
  ko.virtualElements.setDomNodeChildren(element, clonedNodesArray);
3965
4394
  }
3966
4395
 
3967
- function createViewModel(componentDefinition, element, originalChildNodes, componentParams) {
4396
+ function createViewModel(componentDefinition, componentParams, componentInfo) {
3968
4397
  var componentViewModelFactory = componentDefinition['createViewModel'];
3969
4398
  return componentViewModelFactory
3970
- ? componentViewModelFactory.call(componentDefinition, componentParams, { 'element': element, 'templateNodes': originalChildNodes })
4399
+ ? componentViewModelFactory.call(componentDefinition, componentParams, componentInfo)
3971
4400
  : componentParams; // Template-only component
3972
4401
  }
3973
4402
 
3974
4403
  })();
3975
- var attrHtmlToJavascriptMap = { 'class': 'className', 'for': 'htmlFor' };
4404
+ var attrHtmlToJavaScriptMap = { 'class': 'className', 'for': 'htmlFor' };
3976
4405
  ko.bindingHandlers['attr'] = {
3977
4406
  'update': function(element, valueAccessor, allBindings) {
3978
4407
  var value = ko.utils.unwrapObservable(valueAccessor()) || {};
3979
4408
  ko.utils.objectForEach(value, function(attrName, attrValue) {
3980
4409
  attrValue = ko.utils.unwrapObservable(attrValue);
3981
4410
 
4411
+ // Find the namespace of this attribute, if any.
4412
+ var prefixLen = attrName.indexOf(':');
4413
+ var namespace = "lookupNamespaceURI" in element && prefixLen > 0 && element.lookupNamespaceURI(attrName.substr(0, prefixLen));
4414
+
3982
4415
  // To cover cases like "attr: { checked:someProp }", we want to remove the attribute entirely
3983
4416
  // when someProp is a "no value"-like value (strictly null, false, or undefined)
3984
4417
  // (because the absence of the "checked" attr is how to mark an element as not checked, etc.)
3985
4418
  var toRemove = (attrValue === false) || (attrValue === null) || (attrValue === undefined);
3986
- if (toRemove)
3987
- element.removeAttribute(attrName);
4419
+ if (toRemove) {
4420
+ namespace ? element.removeAttributeNS(namespace, attrName) : element.removeAttribute(attrName);
4421
+ } else {
4422
+ attrValue = attrValue.toString();
4423
+ }
3988
4424
 
3989
- // In IE <= 7 and IE8 Quirks Mode, you have to use the Javascript property name instead of the
4425
+ // In IE <= 7 and IE8 Quirks Mode, you have to use the JavaScript property name instead of the
3990
4426
  // HTML attribute name for certain attributes. IE8 Standards Mode supports the correct behavior,
3991
- // but instead of figuring out the mode, we'll just set the attribute through the Javascript
4427
+ // but instead of figuring out the mode, we'll just set the attribute through the JavaScript
3992
4428
  // property for IE <= 8.
3993
- if (ko.utils.ieVersion <= 8 && attrName in attrHtmlToJavascriptMap) {
3994
- attrName = attrHtmlToJavascriptMap[attrName];
4429
+ if (ko.utils.ieVersion <= 8 && attrName in attrHtmlToJavaScriptMap) {
4430
+ attrName = attrHtmlToJavaScriptMap[attrName];
3995
4431
  if (toRemove)
3996
4432
  element.removeAttribute(attrName);
3997
4433
  else
3998
4434
  element[attrName] = attrValue;
3999
4435
  } else if (!toRemove) {
4000
- element.setAttribute(attrName, attrValue.toString());
4436
+ namespace ? element.setAttributeNS(namespace, attrName, attrValue) : element.setAttribute(attrName, attrValue);
4001
4437
  }
4002
4438
 
4003
4439
  // Treat "name" specially - although you can think of it as an attribute, it also needs
@@ -4005,7 +4441,7 @@ ko.bindingHandlers['attr'] = {
4005
4441
  // Deliberately being case-sensitive here because XHTML would regard "Name" as a different thing
4006
4442
  // entirely, and there's no strong reason to allow for such casing in HTML.
4007
4443
  if (attrName === "name") {
4008
- ko.utils.setElementName(element, toRemove ? "" : attrValue.toString());
4444
+ ko.utils.setElementName(element, toRemove ? "" : attrValue);
4009
4445
  }
4010
4446
  });
4011
4447
  }
@@ -4019,18 +4455,20 @@ ko.bindingHandlers['checked'] = {
4019
4455
  // Treat "value" like "checkedValue" when it is included with "checked" binding
4020
4456
  if (allBindings['has']('checkedValue')) {
4021
4457
  return ko.utils.unwrapObservable(allBindings.get('checkedValue'));
4022
- } else if (allBindings['has']('value')) {
4023
- return ko.utils.unwrapObservable(allBindings.get('value'));
4458
+ } else if (useElementValue) {
4459
+ if (allBindings['has']('value')) {
4460
+ return ko.utils.unwrapObservable(allBindings.get('value'));
4461
+ } else {
4462
+ return element.value;
4463
+ }
4024
4464
  }
4025
-
4026
- return element.value;
4027
4465
  });
4028
4466
 
4029
4467
  function updateModel() {
4030
4468
  // This updates the model value from the view value.
4031
4469
  // It runs in response to DOM events (click) and changes in checkedValue.
4032
4470
  var isChecked = element.checked,
4033
- elemValue = useCheckedValue ? checkedValue() : isChecked;
4471
+ elemValue = checkedValue();
4034
4472
 
4035
4473
  // When we're first setting up this computed, don't change any model state.
4036
4474
  if (ko.computedContext.isInitial()) {
@@ -4038,33 +4476,43 @@ ko.bindingHandlers['checked'] = {
4038
4476
  }
4039
4477
 
4040
4478
  // We can ignore unchecked radio buttons, because some other radio
4041
- // button will be getting checked, and that one can take care of updating state.
4042
- if (isRadio && !isChecked) {
4479
+ // button will be checked, and that one can take care of updating state.
4480
+ // Also ignore value changes to an already unchecked checkbox.
4481
+ if (!isChecked && (isRadio || ko.computedContext.getDependenciesCount())) {
4043
4482
  return;
4044
4483
  }
4045
4484
 
4046
4485
  var modelValue = ko.dependencyDetection.ignore(valueAccessor);
4047
4486
  if (valueIsArray) {
4048
- var writableValue = rawValueIsNonArrayObservable ? modelValue.peek() : modelValue;
4049
- if (oldElemValue !== elemValue) {
4487
+ var writableValue = rawValueIsNonArrayObservable ? modelValue.peek() : modelValue,
4488
+ saveOldValue = oldElemValue;
4489
+ oldElemValue = elemValue;
4490
+
4491
+ if (saveOldValue !== elemValue) {
4050
4492
  // When we're responding to the checkedValue changing, and the element is
4051
4493
  // currently checked, replace the old elem value with the new elem value
4052
4494
  // in the model array.
4053
4495
  if (isChecked) {
4054
4496
  ko.utils.addOrRemoveItem(writableValue, elemValue, true);
4055
- ko.utils.addOrRemoveItem(writableValue, oldElemValue, false);
4497
+ ko.utils.addOrRemoveItem(writableValue, saveOldValue, false);
4056
4498
  }
4057
-
4058
- oldElemValue = elemValue;
4059
4499
  } else {
4060
4500
  // When we're responding to the user having checked/unchecked a checkbox,
4061
4501
  // add/remove the element value to the model array.
4062
4502
  ko.utils.addOrRemoveItem(writableValue, elemValue, isChecked);
4063
4503
  }
4504
+
4064
4505
  if (rawValueIsNonArrayObservable && ko.isWriteableObservable(modelValue)) {
4065
4506
  modelValue(writableValue);
4066
4507
  }
4067
4508
  } else {
4509
+ if (isCheckbox) {
4510
+ if (elemValue === undefined) {
4511
+ elemValue = isChecked;
4512
+ } else if (!isChecked) {
4513
+ elemValue = undefined;
4514
+ }
4515
+ }
4068
4516
  ko.expressionRewriting.writeValueToProperty(modelValue, allBindings, 'checked', elemValue, true);
4069
4517
  }
4070
4518
  };
@@ -4072,16 +4520,19 @@ ko.bindingHandlers['checked'] = {
4072
4520
  function updateView() {
4073
4521
  // This updates the view value from the model value.
4074
4522
  // It runs in response to changes in the bound (checked) value.
4075
- var modelValue = ko.utils.unwrapObservable(valueAccessor());
4523
+ var modelValue = ko.utils.unwrapObservable(valueAccessor()),
4524
+ elemValue = checkedValue();
4076
4525
 
4077
4526
  if (valueIsArray) {
4078
4527
  // When a checkbox is bound to an array, being checked represents its value being present in that array
4079
- element.checked = ko.utils.arrayIndexOf(modelValue, checkedValue()) >= 0;
4080
- } else if (isCheckbox) {
4081
- // When a checkbox is bound to any other value (not an array), being checked represents the value being trueish
4082
- element.checked = modelValue;
4528
+ element.checked = ko.utils.arrayIndexOf(modelValue, elemValue) >= 0;
4529
+ oldElemValue = elemValue;
4530
+ } else if (isCheckbox && elemValue === undefined) {
4531
+ // When a checkbox is bound to any other value (not an array) and "checkedValue" is not defined,
4532
+ // being checked represents the value being trueish
4533
+ element.checked = !!modelValue;
4083
4534
  } else {
4084
- // For radio buttons, being checked means that the radio button's value corresponds to the model value
4535
+ // Otherwise, being checked means that the checkbox or radio button's value corresponds to the model value
4085
4536
  element.checked = (checkedValue() === modelValue);
4086
4537
  }
4087
4538
  };
@@ -4097,8 +4548,8 @@ ko.bindingHandlers['checked'] = {
4097
4548
  var rawValue = valueAccessor(),
4098
4549
  valueIsArray = isCheckbox && (ko.utils.unwrapObservable(rawValue) instanceof Array),
4099
4550
  rawValueIsNonArrayObservable = !(valueIsArray && rawValue.push && rawValue.splice),
4100
- oldElemValue = valueIsArray ? checkedValue() : undefined,
4101
- useCheckedValue = isRadio || valueIsArray;
4551
+ useElementValue = isRadio || valueIsArray,
4552
+ oldElemValue = valueIsArray ? checkedValue() : undefined;
4102
4553
 
4103
4554
  // IE 6 won't allow radio buttons to be selected unless they have a name
4104
4555
  if (isRadio && !element.name)
@@ -4125,6 +4576,15 @@ ko.bindingHandlers['checkedValue'] = {
4125
4576
  };
4126
4577
 
4127
4578
  })();var classesWrittenByBindingKey = '__ko__cssValue';
4579
+ ko.bindingHandlers['class'] = {
4580
+ 'update': function (element, valueAccessor) {
4581
+ var value = ko.utils.stringTrim(ko.utils.unwrapObservable(valueAccessor()));
4582
+ ko.utils.toggleDomNodeCssClass(element, element[classesWrittenByBindingKey], false);
4583
+ element[classesWrittenByBindingKey] = value;
4584
+ ko.utils.toggleDomNodeCssClass(element, value, true);
4585
+ }
4586
+ };
4587
+
4128
4588
  ko.bindingHandlers['css'] = {
4129
4589
  'update': function (element, valueAccessor) {
4130
4590
  var value = ko.utils.unwrapObservable(valueAccessor());
@@ -4134,10 +4594,7 @@ ko.bindingHandlers['css'] = {
4134
4594
  ko.utils.toggleDomNodeCssClass(element, className, shouldHaveClass);
4135
4595
  });
4136
4596
  } else {
4137
- value = ko.utils.stringTrim(String(value || '')); // Make sure we don't try to store or set a non-string value
4138
- ko.utils.toggleDomNodeCssClass(element, element[classesWrittenByBindingKey], false);
4139
- element[classesWrittenByBindingKey] = value;
4140
- ko.utils.toggleDomNodeCssClass(element, value, true);
4597
+ ko.bindingHandlers['class']['update'](element, valueAccessor);
4141
4598
  }
4142
4599
  }
4143
4600
  };
@@ -4227,6 +4684,7 @@ ko.bindingHandlers['foreach'] = {
4227
4684
  return {
4228
4685
  'foreach': unwrappedValue['data'],
4229
4686
  'as': unwrappedValue['as'],
4687
+ 'noChildContext': unwrappedValue['noChildContext'],
4230
4688
  'includeDestroyed': unwrappedValue['includeDestroyed'],
4231
4689
  'afterAdd': unwrappedValue['afterAdd'],
4232
4690
  'beforeRemove': unwrappedValue['beforeRemove'],
@@ -4283,6 +4741,9 @@ ko.bindingHandlers['hasfocus'] = {
4283
4741
  ko.utils.registerEventHandler(element, "focusin", handleElementFocusIn); // For IE
4284
4742
  ko.utils.registerEventHandler(element, "blur", handleElementFocusOut);
4285
4743
  ko.utils.registerEventHandler(element, "focusout", handleElementFocusOut); // For IE
4744
+
4745
+ // Assume element is not focused (prevents "blur" being called initially)
4746
+ element[hasfocusLastValue] = false;
4286
4747
  },
4287
4748
  'update': function(element, valueAccessor) {
4288
4749
  var value = !!ko.utils.unwrapObservable(valueAccessor());
@@ -4305,7 +4766,7 @@ ko.bindingHandlers['hasfocus'] = {
4305
4766
  ko.expressionRewriting.twoWayBindings['hasfocus'] = true;
4306
4767
 
4307
4768
  ko.bindingHandlers['hasFocus'] = ko.bindingHandlers['hasfocus']; // Make "hasFocus" an alias
4308
- ko.expressionRewriting.twoWayBindings['hasFocus'] = true;
4769
+ ko.expressionRewriting.twoWayBindings['hasFocus'] = 'hasfocus';
4309
4770
  ko.bindingHandlers['html'] = {
4310
4771
  'init': function() {
4311
4772
  // Prevent binding on the dynamically-injected HTML (as developers are unlikely to expect that, and it has security implications)
@@ -4316,36 +4777,74 @@ ko.bindingHandlers['html'] = {
4316
4777
  ko.utils.setHtml(element, valueAccessor());
4317
4778
  }
4318
4779
  };
4780
+ (function () {
4781
+
4319
4782
  // Makes a binding like with or if
4320
- function makeWithIfBinding(bindingKey, isWith, isNot, makeContextCallback) {
4783
+ function makeWithIfBinding(bindingKey, isWith, isNot) {
4321
4784
  ko.bindingHandlers[bindingKey] = {
4322
4785
  'init': function(element, valueAccessor, allBindings, viewModel, bindingContext) {
4323
- var didDisplayOnLastUpdate,
4324
- savedNodes;
4786
+ var didDisplayOnLastUpdate, savedNodes, contextOptions = {}, completeOnRender, needAsyncContext, renderOnEveryChange;
4787
+
4788
+ if (isWith) {
4789
+ var as = allBindings.get('as'), noChildContext = allBindings.get('noChildContext');
4790
+ renderOnEveryChange = !(as && noChildContext);
4791
+ contextOptions = { 'as': as, 'noChildContext': noChildContext, 'exportDependencies': renderOnEveryChange };
4792
+ }
4793
+
4794
+ completeOnRender = allBindings.get("completeOn") == "render";
4795
+ needAsyncContext = completeOnRender || allBindings['has'](ko.bindingEvent.descendantsComplete);
4796
+
4325
4797
  ko.computed(function() {
4326
- var dataValue = ko.utils.unwrapObservable(valueAccessor()),
4327
- shouldDisplay = !isNot !== !dataValue, // equivalent to isNot ? !dataValue : !!dataValue
4328
- isFirstRender = !savedNodes,
4329
- needsRefresh = isFirstRender || isWith || (shouldDisplay !== didDisplayOnLastUpdate);
4330
-
4331
- if (needsRefresh) {
4332
- // Save a copy of the inner nodes on the initial update, but only if we have dependencies.
4333
- if (isFirstRender && ko.computedContext.getDependenciesCount()) {
4334
- savedNodes = ko.utils.cloneNodes(ko.virtualElements.childNodes(element), true /* shouldCleanNodes */);
4798
+ var value = ko.utils.unwrapObservable(valueAccessor()),
4799
+ shouldDisplay = !isNot !== !value, // equivalent to isNot ? !value : !!value,
4800
+ isInitial = !savedNodes,
4801
+ childContext;
4802
+
4803
+ if (!renderOnEveryChange && shouldDisplay === didDisplayOnLastUpdate) {
4804
+ return;
4805
+ }
4806
+
4807
+ if (needAsyncContext) {
4808
+ bindingContext = ko.bindingEvent.startPossiblyAsyncContentBinding(element, bindingContext);
4809
+ }
4810
+
4811
+ if (shouldDisplay) {
4812
+ if (!isWith || renderOnEveryChange) {
4813
+ contextOptions['dataDependency'] = ko.computedContext.computed();
4335
4814
  }
4336
4815
 
4337
- if (shouldDisplay) {
4338
- if (!isFirstRender) {
4339
- ko.virtualElements.setDomNodeChildren(element, ko.utils.cloneNodes(savedNodes));
4340
- }
4341
- ko.applyBindingsToDescendants(makeContextCallback ? makeContextCallback(bindingContext, dataValue) : bindingContext, element);
4816
+ if (isWith) {
4817
+ childContext = bindingContext['createChildContext'](typeof value == "function" ? value : valueAccessor, contextOptions);
4818
+ } else if (ko.computedContext.getDependenciesCount()) {
4819
+ childContext = bindingContext['extend'](null, contextOptions);
4342
4820
  } else {
4343
- ko.virtualElements.emptyNode(element);
4821
+ childContext = bindingContext;
4344
4822
  }
4823
+ }
4824
+
4825
+ // Save a copy of the inner nodes on the initial update, but only if we have dependencies.
4826
+ if (isInitial && ko.computedContext.getDependenciesCount()) {
4827
+ savedNodes = ko.utils.cloneNodes(ko.virtualElements.childNodes(element), true /* shouldCleanNodes */);
4828
+ }
4829
+
4830
+ if (shouldDisplay) {
4831
+ if (!isInitial) {
4832
+ ko.virtualElements.setDomNodeChildren(element, ko.utils.cloneNodes(savedNodes));
4833
+ }
4834
+
4835
+ ko.applyBindingsToDescendants(childContext, element);
4836
+ } else {
4837
+ ko.virtualElements.emptyNode(element);
4345
4838
 
4346
- didDisplayOnLastUpdate = shouldDisplay;
4839
+ if (!completeOnRender) {
4840
+ ko.bindingEvent.notify(element, ko.bindingEvent.childrenComplete);
4841
+ }
4347
4842
  }
4843
+
4844
+ didDisplayOnLastUpdate = shouldDisplay;
4845
+
4348
4846
  }, null, { disposeWhenNodeIsRemoved: element });
4847
+
4349
4848
  return { 'controlsDescendantBindings': true };
4350
4849
  }
4351
4850
  };
@@ -4356,11 +4855,18 @@ function makeWithIfBinding(bindingKey, isWith, isNot, makeContextCallback) {
4356
4855
  // Construct the actual binding handlers
4357
4856
  makeWithIfBinding('if');
4358
4857
  makeWithIfBinding('ifnot', false /* isWith */, true /* isNot */);
4359
- makeWithIfBinding('with', true /* isWith */, false /* isNot */,
4360
- function(bindingContext, dataValue) {
4361
- return bindingContext['createChildContext'](dataValue);
4858
+ makeWithIfBinding('with', true /* isWith */);
4859
+
4860
+ })();ko.bindingHandlers['let'] = {
4861
+ 'init': function(element, valueAccessor, allBindings, viewModel, bindingContext) {
4862
+ // Make a modified binding context, with extra properties, and apply it to descendant elements
4863
+ var innerContext = bindingContext['extend'](valueAccessor);
4864
+ ko.applyBindingsToDescendants(innerContext, element);
4865
+
4866
+ return { 'controlsDescendantBindings': true };
4362
4867
  }
4363
- );
4868
+ };
4869
+ ko.virtualElements.allowedBindings['let'] = true;
4364
4870
  var captionPlaceholder = {};
4365
4871
  ko.bindingHandlers['options'] = {
4366
4872
  'init': function(element) {
@@ -4570,7 +5076,23 @@ ko.bindingHandlers['style'] = {
4570
5076
  styleValue = "";
4571
5077
  }
4572
5078
 
4573
- element.style[styleName] = styleValue;
5079
+ if (jQueryInstance) {
5080
+ jQueryInstance(element)['css'](styleName, styleValue);
5081
+ } else if (/^--/.test(styleName)) {
5082
+ // Is styleName a custom CSS property?
5083
+ element.style.setProperty(styleName, styleValue);
5084
+ } else {
5085
+ styleName = styleName.replace(/-(\w)/g, function (all, letter) {
5086
+ return letter.toUpperCase();
5087
+ });
5088
+
5089
+ var previousStyle = element.style[styleName];
5090
+ element.style[styleName] = styleValue;
5091
+
5092
+ if (styleValue !== previousStyle && element.style[styleName] == previousStyle && !isNaN(styleValue)) {
5093
+ element.style[styleName] = styleValue + "px";
5094
+ }
5095
+ }
4574
5096
  });
4575
5097
  }
4576
5098
  };
@@ -4614,10 +5136,16 @@ if (window && window.navigator) {
4614
5136
  };
4615
5137
 
4616
5138
  // Detect various browser versions because some old versions don't fully support the 'input' event
4617
- var operaVersion = window.opera && window.opera.version && parseInt(window.opera.version()),
4618
- userAgent = window.navigator.userAgent,
4619
- safariVersion = parseVersion(userAgent.match(/^(?:(?!chrome).)*version\/([^ ]*) safari/i)),
4620
- firefoxVersion = parseVersion(userAgent.match(/Firefox\/([^ ]*)/));
5139
+ var userAgent = window.navigator.userAgent,
5140
+ operaVersion, chromeVersion, safariVersion, firefoxVersion, ieVersion, edgeVersion;
5141
+
5142
+ (operaVersion = window.opera && window.opera.version && parseInt(window.opera.version()))
5143
+ || (edgeVersion = parseVersion(userAgent.match(/Edge\/([^ ]+)$/)))
5144
+ || (chromeVersion = parseVersion(userAgent.match(/Chrome\/([^ ]+)/)))
5145
+ || (safariVersion = parseVersion(userAgent.match(/Version\/([^ ]+) Safari/)))
5146
+ || (firefoxVersion = parseVersion(userAgent.match(/Firefox\/([^ ]+)/)))
5147
+ || (ieVersion = ko.utils.ieVersion || parseVersion(userAgent.match(/MSIE ([^ ]+)/))) // Detects up to IE 10
5148
+ || (ieVersion = parseVersion(userAgent.match(/rv:([^ )]+)/))); // Detects IE 11
4621
5149
  }
4622
5150
 
4623
5151
  // IE 8 and 9 have bugs that prevent the normal events from firing when the value changes.
@@ -4626,7 +5154,7 @@ if (window && window.navigator) {
4626
5154
  // fired at the document level only and doesn't directly indicate which element changed. We
4627
5155
  // set up just one event handler for the document and use 'activeElement' to determine which
4628
5156
  // element was changed.
4629
- if (ko.utils.ieVersion < 10) {
5157
+ if (ieVersion >= 8 && ieVersion < 10) {
4630
5158
  var selectionChangeRegisteredName = ko.utils.domData.nextKey(),
4631
5159
  selectionChangeHandlerName = ko.utils.domData.nextKey();
4632
5160
  var selectionChangeHandler = function(event) {
@@ -4680,7 +5208,8 @@ ko.bindingHandlers['textInput'] = {
4680
5208
 
4681
5209
  // IE9 will mess up the DOM if you handle events synchronously which results in DOM changes (such as other bindings);
4682
5210
  // so we'll make sure all updates are asynchronous
4683
- var ieUpdateModel = ko.utils.ieVersion == 9 ? deferUpdateModel : updateModel;
5211
+ var ieUpdateModel = ko.utils.ieVersion == 9 ? deferUpdateModel : updateModel,
5212
+ ourUpdate = false;
4684
5213
 
4685
5214
  var updateView = function () {
4686
5215
  var modelValue = ko.utils.unwrapObservable(valueAccessor());
@@ -4697,8 +5226,10 @@ ko.bindingHandlers['textInput'] = {
4697
5226
  // Update the element only if the element and model are different. On some browsers, updating the value
4698
5227
  // will move the cursor to the end of the input, which would be bad while the user is typing.
4699
5228
  if (element.value !== modelValue) {
4700
- previousElementValue = modelValue; // Make sure we ignore events (propertychange) that result from updating the value
5229
+ ourUpdate = true; // Make sure we ignore events (propertychange) that result from updating the value
4701
5230
  element.value = modelValue;
5231
+ ourUpdate = false;
5232
+ previousElementValue = element.value; // In case the browser changes the value (see #2281)
4702
5233
  }
4703
5234
  };
4704
5235
 
@@ -4716,62 +5247,74 @@ ko.bindingHandlers['textInput'] = {
4716
5247
  }
4717
5248
  });
4718
5249
  } else {
4719
- if (ko.utils.ieVersion < 10) {
5250
+ if (ieVersion) {
5251
+ // All versions (including 11) of Internet Explorer have a bug that they don't generate an input or propertychange event when ESC is pressed
5252
+ onEvent('keypress', updateModel);
5253
+ }
5254
+ if (ieVersion < 11) {
4720
5255
  // Internet Explorer <= 8 doesn't support the 'input' event, but does include 'propertychange' that fires whenever
4721
5256
  // any property of an element changes. Unlike 'input', it also fires if a property is changed from JavaScript code,
4722
- // but that's an acceptable compromise for this binding. IE 9 does support 'input', but since it doesn't fire it
4723
- // when using autocomplete, we'll use 'propertychange' for it also.
5257
+ // but that's an acceptable compromise for this binding. IE 9 and 10 support 'input', but since they don't always
5258
+ // fire it when using autocomplete, we'll use 'propertychange' for them also.
4724
5259
  onEvent('propertychange', function(event) {
4725
- if (event.propertyName === 'value') {
5260
+ if (!ourUpdate && event.propertyName === 'value') {
4726
5261
  ieUpdateModel(event);
4727
5262
  }
4728
5263
  });
5264
+ }
5265
+ if (ieVersion == 8) {
5266
+ // IE 8 has a bug where it fails to fire 'propertychange' on the first update following a value change from
5267
+ // JavaScript code. It also doesn't fire if you clear the entire value. To fix this, we bind to the following
5268
+ // events too.
5269
+ onEvent('keyup', updateModel); // A single keystoke
5270
+ onEvent('keydown', updateModel); // The first character when a key is held down
5271
+ }
5272
+ if (registerForSelectionChangeEvent) {
5273
+ // Internet Explorer 9 doesn't fire the 'input' event when deleting text, including using
5274
+ // the backspace, delete, or ctrl-x keys, clicking the 'x' to clear the input, dragging text
5275
+ // out of the field, and cutting or deleting text using the context menu. 'selectionchange'
5276
+ // can detect all of those except dragging text out of the field, for which we use 'dragend'.
5277
+ // These are also needed in IE8 because of the bug described above.
5278
+ registerForSelectionChangeEvent(element, ieUpdateModel); // 'selectionchange' covers cut, paste, drop, delete, etc.
5279
+ onEvent('dragend', deferUpdateModel);
5280
+ }
4729
5281
 
4730
- if (ko.utils.ieVersion == 8) {
4731
- // IE 8 has a bug where it fails to fire 'propertychange' on the first update following a value change from
4732
- // JavaScript code. It also doesn't fire if you clear the entire value. To fix this, we bind to the following
4733
- // events too.
4734
- onEvent('keyup', updateModel); // A single keystoke
4735
- onEvent('keydown', updateModel); // The first character when a key is held down
4736
- }
4737
- if (ko.utils.ieVersion >= 8) {
4738
- // Internet Explorer 9 doesn't fire the 'input' event when deleting text, including using
4739
- // the backspace, delete, or ctrl-x keys, clicking the 'x' to clear the input, dragging text
4740
- // out of the field, and cutting or deleting text using the context menu. 'selectionchange'
4741
- // can detect all of those except dragging text out of the field, for which we use 'dragend'.
4742
- // These are also needed in IE8 because of the bug described above.
4743
- registerForSelectionChangeEvent(element, ieUpdateModel); // 'selectionchange' covers cut, paste, drop, delete, etc.
4744
- onEvent('dragend', deferUpdateModel);
4745
- }
4746
- } else {
5282
+ if (!ieVersion || ieVersion >= 9) {
4747
5283
  // All other supported browsers support the 'input' event, which fires whenever the content of the element is changed
4748
5284
  // through the user interface.
4749
- onEvent('input', updateModel);
4750
-
4751
- if (safariVersion < 5 && ko.utils.tagNameLower(element) === "textarea") {
4752
- // Safari <5 doesn't fire the 'input' event for <textarea> elements (it does fire 'textInput'
4753
- // but only when typing). So we'll just catch as much as we can with keydown, cut, and paste.
4754
- onEvent('keydown', deferUpdateModel);
4755
- onEvent('paste', deferUpdateModel);
4756
- onEvent('cut', deferUpdateModel);
4757
- } else if (operaVersion < 11) {
4758
- // Opera 10 doesn't always fire the 'input' event for cut, paste, undo & drop operations.
4759
- // We can try to catch some of those using 'keydown'.
4760
- onEvent('keydown', deferUpdateModel);
4761
- } else if (firefoxVersion < 4.0) {
4762
- // Firefox <= 3.6 doesn't fire the 'input' event when text is filled in through autocomplete
4763
- onEvent('DOMAutoComplete', updateModel);
4764
-
4765
- // Firefox <=3.5 doesn't fire the 'input' event when text is dropped into the input.
4766
- onEvent('dragdrop', updateModel); // <3.5
4767
- onEvent('drop', updateModel); // 3.5
4768
- }
5285
+ onEvent('input', ieUpdateModel);
5286
+ }
5287
+
5288
+ if (safariVersion < 5 && ko.utils.tagNameLower(element) === "textarea") {
5289
+ // Safari <5 doesn't fire the 'input' event for <textarea> elements (it does fire 'textInput'
5290
+ // but only when typing). So we'll just catch as much as we can with keydown, cut, and paste.
5291
+ onEvent('keydown', deferUpdateModel);
5292
+ onEvent('paste', deferUpdateModel);
5293
+ onEvent('cut', deferUpdateModel);
5294
+ } else if (operaVersion < 11) {
5295
+ // Opera 10 doesn't always fire the 'input' event for cut, paste, undo & drop operations.
5296
+ // We can try to catch some of those using 'keydown'.
5297
+ onEvent('keydown', deferUpdateModel);
5298
+ } else if (firefoxVersion < 4.0) {
5299
+ // Firefox <= 3.6 doesn't fire the 'input' event when text is filled in through autocomplete
5300
+ onEvent('DOMAutoComplete', updateModel);
5301
+
5302
+ // Firefox <=3.5 doesn't fire the 'input' event when text is dropped into the input.
5303
+ onEvent('dragdrop', updateModel); // <3.5
5304
+ onEvent('drop', updateModel); // 3.5
5305
+ } else if (edgeVersion && element.type === "number") {
5306
+ // Microsoft Edge doesn't fire 'input' or 'change' events for number inputs when
5307
+ // the value is changed via the up / down arrow keys
5308
+ onEvent('keydown', deferUpdateModel);
4769
5309
  }
4770
5310
  }
4771
5311
 
4772
5312
  // Bind to the change event so that we can catch programmatic updates of the value that fire this event.
4773
5313
  onEvent('change', updateModel);
4774
5314
 
5315
+ // To deal with browsers that don't notify any kind of event for some changes (IE, Safari, etc.)
5316
+ onEvent('blur', updateModel);
5317
+
4775
5318
  ko.computed(updateView, null, { disposeWhenNodeIsRemoved: element });
4776
5319
  }
4777
5320
  };
@@ -4794,11 +5337,29 @@ ko.bindingHandlers['textinput'] = {
4794
5337
  }
4795
5338
  };
4796
5339
  ko.bindingHandlers['uniqueName'].currentIndex = 0;
5340
+ ko.bindingHandlers['using'] = {
5341
+ 'init': function(element, valueAccessor, allBindings, viewModel, bindingContext) {
5342
+ var options;
5343
+
5344
+ if (allBindings['has']('as')) {
5345
+ options = { 'as': allBindings.get('as'), 'noChildContext': allBindings.get('noChildContext') };
5346
+ }
5347
+
5348
+ var innerContext = bindingContext['createChildContext'](valueAccessor, options);
5349
+ ko.applyBindingsToDescendants(innerContext, element);
5350
+
5351
+ return { 'controlsDescendantBindings': true };
5352
+ }
5353
+ };
5354
+ ko.virtualElements.allowedBindings['using'] = true;
4797
5355
  ko.bindingHandlers['value'] = {
4798
5356
  'after': ['options', 'foreach'],
4799
5357
  'init': function (element, valueAccessor, allBindings) {
5358
+ var tagName = ko.utils.tagNameLower(element),
5359
+ isInputElement = tagName == "input";
5360
+
4800
5361
  // If the value binding is placed on a radio/checkbox, then just pass through to checkedValue and quit
4801
- if (element.tagName.toLowerCase() == "input" && (element.type == "checkbox" || element.type == "radio")) {
5362
+ if (isInputElement && (element.type == "checkbox" || element.type == "radio")) {
4802
5363
  ko.applyBindingAccessorsToNode(element, { 'checkedValue': valueAccessor });
4803
5364
  return;
4804
5365
  }
@@ -4826,7 +5387,7 @@ ko.bindingHandlers['value'] = {
4826
5387
 
4827
5388
  // Workaround for https://github.com/SteveSanderson/knockout/issues/122
4828
5389
  // IE doesn't fire "change" events on textboxes if the user selects a value from its autocomplete list
4829
- var ieAutoCompleteHackNeeded = ko.utils.ieVersion && element.tagName.toLowerCase() == "input" && element.type == "text"
5390
+ var ieAutoCompleteHackNeeded = ko.utils.ieVersion && isInputElement && element.type == "text"
4830
5391
  && element.autocomplete != "off" && (!element.form || element.form.autocomplete != "off");
4831
5392
  if (ieAutoCompleteHackNeeded && ko.utils.arrayIndexOf(eventsToCatch, "propertychange") == -1) {
4832
5393
  ko.utils.registerEventHandler(element, "propertychange", function () { propertyChangedFired = true });
@@ -4860,40 +5421,45 @@ ko.bindingHandlers['value'] = {
4860
5421
  ko.utils.registerEventHandler(element, eventName, handler);
4861
5422
  });
4862
5423
 
4863
- var updateFromModel = function () {
4864
- var newValue = ko.utils.unwrapObservable(valueAccessor());
4865
- var elementValue = ko.selectExtensions.readValue(element);
5424
+ var updateFromModel;
4866
5425
 
4867
- if (elementValueBeforeEvent !== null && newValue === elementValueBeforeEvent) {
4868
- ko.utils.setTimeout(updateFromModel, 0);
4869
- return;
5426
+ if (isInputElement && element.type == "file") {
5427
+ // For file input elements, can only write the empty string
5428
+ updateFromModel = function () {
5429
+ var newValue = ko.utils.unwrapObservable(valueAccessor());
5430
+ if (newValue === null || newValue === undefined || newValue === "") {
5431
+ element.value = "";
5432
+ } else {
5433
+ ko.dependencyDetection.ignore(valueUpdateHandler); // reset the model to match the element
5434
+ }
4870
5435
  }
5436
+ } else {
5437
+ updateFromModel = function () {
5438
+ var newValue = ko.utils.unwrapObservable(valueAccessor());
5439
+ var elementValue = ko.selectExtensions.readValue(element);
4871
5440
 
4872
- var valueHasChanged = (newValue !== elementValue);
5441
+ if (elementValueBeforeEvent !== null && newValue === elementValueBeforeEvent) {
5442
+ ko.utils.setTimeout(updateFromModel, 0);
5443
+ return;
5444
+ }
4873
5445
 
4874
- if (valueHasChanged) {
4875
- if (ko.utils.tagNameLower(element) === "select") {
4876
- var allowUnset = allBindings.get('valueAllowUnset');
4877
- var applyValueAction = function () {
4878
- ko.selectExtensions.writeValue(element, newValue, allowUnset);
4879
- };
4880
- applyValueAction();
5446
+ var valueHasChanged = newValue !== elementValue;
4881
5447
 
4882
- if (!allowUnset && newValue !== ko.selectExtensions.readValue(element)) {
4883
- // If you try to set a model value that can't be represented in an already-populated dropdown, reject that change,
4884
- // because you're not allowed to have a model value that disagrees with a visible UI selection.
4885
- ko.dependencyDetection.ignore(ko.utils.triggerEvent, null, [element, "change"]);
5448
+ if (valueHasChanged || elementValue === undefined) {
5449
+ if (tagName === "select") {
5450
+ var allowUnset = allBindings.get('valueAllowUnset');
5451
+ ko.selectExtensions.writeValue(element, newValue, allowUnset);
5452
+ if (!allowUnset && newValue !== ko.selectExtensions.readValue(element)) {
5453
+ // If you try to set a model value that can't be represented in an already-populated dropdown, reject that change,
5454
+ // because you're not allowed to have a model value that disagrees with a visible UI selection.
5455
+ ko.dependencyDetection.ignore(valueUpdateHandler);
5456
+ }
4886
5457
  } else {
4887
- // Workaround for IE6 bug: It won't reliably apply values to SELECT nodes during the same execution thread
4888
- // right after you've changed the set of OPTION nodes on it. So for that node type, we'll schedule a second thread
4889
- // to apply the value as well.
4890
- ko.utils.setTimeout(applyValueAction, 0);
5458
+ ko.selectExtensions.writeValue(element, newValue);
4891
5459
  }
4892
- } else {
4893
- ko.selectExtensions.writeValue(element, newValue);
4894
5460
  }
4895
- }
4896
- };
5461
+ };
5462
+ }
4897
5463
 
4898
5464
  ko.computed(updateFromModel, null, { disposeWhenNodeIsRemoved: element });
4899
5465
  },
@@ -4910,6 +5476,12 @@ ko.bindingHandlers['visible'] = {
4910
5476
  element.style.display = "none";
4911
5477
  }
4912
5478
  };
5479
+
5480
+ ko.bindingHandlers['hidden'] = {
5481
+ 'update': function (element, valueAccessor) {
5482
+ ko.bindingHandlers['visible']['update'](element, function() { return !ko.utils.unwrapObservable(valueAccessor()) });
5483
+ }
5484
+ };
4913
5485
  // 'click' is just a shorthand for the usual full-length event:{click:handler}
4914
5486
  makeEventHandlerShortcut('click');
4915
5487
  // If you want to make a custom template engine,
@@ -4993,7 +5565,7 @@ ko.templateRewriting = (function () {
4993
5565
  var allValidators = ko.expressionRewriting.bindingRewriteValidators;
4994
5566
  for (var i = 0; i < keyValueArray.length; i++) {
4995
5567
  var key = keyValueArray[i]['key'];
4996
- if (allValidators.hasOwnProperty(key)) {
5568
+ if (Object.prototype.hasOwnProperty.call(allValidators, key)) {
4997
5569
  var validator = allValidators[key];
4998
5570
 
4999
5571
  if (typeof validator === "function") {
@@ -5136,11 +5708,22 @@ ko.exportSymbol('__tr_ambtns', ko.templateRewriting.applyMemoizedBindingsToNextS
5136
5708
  var element = this.domElement;
5137
5709
  if (arguments.length == 0) {
5138
5710
  var templateData = getTemplateDomData(element),
5139
- containerData = templateData.containerData;
5140
- return containerData || (
5141
- this.templateType === templateTemplate ? element.content :
5142
- this.templateType === templateElement ? element :
5143
- undefined);
5711
+ nodes = templateData.containerData || (
5712
+ this.templateType === templateTemplate ? element.content :
5713
+ this.templateType === templateElement ? element :
5714
+ undefined);
5715
+ if (!nodes || templateData.alwaysCheckText) {
5716
+ // If the template is associated with an element that stores the template as text,
5717
+ // parse and cache the nodes whenever there's new text content available. This allows
5718
+ // the user to update the template content by updating the text of template node.
5719
+ var text = this['text']();
5720
+ if (text) {
5721
+ nodes = ko.utils.parseHtmlForTemplateNodes(text, element.ownerDocument);
5722
+ this['text'](""); // clear the text from the node
5723
+ setTemplateDomData(element, {containerData: nodes, alwaysCheckText: true});
5724
+ }
5725
+ }
5726
+ return nodes;
5144
5727
  } else {
5145
5728
  var valueToWrite = arguments[0];
5146
5729
  setTemplateDomData(element, {containerData: valueToWrite});
@@ -5281,8 +5864,12 @@ ko.exportSymbol('__tr_ambtns', ko.templateRewriting.applyMemoizedBindingsToNextS
5281
5864
 
5282
5865
  if (haveAddedNodesToParent) {
5283
5866
  activateBindingsOnContinuousNodeArray(renderedNodesArray, bindingContext);
5284
- if (options['afterRender'])
5285
- ko.dependencyDetection.ignore(options['afterRender'], null, [renderedNodesArray, bindingContext['$data']]);
5867
+ if (options['afterRender']) {
5868
+ ko.dependencyDetection.ignore(options['afterRender'], null, [renderedNodesArray, bindingContext[options['as'] || '$data']]);
5869
+ }
5870
+ if (renderMode == "replaceChildren") {
5871
+ ko.bindingEvent.notify(targetNodeOrNodeArray, ko.bindingEvent.childrenComplete);
5872
+ }
5286
5873
  }
5287
5874
 
5288
5875
  return renderedNodesArray;
@@ -5319,7 +5906,7 @@ ko.exportSymbol('__tr_ambtns', ko.templateRewriting.applyMemoizedBindingsToNextS
5319
5906
  // Ensure we've got a proper binding context to work with
5320
5907
  var bindingContext = (dataOrBindingContext && (dataOrBindingContext instanceof ko.bindingContext))
5321
5908
  ? dataOrBindingContext
5322
- : new ko.bindingContext(ko.utils.unwrapObservable(dataOrBindingContext));
5909
+ : new ko.bindingContext(dataOrBindingContext, null, null, null, { "exportDependencies": true });
5323
5910
 
5324
5911
  var templateName = resolveTemplateName(template, bindingContext['$data'], bindingContext),
5325
5912
  renderedNodesArray = executeTemplate(targetNodeOrNodeArray, renderMode, templateName, bindingContext, options);
@@ -5343,18 +5930,25 @@ ko.exportSymbol('__tr_ambtns', ko.templateRewriting.applyMemoizedBindingsToNextS
5343
5930
  ko.renderTemplateForEach = function (template, arrayOrObservableArray, options, targetNode, parentBindingContext) {
5344
5931
  // Since setDomNodeChildrenFromArrayMapping always calls executeTemplateForArrayItem and then
5345
5932
  // activateBindingsCallback for added items, we can store the binding context in the former to use in the latter.
5346
- var arrayItemContext;
5933
+ var arrayItemContext, asName = options['as'];
5347
5934
 
5348
5935
  // This will be called by setDomNodeChildrenFromArrayMapping to get the nodes to add to targetNode
5349
5936
  var executeTemplateForArrayItem = function (arrayValue, index) {
5350
5937
  // Support selecting template as a function of the data being rendered
5351
- arrayItemContext = parentBindingContext['createChildContext'](arrayValue, options['as'], function(context) {
5352
- context['$index'] = index;
5938
+ arrayItemContext = parentBindingContext['createChildContext'](arrayValue, {
5939
+ 'as': asName,
5940
+ 'noChildContext': options['noChildContext'],
5941
+ 'extend': function(context) {
5942
+ context['$index'] = index;
5943
+ if (asName) {
5944
+ context[asName + "Index"] = index;
5945
+ }
5946
+ }
5353
5947
  });
5354
5948
 
5355
5949
  var templateName = resolveTemplateName(template, arrayValue, arrayItemContext);
5356
- return executeTemplate(null, "ignoreTargetNode", templateName, arrayItemContext, options);
5357
- }
5950
+ return executeTemplate(targetNode, "ignoreTargetNode", templateName, arrayItemContext, options);
5951
+ };
5358
5952
 
5359
5953
  // This will be called whenever setDomNodeChildrenFromArrayMapping has added nodes to targetNode
5360
5954
  var activateBindingsCallback = function(arrayValue, addedNodesArray, index) {
@@ -5367,21 +5961,40 @@ ko.exportSymbol('__tr_ambtns', ko.templateRewriting.applyMemoizedBindingsToNextS
5367
5961
  arrayItemContext = null;
5368
5962
  };
5369
5963
 
5370
- return ko.dependentObservable(function () {
5371
- var unwrappedArray = ko.utils.unwrapObservable(arrayOrObservableArray) || [];
5372
- if (typeof unwrappedArray.length == "undefined") // Coerce single value into array
5373
- unwrappedArray = [unwrappedArray];
5374
-
5375
- // Filter out any entries marked as destroyed
5376
- var filteredArray = ko.utils.arrayFilter(unwrappedArray, function(item) {
5377
- return options['includeDestroyed'] || item === undefined || item === null || !ko.utils.unwrapObservable(item['_destroy']);
5378
- });
5379
-
5964
+ var setDomNodeChildrenFromArrayMapping = function (newArray, changeList) {
5380
5965
  // Call setDomNodeChildrenFromArrayMapping, ignoring any observables unwrapped within (most likely from a callback function).
5381
5966
  // If the array items are observables, though, they will be unwrapped in executeTemplateForArrayItem and managed within setDomNodeChildrenFromArrayMapping.
5382
- ko.dependencyDetection.ignore(ko.utils.setDomNodeChildrenFromArrayMapping, null, [targetNode, filteredArray, executeTemplateForArrayItem, options, activateBindingsCallback]);
5967
+ ko.dependencyDetection.ignore(ko.utils.setDomNodeChildrenFromArrayMapping, null, [targetNode, newArray, executeTemplateForArrayItem, options, activateBindingsCallback, changeList]);
5968
+ ko.bindingEvent.notify(targetNode, ko.bindingEvent.childrenComplete);
5969
+ };
5970
+
5971
+ var shouldHideDestroyed = (options['includeDestroyed'] === false) || (ko.options['foreachHidesDestroyed'] && !options['includeDestroyed']);
5383
5972
 
5384
- }, null, { disposeWhenNodeIsRemoved: targetNode });
5973
+ if (!shouldHideDestroyed && !options['beforeRemove'] && ko.isObservableArray(arrayOrObservableArray)) {
5974
+ setDomNodeChildrenFromArrayMapping(arrayOrObservableArray.peek());
5975
+
5976
+ var subscription = arrayOrObservableArray.subscribe(function (changeList) {
5977
+ setDomNodeChildrenFromArrayMapping(arrayOrObservableArray(), changeList);
5978
+ }, null, "arrayChange");
5979
+ subscription.disposeWhenNodeIsRemoved(targetNode);
5980
+
5981
+ return subscription;
5982
+ } else {
5983
+ return ko.dependentObservable(function () {
5984
+ var unwrappedArray = ko.utils.unwrapObservable(arrayOrObservableArray) || [];
5985
+ if (typeof unwrappedArray.length == "undefined") // Coerce single value into array
5986
+ unwrappedArray = [unwrappedArray];
5987
+
5988
+ if (shouldHideDestroyed) {
5989
+ // Filter out any entries marked as destroyed
5990
+ unwrappedArray = ko.utils.arrayFilter(unwrappedArray, function(item) {
5991
+ return item === undefined || item === null || !ko.utils.unwrapObservable(item['_destroy']);
5992
+ });
5993
+ }
5994
+ setDomNodeChildrenFromArrayMapping(unwrappedArray);
5995
+
5996
+ }, null, { disposeWhenNodeIsRemoved: targetNode });
5997
+ }
5385
5998
  };
5386
5999
 
5387
6000
  var templateComputedDomDataKey = ko.utils.domData.nextKey();
@@ -5389,9 +6002,10 @@ ko.exportSymbol('__tr_ambtns', ko.templateRewriting.applyMemoizedBindingsToNextS
5389
6002
  var oldComputed = ko.utils.domData.get(element, templateComputedDomDataKey);
5390
6003
  if (oldComputed && (typeof(oldComputed.dispose) == 'function'))
5391
6004
  oldComputed.dispose();
5392
- ko.utils.domData.set(element, templateComputedDomDataKey, (newComputed && newComputed.isActive()) ? newComputed : undefined);
6005
+ ko.utils.domData.set(element, templateComputedDomDataKey, (newComputed && (!newComputed.isActive || newComputed.isActive())) ? newComputed : undefined);
5393
6006
  }
5394
6007
 
6008
+ var cleanContainerDomDataKey = ko.utils.domData.nextKey();
5395
6009
  ko.bindingHandlers['template'] = {
5396
6010
  'init': function(element, valueAccessor) {
5397
6011
  // Support anonymous templates
@@ -5408,19 +6022,30 @@ ko.exportSymbol('__tr_ambtns', ko.templateRewriting.applyMemoizedBindingsToNextS
5408
6022
  if (ko.isObservable(nodes)) {
5409
6023
  throw new Error('The "nodes" option must be a plain, non-observable array.');
5410
6024
  }
5411
- var container = ko.utils.moveCleanedNodesToContainerElement(nodes); // This also removes the nodes from their current parent
6025
+
6026
+ // If the nodes are already attached to a KO-generated container, we reuse that container without moving the
6027
+ // elements to a new one (we check only the first node, as the nodes are always moved together)
6028
+ var container = nodes[0] && nodes[0].parentNode;
6029
+ if (!container || !ko.utils.domData.get(container, cleanContainerDomDataKey)) {
6030
+ container = ko.utils.moveCleanedNodesToContainerElement(nodes);
6031
+ ko.utils.domData.set(container, cleanContainerDomDataKey, true);
6032
+ }
6033
+
5412
6034
  new ko.templateSources.anonymousTemplate(element)['nodes'](container);
5413
6035
  } else {
5414
6036
  // It's an anonymous template - store the element contents, then clear the element
5415
- var templateNodes = ko.virtualElements.childNodes(element),
5416
- container = ko.utils.moveCleanedNodesToContainerElement(templateNodes); // This also removes the nodes from their current parent
5417
- new ko.templateSources.anonymousTemplate(element)['nodes'](container);
6037
+ var templateNodes = ko.virtualElements.childNodes(element);
6038
+ if (templateNodes.length > 0) {
6039
+ var container = ko.utils.moveCleanedNodesToContainerElement(templateNodes); // This also removes the nodes from their current parent
6040
+ new ko.templateSources.anonymousTemplate(element)['nodes'](container);
6041
+ } else {
6042
+ throw new Error("Anonymous template defined, but no template content was provided");
6043
+ }
5418
6044
  }
5419
6045
  return { 'controlsDescendantBindings': true };
5420
6046
  },
5421
6047
  'update': function (element, valueAccessor, allBindings, viewModel, bindingContext) {
5422
6048
  var value = valueAccessor(),
5423
- dataValue,
5424
6049
  options = ko.utils.unwrapObservable(value),
5425
6050
  shouldDisplay = true,
5426
6051
  templateComputed = null,
@@ -5437,8 +6062,6 @@ ko.exportSymbol('__tr_ambtns', ko.templateRewriting.applyMemoizedBindingsToNextS
5437
6062
  shouldDisplay = ko.utils.unwrapObservable(options['if']);
5438
6063
  if (shouldDisplay && 'ifnot' in options)
5439
6064
  shouldDisplay = !ko.utils.unwrapObservable(options['ifnot']);
5440
-
5441
- dataValue = ko.utils.unwrapObservable(options['data']);
5442
6065
  }
5443
6066
 
5444
6067
  if ('foreach' in options) {
@@ -5449,9 +6072,14 @@ ko.exportSymbol('__tr_ambtns', ko.templateRewriting.applyMemoizedBindingsToNextS
5449
6072
  ko.virtualElements.emptyNode(element);
5450
6073
  } else {
5451
6074
  // Render once for this single data point (or use the viewModel if no data was provided)
5452
- var innerBindingContext = ('data' in options) ?
5453
- bindingContext['createChildContext'](dataValue, options['as']) : // Given an explitit 'data' value, we create a child binding context for it
5454
- bindingContext; // Given no explicit 'data' value, we retain the same binding context
6075
+ var innerBindingContext = bindingContext;
6076
+ if ('data' in options) {
6077
+ innerBindingContext = bindingContext['createChildContext'](options['data'], {
6078
+ 'as': options['as'],
6079
+ 'noChildContext': options['noChildContext'],
6080
+ 'exportDependencies': true
6081
+ });
6082
+ }
5455
6083
  templateComputed = ko.renderTemplate(templateName || element, innerBindingContext, options, element);
5456
6084
  }
5457
6085
 
@@ -5614,98 +6242,126 @@ ko.exportSymbol('utils.compareArrays', ko.utils.compareArrays);
5614
6242
  var lastMappingResultDomDataKey = ko.utils.domData.nextKey(),
5615
6243
  deletedItemDummyValue = ko.utils.domData.nextKey();
5616
6244
 
5617
- ko.utils.setDomNodeChildrenFromArrayMapping = function (domNode, array, mapping, options, callbackAfterAddingNodes) {
5618
- // Compare the provided array against the previous one
6245
+ ko.utils.setDomNodeChildrenFromArrayMapping = function (domNode, array, mapping, options, callbackAfterAddingNodes, editScript) {
5619
6246
  array = array || [];
6247
+ if (typeof array.length == "undefined") // Coerce single value into array
6248
+ array = [array];
6249
+
5620
6250
  options = options || {};
5621
- var isFirstExecution = ko.utils.domData.get(domNode, lastMappingResultDomDataKey) === undefined;
5622
- var lastMappingResult = ko.utils.domData.get(domNode, lastMappingResultDomDataKey) || [];
5623
- var lastArray = ko.utils.arrayMap(lastMappingResult, function (x) { return x.arrayEntry; });
5624
- var editScript = ko.utils.compareArrays(lastArray, array, options['dontLimitMoves']);
6251
+ var lastMappingResult = ko.utils.domData.get(domNode, lastMappingResultDomDataKey);
6252
+ var isFirstExecution = !lastMappingResult;
5625
6253
 
5626
6254
  // Build the new mapping result
5627
6255
  var newMappingResult = [];
5628
6256
  var lastMappingResultIndex = 0;
5629
- var newMappingResultIndex = 0;
6257
+ var currentArrayIndex = 0;
5630
6258
 
5631
6259
  var nodesToDelete = [];
5632
- var itemsToProcess = [];
6260
+ var itemsToMoveFirstIndexes = [];
5633
6261
  var itemsForBeforeRemoveCallbacks = [];
5634
6262
  var itemsForMoveCallbacks = [];
5635
6263
  var itemsForAfterAddCallbacks = [];
5636
6264
  var mapData;
6265
+ var countWaitingForRemove = 0;
6266
+
6267
+ function itemAdded(value) {
6268
+ mapData = { arrayEntry: value, indexObservable: ko.observable(currentArrayIndex++) };
6269
+ newMappingResult.push(mapData);
6270
+ if (!isFirstExecution) {
6271
+ itemsForAfterAddCallbacks.push(mapData);
6272
+ }
6273
+ }
5637
6274
 
5638
- function itemMovedOrRetained(editScriptIndex, oldPosition) {
6275
+ function itemMovedOrRetained(oldPosition) {
5639
6276
  mapData = lastMappingResult[oldPosition];
5640
- if (newMappingResultIndex !== oldPosition)
5641
- itemsForMoveCallbacks[editScriptIndex] = mapData;
6277
+ if (currentArrayIndex !== mapData.indexObservable.peek())
6278
+ itemsForMoveCallbacks.push(mapData);
5642
6279
  // Since updating the index might change the nodes, do so before calling fixUpContinuousNodeArray
5643
- mapData.indexObservable(newMappingResultIndex++);
6280
+ mapData.indexObservable(currentArrayIndex++);
5644
6281
  ko.utils.fixUpContinuousNodeArray(mapData.mappedNodes, domNode);
5645
6282
  newMappingResult.push(mapData);
5646
- itemsToProcess.push(mapData);
5647
6283
  }
5648
6284
 
5649
6285
  function callCallback(callback, items) {
5650
6286
  if (callback) {
5651
6287
  for (var i = 0, n = items.length; i < n; i++) {
5652
- if (items[i]) {
5653
- ko.utils.arrayForEach(items[i].mappedNodes, function(node) {
5654
- callback(node, i, items[i].arrayEntry);
5655
- });
5656
- }
6288
+ ko.utils.arrayForEach(items[i].mappedNodes, function(node) {
6289
+ callback(node, i, items[i].arrayEntry);
6290
+ });
5657
6291
  }
5658
6292
  }
5659
6293
  }
5660
6294
 
5661
- for (var i = 0, editScriptItem, movedIndex; editScriptItem = editScript[i]; i++) {
5662
- movedIndex = editScriptItem['moved'];
5663
- switch (editScriptItem['status']) {
5664
- case "deleted":
5665
- if (movedIndex === undefined) {
5666
- mapData = lastMappingResult[lastMappingResultIndex];
6295
+ if (isFirstExecution) {
6296
+ ko.utils.arrayForEach(array, itemAdded);
6297
+ } else {
6298
+ if (!editScript || (lastMappingResult && lastMappingResult['_countWaitingForRemove'])) {
6299
+ // Compare the provided array against the previous one
6300
+ var lastArray = ko.utils.arrayMap(lastMappingResult, function (x) { return x.arrayEntry; }),
6301
+ compareOptions = {
6302
+ 'dontLimitMoves': options['dontLimitMoves'],
6303
+ 'sparse': true
6304
+ };
6305
+ editScript = ko.utils.compareArrays(lastArray, array, compareOptions);
6306
+ }
5667
6307
 
5668
- // Stop tracking changes to the mapping for these nodes
5669
- if (mapData.dependentObservable) {
5670
- mapData.dependentObservable.dispose();
5671
- mapData.dependentObservable = undefined;
6308
+ for (var i = 0, editScriptItem, movedIndex, itemIndex; editScriptItem = editScript[i]; i++) {
6309
+ movedIndex = editScriptItem['moved'];
6310
+ itemIndex = editScriptItem['index'];
6311
+ switch (editScriptItem['status']) {
6312
+ case "deleted":
6313
+ while (lastMappingResultIndex < itemIndex) {
6314
+ itemMovedOrRetained(lastMappingResultIndex++);
5672
6315
  }
6316
+ if (movedIndex === undefined) {
6317
+ mapData = lastMappingResult[lastMappingResultIndex];
5673
6318
 
5674
- // Queue these nodes for later removal
5675
- if (ko.utils.fixUpContinuousNodeArray(mapData.mappedNodes, domNode).length) {
5676
- if (options['beforeRemove']) {
5677
- newMappingResult.push(mapData);
5678
- itemsToProcess.push(mapData);
5679
- if (mapData.arrayEntry === deletedItemDummyValue) {
5680
- mapData = null;
5681
- } else {
5682
- itemsForBeforeRemoveCallbacks[i] = mapData;
5683
- }
6319
+ // Stop tracking changes to the mapping for these nodes
6320
+ if (mapData.dependentObservable) {
6321
+ mapData.dependentObservable.dispose();
6322
+ mapData.dependentObservable = undefined;
5684
6323
  }
5685
- if (mapData) {
5686
- nodesToDelete.push.apply(nodesToDelete, mapData.mappedNodes);
6324
+
6325
+ // Queue these nodes for later removal
6326
+ if (ko.utils.fixUpContinuousNodeArray(mapData.mappedNodes, domNode).length) {
6327
+ if (options['beforeRemove']) {
6328
+ newMappingResult.push(mapData);
6329
+ countWaitingForRemove++;
6330
+ if (mapData.arrayEntry === deletedItemDummyValue) {
6331
+ mapData = null;
6332
+ } else {
6333
+ itemsForBeforeRemoveCallbacks.push(mapData);
6334
+ }
6335
+ }
6336
+ if (mapData) {
6337
+ nodesToDelete.push.apply(nodesToDelete, mapData.mappedNodes);
6338
+ }
5687
6339
  }
5688
6340
  }
5689
- }
5690
- lastMappingResultIndex++;
5691
- break;
6341
+ lastMappingResultIndex++;
6342
+ break;
5692
6343
 
5693
- case "retained":
5694
- itemMovedOrRetained(i, lastMappingResultIndex++);
5695
- break;
6344
+ case "added":
6345
+ while (currentArrayIndex < itemIndex) {
6346
+ itemMovedOrRetained(lastMappingResultIndex++);
6347
+ }
6348
+ if (movedIndex !== undefined) {
6349
+ itemsToMoveFirstIndexes.push(newMappingResult.length);
6350
+ itemMovedOrRetained(movedIndex);
6351
+ } else {
6352
+ itemAdded(editScriptItem['value']);
6353
+ }
6354
+ break;
6355
+ }
6356
+ }
5696
6357
 
5697
- case "added":
5698
- if (movedIndex !== undefined) {
5699
- itemMovedOrRetained(i, movedIndex);
5700
- } else {
5701
- mapData = { arrayEntry: editScriptItem['value'], indexObservable: ko.observable(newMappingResultIndex++) };
5702
- newMappingResult.push(mapData);
5703
- itemsToProcess.push(mapData);
5704
- if (!isFirstExecution)
5705
- itemsForAfterAddCallbacks[i] = mapData;
5706
- }
5707
- break;
6358
+ while (currentArrayIndex < array.length) {
6359
+ itemMovedOrRetained(lastMappingResultIndex++);
5708
6360
  }
6361
+
6362
+ // Record that the current view may still contain deleted items
6363
+ // because it means we won't be able to use a provided editScript.
6364
+ newMappingResult['_countWaitingForRemove'] = countWaitingForRemove;
5709
6365
  }
5710
6366
 
5711
6367
  // Store a copy of the array items we just considered so we can difference it next time
@@ -5717,25 +6373,57 @@ ko.exportSymbol('utils.compareArrays', ko.utils.compareArrays);
5717
6373
  // Next remove nodes for deleted items (or just clean if there's a beforeRemove callback)
5718
6374
  ko.utils.arrayForEach(nodesToDelete, options['beforeRemove'] ? ko.cleanNode : ko.removeNode);
5719
6375
 
6376
+ var i, j, nextNodeInDom, lastNode, nodeToInsert, mappedNodes, activeElement;
6377
+
6378
+ // Since most browsers remove the focus from an element when it's moved to another location,
6379
+ // save the focused element and try to restore it later.
6380
+ try {
6381
+ activeElement = domNode.ownerDocument.activeElement;
6382
+ } catch(e) {
6383
+ // IE9 throws if you access activeElement during page load (see issue #703)
6384
+ }
6385
+
6386
+ // Try to reduce overall moved nodes by first moving the ones that were marked as moved by the edit script
6387
+ if (itemsToMoveFirstIndexes.length) {
6388
+ while ((i = itemsToMoveFirstIndexes.shift()) != undefined) {
6389
+ mapData = newMappingResult[i];
6390
+ for (lastNode = undefined; i; ) {
6391
+ if ((mappedNodes = newMappingResult[--i].mappedNodes) && mappedNodes.length) {
6392
+ lastNode = mappedNodes[mappedNodes.length-1];
6393
+ break;
6394
+ }
6395
+ }
6396
+ for (j = 0; nodeToInsert = mapData.mappedNodes[j]; lastNode = nodeToInsert, j++) {
6397
+ ko.virtualElements.insertAfter(domNode, nodeToInsert, lastNode);
6398
+ }
6399
+ }
6400
+ }
6401
+
5720
6402
  // Next add/reorder the remaining items (will include deleted items if there's a beforeRemove callback)
5721
- for (var i = 0, nextNode = ko.virtualElements.firstChild(domNode), lastNode, node; mapData = itemsToProcess[i]; i++) {
6403
+ for (i = 0, nextNodeInDom = ko.virtualElements.firstChild(domNode); mapData = newMappingResult[i]; i++) {
5722
6404
  // Get nodes for newly added items
5723
6405
  if (!mapData.mappedNodes)
5724
6406
  ko.utils.extend(mapData, mapNodeAndRefreshWhenChanged(domNode, mapping, mapData.arrayEntry, callbackAfterAddingNodes, mapData.indexObservable));
5725
6407
 
5726
6408
  // Put nodes in the right place if they aren't there already
5727
- for (var j = 0; node = mapData.mappedNodes[j]; nextNode = node.nextSibling, lastNode = node, j++) {
5728
- if (node !== nextNode)
5729
- ko.virtualElements.insertAfter(domNode, node, lastNode);
6409
+ for (j = 0; nodeToInsert = mapData.mappedNodes[j]; nextNodeInDom = nodeToInsert.nextSibling, lastNode = nodeToInsert, j++) {
6410
+ if (nodeToInsert !== nextNodeInDom)
6411
+ ko.virtualElements.insertAfter(domNode, nodeToInsert, lastNode);
5730
6412
  }
5731
6413
 
5732
6414
  // Run the callbacks for newly added nodes (for example, to apply bindings, etc.)
5733
6415
  if (!mapData.initialized && callbackAfterAddingNodes) {
5734
6416
  callbackAfterAddingNodes(mapData.arrayEntry, mapData.mappedNodes, mapData.indexObservable);
5735
6417
  mapData.initialized = true;
6418
+ lastNode = mapData.mappedNodes[mapData.mappedNodes.length - 1]; // get the last node again since it may have been changed by a preprocessor
5736
6419
  }
5737
6420
  }
5738
6421
 
6422
+ // Restore the focused element if it had lost focus
6423
+ if (activeElement && domNode.ownerDocument.activeElement != activeElement) {
6424
+ activeElement.focus();
6425
+ }
6426
+
5739
6427
  // If there's a beforeRemove callback, call it after reordering.
5740
6428
  // Note that we assume that the beforeRemove callback will usually be used to remove the nodes using
5741
6429
  // some sort of animation, which is why we first reorder the nodes that will be removed. If the
@@ -5747,9 +6435,7 @@ ko.exportSymbol('utils.compareArrays', ko.utils.compareArrays);
5747
6435
  // as already "removed" so we won't call beforeRemove for it again, and it ensures that the item won't match up
5748
6436
  // with an actual item in the array and appear as "retained" or "moved".
5749
6437
  for (i = 0; i < itemsForBeforeRemoveCallbacks.length; ++i) {
5750
- if (itemsForBeforeRemoveCallbacks[i]) {
5751
- itemsForBeforeRemoveCallbacks[i].arrayEntry = deletedItemDummyValue;
5752
- }
6438
+ itemsForBeforeRemoveCallbacks[i].arrayEntry = deletedItemDummyValue;
5753
6439
  }
5754
6440
 
5755
6441
  // Finally call afterMove and afterAdd callbacks