knockoutjs-rails 3.4.0.1 → 3.5.0

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