knockoutjs-rails 3.3.0.1 → 3.4.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
2
  SHA1:
3
- metadata.gz: 62bbd23f8af8cf326244c073f7f1c5b4e95961d0
4
- data.tar.gz: aa4c037d8be8807581b62a90a86e333bc46d9c78
3
+ metadata.gz: 89017fcfcfc1f72d9d72cf6c9f8274261cebc10c
4
+ data.tar.gz: 880e18223ec85fd7f468d975ca4132ce5d6eed0e
5
5
  SHA512:
6
- metadata.gz: d472ab63e4cabb0890c3eb2d8f8eeefd522ed9be27ad2d1330ac9f855e4b0490735406ce8385ca4d0e3698f8926f66db57741d7b8322ab332440b6e0cc9c66dc
7
- data.tar.gz: 2a6cb41037b7f472dff54c6c102b5fbce7d0964ed7a36037d9acea7f9f1a3d174144d8f1bfb9764fca58980a3e734195c3d89d6c2a7bfe01bf1a7b5d1dea837d
6
+ metadata.gz: e75993ebe967fe9a7464fc294ad0e8035f2132a79dca7510b50e37d4386acd28e0a11dec6867af00518f21951a66b762397f8ebe94db2502689a10ccc391415b
7
+ data.tar.gz: 64b06ff23c2a09e5ee820458b639389ebe2b61591cd43565582a3cc85837fb3ae565f816382fe32f03ad344518d171e1ddef4b84e70ca1079479a0776ba8a563
data/README.md CHANGED
@@ -25,7 +25,7 @@ application.js):
25
25
 
26
26
  ## Versioning
27
27
 
28
- knockoutjs-rails 2.3.0 == Knockout.js 2.3.0
28
+ knockoutjs-rails 3.4.0 == Knockout.js 3.4.0
29
29
 
30
30
  Every attempt is made to mirror the currently shipping Knockout.js version number wherever possible.
31
31
  The major and minor version numbers will always represent the Knockout.js version, but the patch level
@@ -1,5 +1,5 @@
1
1
  module Knockoutjs
2
2
  module Rails
3
- VERSION = "3.3.0.1"
3
+ VERSION = "3.4.0"
4
4
  end
5
5
  end
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Knockout JavaScript library v3.3.0
2
+ * Knockout JavaScript library v3.4.0
3
3
  * (c) Steven Sanderson - http://knockoutjs.com/
4
4
  * License: MIT (http://www.opensource.org/licenses/mit-license.php)
5
5
  */
@@ -19,7 +19,7 @@ var DEBUG=true;
19
19
  if (typeof define === 'function' && define['amd']) {
20
20
  // [1] AMD anonymous module
21
21
  define(['exports', 'require'], factory);
22
- } else if (typeof require === 'function' && typeof exports === 'object' && typeof module === 'object') {
22
+ } else if (typeof exports === 'object' && typeof module === 'object') {
23
23
  // [2] CommonJS/Node.js
24
24
  factory(module['exports'] || exports); // module.exports is for Node.js
25
25
  } else {
@@ -45,9 +45,16 @@ ko.exportSymbol = function(koPath, object) {
45
45
  ko.exportProperty = function(owner, publicName, object) {
46
46
  owner[publicName] = object;
47
47
  };
48
- ko.version = "3.3.0";
48
+ ko.version = "3.4.0";
49
49
 
50
50
  ko.exportSymbol('version', ko.version);
51
+ // For any options that may affect various areas of Knockout and aren't directly associated with data binding.
52
+ ko.options = {
53
+ 'deferUpdates': false,
54
+ 'useOnlyNativeEvents': false
55
+ };
56
+
57
+ //ko.exportSymbol('options', ko.options); // 'options' isn't minified
51
58
  ko.utils = (function () {
52
59
  function objectForEach(obj, action) {
53
60
  for (var prop in obj) {
@@ -74,6 +81,7 @@ ko.utils = (function () {
74
81
  }
75
82
 
76
83
  var canSetPrototype = ({ __proto__: [] } instanceof Array);
84
+ var canUseSymbols = !DEBUG && typeof Symbol === 'function';
77
85
 
78
86
  // Represent the known event types in a compact way, then at runtime transform it into a hash with event name as key (for fast lookup)
79
87
  var knownEvents = {}, knownEventTypesByEventName = {};
@@ -304,8 +312,11 @@ ko.utils = (function () {
304
312
  // Rules:
305
313
  // [A] Any leading nodes that have been removed should be ignored
306
314
  // These most likely correspond to memoization nodes that were already removed during binding
307
- // See https://github.com/SteveSanderson/knockout/pull/440
308
- // [B] We want to output a continuous series of nodes. So, ignore any nodes that have already been removed,
315
+ // See https://github.com/knockout/knockout/pull/440
316
+ // [B] Any trailing nodes that have been remove should be ignored
317
+ // This prevents the code here from adding unrelated nodes to the array while processing rule [C]
318
+ // See https://github.com/knockout/knockout/pull/1903
319
+ // [C] We want to output a continuous series of nodes. So, ignore any nodes that have already been removed,
309
320
  // and include any nodes that have been inserted among the previous collection
310
321
 
311
322
  if (continuousNodeArray.length) {
@@ -317,6 +328,10 @@ ko.utils = (function () {
317
328
  continuousNodeArray.splice(0, 1);
318
329
 
319
330
  // Rule [B]
331
+ while (continuousNodeArray.length > 1 && continuousNodeArray[continuousNodeArray.length - 1].parentNode !== parentNode)
332
+ continuousNodeArray.length--;
333
+
334
+ // Rule [C]
320
335
  if (continuousNodeArray.length > 1) {
321
336
  var current = continuousNodeArray[0], last = continuousNodeArray[continuousNodeArray.length - 1];
322
337
  // Replace with the actual new continuous node set
@@ -324,8 +339,6 @@ ko.utils = (function () {
324
339
  while (current !== last) {
325
340
  continuousNodeArray.push(current);
326
341
  current = current.nextSibling;
327
- if (!current) // Won't happen, except if the developer has manually removed some DOM elements (then we're in an undefined scenario)
328
- return;
329
342
  }
330
343
  continuousNodeArray.push(last);
331
344
  }
@@ -385,14 +398,38 @@ ko.utils = (function () {
385
398
  return element && element.tagName && element.tagName.toLowerCase();
386
399
  },
387
400
 
401
+ catchFunctionErrors: function (delegate) {
402
+ return ko['onError'] ? function () {
403
+ try {
404
+ return delegate.apply(this, arguments);
405
+ } catch (e) {
406
+ ko['onError'] && ko['onError'](e);
407
+ throw e;
408
+ }
409
+ } : delegate;
410
+ },
411
+
412
+ setTimeout: function (handler, timeout) {
413
+ return setTimeout(ko.utils.catchFunctionErrors(handler), timeout);
414
+ },
415
+
416
+ deferError: function (error) {
417
+ setTimeout(function () {
418
+ ko['onError'] && ko['onError'](error);
419
+ throw error;
420
+ }, 0);
421
+ },
422
+
388
423
  registerEventHandler: function (element, eventType, handler) {
424
+ var wrappedHandler = ko.utils.catchFunctionErrors(handler);
425
+
389
426
  var mustUseAttachEvent = ieVersion && eventsThatMustBeRegisteredUsingAttachEvent[eventType];
390
- if (!mustUseAttachEvent && jQueryInstance) {
391
- jQueryInstance(element)['bind'](eventType, handler);
427
+ if (!ko.options['useOnlyNativeEvents'] && !mustUseAttachEvent && jQueryInstance) {
428
+ jQueryInstance(element)['bind'](eventType, wrappedHandler);
392
429
  } else if (!mustUseAttachEvent && typeof element.addEventListener == "function")
393
- element.addEventListener(eventType, handler, false);
430
+ element.addEventListener(eventType, wrappedHandler, false);
394
431
  else if (typeof element.attachEvent != "undefined") {
395
- var attachEventHandler = function (event) { handler.call(element, event); },
432
+ var attachEventHandler = function (event) { wrappedHandler.call(element, event); },
396
433
  attachEventName = "on" + eventType;
397
434
  element.attachEvent(attachEventName, attachEventHandler);
398
435
 
@@ -415,7 +452,7 @@ ko.utils = (function () {
415
452
  // In both cases, we'll use the click method instead.
416
453
  var useClickWorkaround = isClickOnCheckableElement(element, eventType);
417
454
 
418
- if (jQueryInstance && !useClickWorkaround) {
455
+ if (!ko.options['useOnlyNativeEvents'] && jQueryInstance && !useClickWorkaround) {
419
456
  jQueryInstance(element)['trigger'](eventType);
420
457
  } else if (typeof document.createEvent == "function") {
421
458
  if (typeof element.dispatchEvent == "function") {
@@ -515,6 +552,10 @@ ko.utils = (function () {
515
552
  return result;
516
553
  },
517
554
 
555
+ createSymbolOrString: function(identifier) {
556
+ return canUseSymbols ? Symbol(identifier) : identifier;
557
+ },
558
+
518
559
  isIe6 : isIe6,
519
560
  isIe7 : isIe7,
520
561
  ieVersion : ieVersion,
@@ -793,7 +834,29 @@ ko.exportSymbol('utils.domNodeDisposal', ko.utils.domNodeDisposal);
793
834
  ko.exportSymbol('utils.domNodeDisposal.addDisposeCallback', ko.utils.domNodeDisposal.addDisposeCallback);
794
835
  ko.exportSymbol('utils.domNodeDisposal.removeDisposeCallback', ko.utils.domNodeDisposal.removeDisposeCallback);
795
836
  (function () {
796
- var leadingCommentRegex = /^(\s*)<!--(.*?)-->/;
837
+ var none = [0, "", ""],
838
+ table = [1, "<table>", "</table>"],
839
+ tbody = [2, "<table><tbody>", "</tbody></table>"],
840
+ tr = [3, "<table><tbody><tr>", "</tr></tbody></table>"],
841
+ select = [1, "<select multiple='multiple'>", "</select>"],
842
+ lookup = {
843
+ 'thead': table,
844
+ 'tbody': table,
845
+ 'tfoot': table,
846
+ 'tr': tbody,
847
+ 'td': tr,
848
+ 'th': tr,
849
+ 'option': select,
850
+ 'optgroup': select
851
+ },
852
+
853
+ // This is needed for old IE if you're *not* using either jQuery or innerShiv. Doesn't affect other cases.
854
+ mayRequireCreateElementHack = ko.utils.ieVersion <= 8;
855
+
856
+ function getWrap(tags) {
857
+ var m = tags.match(/^<([a-z]+)[ >]/);
858
+ return (m && lookup[m[1]]) || none;
859
+ }
797
860
 
798
861
  function simpleHtmlParse(html, documentContext) {
799
862
  documentContext || (documentContext = document);
@@ -808,25 +871,34 @@ ko.exportSymbol('utils.domNodeDisposal.removeDisposeCallback', ko.utils.domNodeD
808
871
  // (possibly a text node) in front of the comment. So, KO does not attempt to workaround this IE issue automatically at present.
809
872
 
810
873
  // Trim whitespace, otherwise indexOf won't work as expected
811
- var tags = ko.utils.stringTrim(html).toLowerCase(), div = documentContext.createElement("div");
812
-
813
- // Finds the first match from the left column, and returns the corresponding "wrap" data from the right column
814
- var wrap = tags.match(/^<(thead|tbody|tfoot)/) && [1, "<table>", "</table>"] ||
815
- !tags.indexOf("<tr") && [2, "<table><tbody>", "</tbody></table>"] ||
816
- (!tags.indexOf("<td") || !tags.indexOf("<th")) && [3, "<table><tbody><tr>", "</tr></tbody></table>"] ||
817
- /* anything else */ [0, "", ""];
874
+ var tags = ko.utils.stringTrim(html).toLowerCase(), div = documentContext.createElement("div"),
875
+ wrap = getWrap(tags),
876
+ depth = wrap[0];
818
877
 
819
878
  // Go to html and back, then peel off extra wrappers
820
879
  // Note that we always prefix with some dummy text, because otherwise, IE<9 will strip out leading comment nodes in descendants. Total madness.
821
880
  var markup = "ignored<div>" + wrap[1] + html + wrap[2] + "</div>";
822
881
  if (typeof windowContext['innerShiv'] == "function") {
882
+ // Note that innerShiv is deprecated in favour of html5shiv. We should consider adding
883
+ // support for html5shiv (except if no explicit support is needed, e.g., if html5shiv
884
+ // somehow shims the native APIs so it just works anyway)
823
885
  div.appendChild(windowContext['innerShiv'](markup));
824
886
  } else {
887
+ if (mayRequireCreateElementHack) {
888
+ // The document.createElement('my-element') trick to enable custom elements in IE6-8
889
+ // only works if we assign innerHTML on an element associated with that document.
890
+ documentContext.appendChild(div);
891
+ }
892
+
825
893
  div.innerHTML = markup;
894
+
895
+ if (mayRequireCreateElementHack) {
896
+ div.parentNode.removeChild(div);
897
+ }
826
898
  }
827
899
 
828
900
  // Move to the right depth
829
- while (wrap[0]--)
901
+ while (depth--)
830
902
  div = div.lastChild;
831
903
 
832
904
  return ko.utils.makeArray(div.lastChild.childNodes);
@@ -858,8 +930,9 @@ ko.exportSymbol('utils.domNodeDisposal.removeDisposeCallback', ko.utils.domNodeD
858
930
  }
859
931
 
860
932
  ko.utils.parseHtmlFragment = function(html, documentContext) {
861
- return jQueryInstance ? jQueryHtmlParse(html, documentContext) // As below, benefit from jQuery's optimisations where possible
862
- : simpleHtmlParse(html, documentContext); // ... otherwise, this simple logic will do in most common cases.
933
+ return jQueryInstance ?
934
+ jQueryHtmlParse(html, documentContext) : // As below, benefit from jQuery's optimisations where possible
935
+ simpleHtmlParse(html, documentContext); // ... otherwise, this simple logic will do in most common cases.
863
936
  };
864
937
 
865
938
  ko.utils.setHtml = function(node, html) {
@@ -959,6 +1032,114 @@ ko.exportSymbol('memoization.memoize', ko.memoization.memoize);
959
1032
  ko.exportSymbol('memoization.unmemoize', ko.memoization.unmemoize);
960
1033
  ko.exportSymbol('memoization.parseMemoText', ko.memoization.parseMemoText);
961
1034
  ko.exportSymbol('memoization.unmemoizeDomNodeAndDescendants', ko.memoization.unmemoizeDomNodeAndDescendants);
1035
+ ko.tasks = (function () {
1036
+ var scheduler,
1037
+ taskQueue = [],
1038
+ taskQueueLength = 0,
1039
+ nextHandle = 1,
1040
+ nextIndexToProcess = 0;
1041
+
1042
+ if (window['MutationObserver']) {
1043
+ // Chrome 27+, Firefox 14+, IE 11+, Opera 15+, Safari 6.1+
1044
+ // From https://github.com/petkaantonov/bluebird * Copyright (c) 2014 Petka Antonov * License: MIT
1045
+ scheduler = (function (callback) {
1046
+ var div = document.createElement("div");
1047
+ new MutationObserver(callback).observe(div, {attributes: true});
1048
+ return function () { div.classList.toggle("foo"); };
1049
+ })(scheduledProcess);
1050
+ } else if (document && "onreadystatechange" in document.createElement("script")) {
1051
+ // IE 6-10
1052
+ // From https://github.com/YuzuJS/setImmediate * Copyright (c) 2012 Barnesandnoble.com, llc, Donavon West, and Domenic Denicola * License: MIT
1053
+ scheduler = function (callback) {
1054
+ var script = document.createElement("script");
1055
+ script.onreadystatechange = function () {
1056
+ script.onreadystatechange = null;
1057
+ document.documentElement.removeChild(script);
1058
+ script = null;
1059
+ callback();
1060
+ };
1061
+ document.documentElement.appendChild(script);
1062
+ };
1063
+ } else {
1064
+ scheduler = function (callback) {
1065
+ setTimeout(callback, 0);
1066
+ };
1067
+ }
1068
+
1069
+ function processTasks() {
1070
+ if (taskQueueLength) {
1071
+ // Each mark represents the end of a logical group of tasks and the number of these groups is
1072
+ // limited to prevent unchecked recursion.
1073
+ var mark = taskQueueLength, countMarks = 0;
1074
+
1075
+ // nextIndexToProcess keeps track of where we are in the queue; processTasks can be called recursively without issue
1076
+ for (var task; nextIndexToProcess < taskQueueLength; ) {
1077
+ if (task = taskQueue[nextIndexToProcess++]) {
1078
+ if (nextIndexToProcess > mark) {
1079
+ if (++countMarks >= 5000) {
1080
+ nextIndexToProcess = taskQueueLength; // skip all tasks remaining in the queue since any of them could be causing the recursion
1081
+ ko.utils.deferError(Error("'Too much recursion' after processing " + countMarks + " task groups."));
1082
+ break;
1083
+ }
1084
+ mark = taskQueueLength;
1085
+ }
1086
+ try {
1087
+ task();
1088
+ } catch (ex) {
1089
+ ko.utils.deferError(ex);
1090
+ }
1091
+ }
1092
+ }
1093
+ }
1094
+ }
1095
+
1096
+ function scheduledProcess() {
1097
+ processTasks();
1098
+
1099
+ // Reset the queue
1100
+ nextIndexToProcess = taskQueueLength = taskQueue.length = 0;
1101
+ }
1102
+
1103
+ function scheduleTaskProcessing() {
1104
+ ko.tasks['scheduler'](scheduledProcess);
1105
+ }
1106
+
1107
+ var tasks = {
1108
+ 'scheduler': scheduler, // Allow overriding the scheduler
1109
+
1110
+ schedule: function (func) {
1111
+ if (!taskQueueLength) {
1112
+ scheduleTaskProcessing();
1113
+ }
1114
+
1115
+ taskQueue[taskQueueLength++] = func;
1116
+ return nextHandle++;
1117
+ },
1118
+
1119
+ cancel: function (handle) {
1120
+ var index = handle - (nextHandle - taskQueueLength);
1121
+ if (index >= nextIndexToProcess && index < taskQueueLength) {
1122
+ taskQueue[index] = null;
1123
+ }
1124
+ },
1125
+
1126
+ // For testing only: reset the queue and return the previous queue length
1127
+ 'resetForTesting': function () {
1128
+ var length = taskQueueLength - nextIndexToProcess;
1129
+ nextIndexToProcess = taskQueueLength = taskQueue.length = 0;
1130
+ return length;
1131
+ },
1132
+
1133
+ runEarly: processTasks
1134
+ };
1135
+
1136
+ return tasks;
1137
+ })();
1138
+
1139
+ ko.exportSymbol('tasks', ko.tasks);
1140
+ ko.exportSymbol('tasks.schedule', ko.tasks.schedule);
1141
+ //ko.exportSymbol('tasks.cancel', ko.tasks.cancel); "cancel" isn't minified
1142
+ ko.exportSymbol('tasks.runEarly', ko.tasks.runEarly);
962
1143
  ko.extenders = {
963
1144
  'throttle': function(target, timeout) {
964
1145
  // Throttling means two things:
@@ -974,7 +1155,7 @@ ko.extenders = {
974
1155
  'read': target,
975
1156
  'write': function(value) {
976
1157
  clearTimeout(writeTimeoutInstance);
977
- writeTimeoutInstance = setTimeout(function() {
1158
+ writeTimeoutInstance = ko.utils.setTimeout(function() {
978
1159
  target(value);
979
1160
  }, timeout);
980
1161
  }
@@ -991,12 +1172,33 @@ ko.extenders = {
991
1172
  method = options['method'];
992
1173
  }
993
1174
 
1175
+ // rateLimit supersedes deferred updates
1176
+ target._deferUpdates = false;
1177
+
994
1178
  limitFunction = method == 'notifyWhenChangesStop' ? debounce : throttle;
995
1179
  target.limit(function(callback) {
996
1180
  return limitFunction(callback, timeout);
997
1181
  });
998
1182
  },
999
1183
 
1184
+ 'deferred': function(target, options) {
1185
+ if (options !== true) {
1186
+ throw new Error('The \'deferred\' extender only accepts the value \'true\', because it is not supported to turn deferral off once enabled.')
1187
+ }
1188
+
1189
+ if (!target._deferUpdates) {
1190
+ target._deferUpdates = true;
1191
+ target.limit(function (callback) {
1192
+ var handle;
1193
+ return function () {
1194
+ ko.tasks.cancel(handle);
1195
+ handle = ko.tasks.schedule(callback);
1196
+ target['notifySubscribers'](undefined, 'dirty');
1197
+ };
1198
+ });
1199
+ }
1200
+ },
1201
+
1000
1202
  'notify': function(target, notifyWhen) {
1001
1203
  target["equalityComparer"] = notifyWhen == "always" ?
1002
1204
  null : // null equalityComparer means to always notify
@@ -1014,7 +1216,7 @@ function throttle(callback, timeout) {
1014
1216
  var timeoutInstance;
1015
1217
  return function () {
1016
1218
  if (!timeoutInstance) {
1017
- timeoutInstance = setTimeout(function() {
1219
+ timeoutInstance = ko.utils.setTimeout(function () {
1018
1220
  timeoutInstance = undefined;
1019
1221
  callback();
1020
1222
  }, timeout);
@@ -1026,7 +1228,7 @@ function debounce(callback, timeout) {
1026
1228
  var timeoutInstance;
1027
1229
  return function () {
1028
1230
  clearTimeout(timeoutInstance);
1029
- timeoutInstance = setTimeout(callback, timeout);
1231
+ timeoutInstance = ko.utils.setTimeout(callback, timeout);
1030
1232
  };
1031
1233
  }
1032
1234
 
@@ -1058,14 +1260,29 @@ ko.subscription.prototype.dispose = function () {
1058
1260
  };
1059
1261
 
1060
1262
  ko.subscribable = function () {
1061
- ko.utils.setPrototypeOfOrExtend(this, ko.subscribable['fn']);
1062
- this._subscriptions = {};
1063
- this._versionNumber = 1;
1263
+ ko.utils.setPrototypeOfOrExtend(this, ko_subscribable_fn);
1264
+ ko_subscribable_fn.init(this);
1064
1265
  }
1065
1266
 
1066
1267
  var defaultEvent = "change";
1067
1268
 
1269
+ // Moved out of "limit" to avoid the extra closure
1270
+ function limitNotifySubscribers(value, event) {
1271
+ if (!event || event === defaultEvent) {
1272
+ this._limitChange(value);
1273
+ } else if (event === 'beforeChange') {
1274
+ this._limitBeforeChange(value);
1275
+ } else {
1276
+ this._origNotifySubscribers(value, event);
1277
+ }
1278
+ }
1279
+
1068
1280
  var ko_subscribable_fn = {
1281
+ init: function(instance) {
1282
+ instance._subscriptions = {};
1283
+ instance._versionNumber = 1;
1284
+ },
1285
+
1069
1286
  subscribe: function (callback, callbackTarget, event) {
1070
1287
  var self = this;
1071
1288
 
@@ -1122,40 +1339,34 @@ var ko_subscribable_fn = {
1122
1339
 
1123
1340
  limit: function(limitFunction) {
1124
1341
  var self = this, selfIsObservable = ko.isObservable(self),
1125
- isPending, previousValue, pendingValue, beforeChange = 'beforeChange';
1342
+ ignoreBeforeChange, previousValue, pendingValue, beforeChange = 'beforeChange';
1126
1343
 
1127
1344
  if (!self._origNotifySubscribers) {
1128
1345
  self._origNotifySubscribers = self["notifySubscribers"];
1129
- self["notifySubscribers"] = function(value, event) {
1130
- if (!event || event === defaultEvent) {
1131
- self._rateLimitedChange(value);
1132
- } else if (event === beforeChange) {
1133
- self._rateLimitedBeforeChange(value);
1134
- } else {
1135
- self._origNotifySubscribers(value, event);
1136
- }
1137
- };
1346
+ self["notifySubscribers"] = limitNotifySubscribers;
1138
1347
  }
1139
1348
 
1140
1349
  var finish = limitFunction(function() {
1350
+ self._notificationIsPending = false;
1351
+
1141
1352
  // If an observable provided a reference to itself, access it to get the latest value.
1142
1353
  // This allows computed observables to delay calculating their value until needed.
1143
1354
  if (selfIsObservable && pendingValue === self) {
1144
1355
  pendingValue = self();
1145
1356
  }
1146
- isPending = false;
1357
+ ignoreBeforeChange = false;
1147
1358
  if (self.isDifferent(previousValue, pendingValue)) {
1148
1359
  self._origNotifySubscribers(previousValue = pendingValue);
1149
1360
  }
1150
1361
  });
1151
1362
 
1152
- self._rateLimitedChange = function(value) {
1153
- isPending = true;
1363
+ self._limitChange = function(value) {
1364
+ self._notificationIsPending = ignoreBeforeChange = true;
1154
1365
  pendingValue = value;
1155
1366
  finish();
1156
1367
  };
1157
- self._rateLimitedBeforeChange = function(value) {
1158
- if (!isPending) {
1368
+ self._limitBeforeChange = function(value) {
1369
+ if (!ignoreBeforeChange) {
1159
1370
  previousValue = value;
1160
1371
  self._origNotifySubscribers(value, beforeChange);
1161
1372
  }
@@ -1172,7 +1383,8 @@ var ko_subscribable_fn = {
1172
1383
  } else {
1173
1384
  var total = 0;
1174
1385
  ko.utils.objectForEach(this._subscriptions, function(eventName, subscriptions) {
1175
- total += subscriptions.length;
1386
+ if (eventName !== 'dirty')
1387
+ total += subscriptions.length;
1176
1388
  });
1177
1389
  return total;
1178
1390
  }
@@ -1239,7 +1451,7 @@ ko.computedContext = ko.dependencyDetection = (function () {
1239
1451
  if (currentFrame) {
1240
1452
  if (!ko.isSubscribable(subscribable))
1241
1453
  throw new Error("Only subscribable things can act as dependencies");
1242
- currentFrame.callback(subscribable, subscribable._id || (subscribable._id = getId()));
1454
+ currentFrame.callback.call(currentFrame.callbackTarget, subscribable, subscribable._id || (subscribable._id = getId()));
1243
1455
  }
1244
1456
  },
1245
1457
 
@@ -1267,21 +1479,19 @@ ko.computedContext = ko.dependencyDetection = (function () {
1267
1479
  ko.exportSymbol('computedContext', ko.computedContext);
1268
1480
  ko.exportSymbol('computedContext.getDependenciesCount', ko.computedContext.getDependenciesCount);
1269
1481
  ko.exportSymbol('computedContext.isInitial', ko.computedContext.isInitial);
1270
- ko.exportSymbol('computedContext.isSleeping', ko.computedContext.isSleeping);
1271
1482
 
1272
1483
  ko.exportSymbol('ignoreDependencies', ko.ignoreDependencies = ko.dependencyDetection.ignore);
1273
- ko.observable = function (initialValue) {
1274
- var _latestValue = initialValue;
1484
+ var observableLatestValue = ko.utils.createSymbolOrString('_latestValue');
1275
1485
 
1486
+ ko.observable = function (initialValue) {
1276
1487
  function observable() {
1277
1488
  if (arguments.length > 0) {
1278
1489
  // Write
1279
1490
 
1280
1491
  // Ignore writes if the value hasn't changed
1281
- if (observable.isDifferent(_latestValue, arguments[0])) {
1492
+ if (observable.isDifferent(observable[observableLatestValue], arguments[0])) {
1282
1493
  observable.valueWillMutate();
1283
- _latestValue = arguments[0];
1284
- if (DEBUG) observable._latestValue = _latestValue;
1494
+ observable[observableLatestValue] = arguments[0];
1285
1495
  observable.valueHasMutated();
1286
1496
  }
1287
1497
  return this; // Permits chained assignments
@@ -1289,37 +1499,46 @@ ko.observable = function (initialValue) {
1289
1499
  else {
1290
1500
  // Read
1291
1501
  ko.dependencyDetection.registerDependency(observable); // The caller only needs to be notified of changes if they did a "read" operation
1292
- return _latestValue;
1502
+ return observable[observableLatestValue];
1293
1503
  }
1294
1504
  }
1295
- ko.subscribable.call(observable);
1296
- ko.utils.setPrototypeOfOrExtend(observable, ko.observable['fn']);
1297
1505
 
1298
- if (DEBUG) observable._latestValue = _latestValue;
1299
- observable.peek = function() { return _latestValue };
1300
- observable.valueHasMutated = function () { observable["notifySubscribers"](_latestValue); }
1301
- observable.valueWillMutate = function () { observable["notifySubscribers"](_latestValue, "beforeChange"); }
1506
+ observable[observableLatestValue] = initialValue;
1507
+
1508
+ // Inherit from 'subscribable'
1509
+ if (!ko.utils.canSetPrototype) {
1510
+ // 'subscribable' won't be on the prototype chain unless we put it there directly
1511
+ ko.utils.extend(observable, ko.subscribable['fn']);
1512
+ }
1513
+ ko.subscribable['fn'].init(observable);
1514
+
1515
+ // Inherit from 'observable'
1516
+ ko.utils.setPrototypeOfOrExtend(observable, observableFn);
1302
1517
 
1303
- ko.exportProperty(observable, 'peek', observable.peek);
1304
- ko.exportProperty(observable, "valueHasMutated", observable.valueHasMutated);
1305
- ko.exportProperty(observable, "valueWillMutate", observable.valueWillMutate);
1518
+ if (ko.options['deferUpdates']) {
1519
+ ko.extenders['deferred'](observable, true);
1520
+ }
1306
1521
 
1307
1522
  return observable;
1308
1523
  }
1309
1524
 
1310
- ko.observable['fn'] = {
1311
- "equalityComparer": valuesArePrimitiveAndEqual
1525
+ // Define prototype for observables
1526
+ var observableFn = {
1527
+ 'equalityComparer': valuesArePrimitiveAndEqual,
1528
+ peek: function() { return this[observableLatestValue]; },
1529
+ valueHasMutated: function () { this['notifySubscribers'](this[observableLatestValue]); },
1530
+ valueWillMutate: function () { this['notifySubscribers'](this[observableLatestValue], 'beforeChange'); }
1312
1531
  };
1313
1532
 
1314
- var protoProperty = ko.observable.protoProperty = "__ko_proto__";
1315
- ko.observable['fn'][protoProperty] = ko.observable;
1316
-
1317
1533
  // Note that for browsers that don't support proto assignment, the
1318
1534
  // inheritance chain is created manually in the ko.observable constructor
1319
1535
  if (ko.utils.canSetPrototype) {
1320
- ko.utils.setPrototypeOf(ko.observable['fn'], ko.subscribable['fn']);
1536
+ ko.utils.setPrototypeOf(observableFn, ko.subscribable['fn']);
1321
1537
  }
1322
1538
 
1539
+ var protoProperty = ko.observable.protoProperty = '__ko_proto__';
1540
+ observableFn[protoProperty] = ko.observable;
1541
+
1323
1542
  ko.hasPrototype = function(instance, prototype) {
1324
1543
  if ((instance === null) || (instance === undefined) || (instance[protoProperty] === undefined)) return false;
1325
1544
  if (instance[protoProperty] === prototype) return true;
@@ -1331,20 +1550,23 @@ ko.isObservable = function (instance) {
1331
1550
  }
1332
1551
  ko.isWriteableObservable = function (instance) {
1333
1552
  // Observable
1334
- if ((typeof instance == "function") && instance[protoProperty] === ko.observable)
1553
+ if ((typeof instance == 'function') && instance[protoProperty] === ko.observable)
1335
1554
  return true;
1336
1555
  // Writeable dependent observable
1337
- if ((typeof instance == "function") && (instance[protoProperty] === ko.dependentObservable) && (instance.hasWriteFunction))
1556
+ if ((typeof instance == 'function') && (instance[protoProperty] === ko.dependentObservable) && (instance.hasWriteFunction))
1338
1557
  return true;
1339
1558
  // Anything else
1340
1559
  return false;
1341
1560
  }
1342
1561
 
1343
-
1344
1562
  ko.exportSymbol('observable', ko.observable);
1345
1563
  ko.exportSymbol('isObservable', ko.isObservable);
1346
1564
  ko.exportSymbol('isWriteableObservable', ko.isWriteableObservable);
1347
1565
  ko.exportSymbol('isWritableObservable', ko.isWriteableObservable);
1566
+ ko.exportSymbol('observable.fn', observableFn);
1567
+ ko.exportProperty(observableFn, 'peek', observableFn.peek);
1568
+ ko.exportProperty(observableFn, 'valueHasMutated', observableFn.valueHasMutated);
1569
+ ko.exportProperty(observableFn, 'valueWillMutate', observableFn.valueWillMutate);
1348
1570
  ko.observableArray = function (initialValues) {
1349
1571
  initialValues = initialValues || [];
1350
1572
 
@@ -1436,6 +1658,12 @@ ko.observableArray['fn'] = {
1436
1658
  }
1437
1659
  };
1438
1660
 
1661
+ // Note that for browsers that don't support proto assignment, the
1662
+ // inheritance chain is created manually in the ko.observableArray constructor
1663
+ if (ko.utils.canSetPrototype) {
1664
+ ko.utils.setPrototypeOf(ko.observableArray['fn'], ko.observable['fn']);
1665
+ }
1666
+
1439
1667
  // Populate ko.observableArray.fn with read/write functions from native arrays
1440
1668
  // Important: Do not add any additional functions here that may reasonably be used to *read* data from the array
1441
1669
  // because we'll eval them without causing subscriptions, so ko.computed output could end up getting stale
@@ -1448,7 +1676,8 @@ ko.utils.arrayForEach(["pop", "push", "reverse", "shift", "sort", "splice", "uns
1448
1676
  this.cacheDiffForKnownOperation(underlyingArray, methodName, arguments);
1449
1677
  var methodCallResult = underlyingArray[methodName].apply(underlyingArray, arguments);
1450
1678
  this.valueHasMutated();
1451
- return methodCallResult;
1679
+ // The native sort and reverse methods return a reference to the array, but it makes more sense to return the observable array instead.
1680
+ return methodCallResult === underlyingArray ? this : methodCallResult;
1452
1681
  };
1453
1682
  });
1454
1683
 
@@ -1460,15 +1689,16 @@ ko.utils.arrayForEach(["slice"], function (methodName) {
1460
1689
  };
1461
1690
  });
1462
1691
 
1463
- // Note that for browsers that don't support proto assignment, the
1464
- // inheritance chain is created manually in the ko.observableArray constructor
1465
- if (ko.utils.canSetPrototype) {
1466
- ko.utils.setPrototypeOf(ko.observableArray['fn'], ko.observable['fn']);
1467
- }
1468
-
1469
1692
  ko.exportSymbol('observableArray', ko.observableArray);
1470
1693
  var arrayChangeEventName = 'arrayChange';
1471
- ko.extenders['trackArrayChanges'] = function(target) {
1694
+ ko.extenders['trackArrayChanges'] = function(target, options) {
1695
+ // Use the provided options--each call to trackArrayChanges overwrites the previously set options
1696
+ target.compareArrayOptions = {};
1697
+ if (options && typeof options == "object") {
1698
+ ko.utils.extend(target.compareArrayOptions, options);
1699
+ }
1700
+ target.compareArrayOptions['sparse'] = true;
1701
+
1472
1702
  // Only modify the target observable once
1473
1703
  if (target.cacheDiffForKnownOperation) {
1474
1704
  return;
@@ -1545,7 +1775,7 @@ ko.extenders['trackArrayChanges'] = function(target) {
1545
1775
  // plugin, which without this check would not be compatible with arrayChange notifications. Normally,
1546
1776
  // notifications are issued immediately so we wouldn't be queueing up more than one.
1547
1777
  if (!cachedDiff || pendingNotifications > 1) {
1548
- cachedDiff = ko.utils.compareArrays(previousContents, currentContents, { 'sparse': true });
1778
+ cachedDiff = ko.utils.compareArrays(previousContents, currentContents, target.compareArrayOptions);
1549
1779
  }
1550
1780
 
1551
1781
  return cachedDiff;
@@ -1605,41 +1835,162 @@ ko.extenders['trackArrayChanges'] = function(target) {
1605
1835
  cachedDiff = diff;
1606
1836
  };
1607
1837
  };
1838
+ var computedState = ko.utils.createSymbolOrString('_state');
1839
+
1608
1840
  ko.computed = ko.dependentObservable = function (evaluatorFunctionOrOptions, evaluatorFunctionTarget, options) {
1609
- var _latestValue,
1610
- _needsEvaluation = true,
1611
- _isBeingEvaluated = false,
1612
- _suppressDisposalUntilDisposeWhenReturnsFalse = false,
1613
- _isDisposed = false,
1614
- readFunction = evaluatorFunctionOrOptions,
1615
- pure = false,
1616
- isSleeping = false;
1617
-
1618
- if (readFunction && typeof readFunction == "object") {
1841
+ if (typeof evaluatorFunctionOrOptions === "object") {
1619
1842
  // Single-parameter syntax - everything is on this "options" param
1620
- options = readFunction;
1621
- readFunction = options["read"];
1843
+ options = evaluatorFunctionOrOptions;
1622
1844
  } else {
1623
1845
  // Multi-parameter syntax - construct the options according to the params passed
1624
1846
  options = options || {};
1625
- if (!readFunction)
1626
- readFunction = options["read"];
1847
+ if (evaluatorFunctionOrOptions) {
1848
+ options["read"] = evaluatorFunctionOrOptions;
1849
+ }
1627
1850
  }
1628
- if (typeof readFunction != "function")
1629
- throw new Error("Pass a function that returns the value of the ko.computed");
1851
+ if (typeof options["read"] != "function")
1852
+ throw Error("Pass a function that returns the value of the ko.computed");
1853
+
1854
+ var writeFunction = options["write"];
1855
+ var state = {
1856
+ latestValue: undefined,
1857
+ isStale: true,
1858
+ isBeingEvaluated: false,
1859
+ suppressDisposalUntilDisposeWhenReturnsFalse: false,
1860
+ isDisposed: false,
1861
+ pure: false,
1862
+ isSleeping: false,
1863
+ readFunction: options["read"],
1864
+ evaluatorFunctionTarget: evaluatorFunctionTarget || options["owner"],
1865
+ disposeWhenNodeIsRemoved: options["disposeWhenNodeIsRemoved"] || options.disposeWhenNodeIsRemoved || null,
1866
+ disposeWhen: options["disposeWhen"] || options.disposeWhen,
1867
+ domNodeDisposalCallback: null,
1868
+ dependencyTracking: {},
1869
+ dependenciesCount: 0,
1870
+ evaluationTimeoutInstance: null
1871
+ };
1630
1872
 
1631
- function addDependencyTracking(id, target, trackingObj) {
1632
- if (pure && target === dependentObservable) {
1633
- throw Error("A 'pure' computed must not be called recursively");
1873
+ function computedObservable() {
1874
+ if (arguments.length > 0) {
1875
+ if (typeof writeFunction === "function") {
1876
+ // Writing a value
1877
+ writeFunction.apply(state.evaluatorFunctionTarget, arguments);
1878
+ } else {
1879
+ throw new Error("Cannot write a value to a ko.computed unless you specify a 'write' option. If you wish to read the current value, don't pass any parameters.");
1880
+ }
1881
+ return this; // Permits chained assignments
1882
+ } else {
1883
+ // Reading the value
1884
+ ko.dependencyDetection.registerDependency(computedObservable);
1885
+ if (state.isStale || (state.isSleeping && computedObservable.haveDependenciesChanged())) {
1886
+ computedObservable.evaluateImmediate();
1887
+ }
1888
+ return state.latestValue;
1634
1889
  }
1890
+ }
1635
1891
 
1636
- dependencyTracking[id] = trackingObj;
1637
- trackingObj._order = _dependenciesCount++;
1638
- trackingObj._version = target.getVersion();
1892
+ computedObservable[computedState] = state;
1893
+ computedObservable.hasWriteFunction = typeof writeFunction === "function";
1894
+
1895
+ // Inherit from 'subscribable'
1896
+ if (!ko.utils.canSetPrototype) {
1897
+ // 'subscribable' won't be on the prototype chain unless we put it there directly
1898
+ ko.utils.extend(computedObservable, ko.subscribable['fn']);
1899
+ }
1900
+ ko.subscribable['fn'].init(computedObservable);
1901
+
1902
+ // Inherit from 'computed'
1903
+ ko.utils.setPrototypeOfOrExtend(computedObservable, computedFn);
1904
+
1905
+ if (options['pure']) {
1906
+ state.pure = true;
1907
+ state.isSleeping = true; // Starts off sleeping; will awake on the first subscription
1908
+ ko.utils.extend(computedObservable, pureComputedOverrides);
1909
+ } else if (options['deferEvaluation']) {
1910
+ ko.utils.extend(computedObservable, deferEvaluationOverrides);
1911
+ }
1912
+
1913
+ if (ko.options['deferUpdates']) {
1914
+ ko.extenders['deferred'](computedObservable, true);
1915
+ }
1916
+
1917
+ if (DEBUG) {
1918
+ // #1731 - Aid debugging by exposing the computed's options
1919
+ computedObservable["_options"] = options;
1920
+ }
1921
+
1922
+ if (state.disposeWhenNodeIsRemoved) {
1923
+ // Since this computed is associated with a DOM node, and we don't want to dispose the computed
1924
+ // until the DOM node is *removed* from the document (as opposed to never having been in the document),
1925
+ // we'll prevent disposal until "disposeWhen" first returns false.
1926
+ state.suppressDisposalUntilDisposeWhenReturnsFalse = true;
1927
+
1928
+ // disposeWhenNodeIsRemoved: true can be used to opt into the "only dispose after first false result"
1929
+ // behaviour even if there's no specific node to watch. In that case, clear the option so we don't try
1930
+ // to watch for a non-node's disposal. This technique is intended for KO's internal use only and shouldn't
1931
+ // be documented or used by application code, as it's likely to change in a future version of KO.
1932
+ if (!state.disposeWhenNodeIsRemoved.nodeType) {
1933
+ state.disposeWhenNodeIsRemoved = null;
1934
+ }
1935
+ }
1936
+
1937
+ // Evaluate, unless sleeping or deferEvaluation is true
1938
+ if (!state.isSleeping && !options['deferEvaluation']) {
1939
+ computedObservable.evaluateImmediate();
1940
+ }
1941
+
1942
+ // Attach a DOM node disposal callback so that the computed will be proactively disposed as soon as the node is
1943
+ // removed using ko.removeNode. But skip if isActive is false (there will never be any dependencies to dispose).
1944
+ if (state.disposeWhenNodeIsRemoved && computedObservable.isActive()) {
1945
+ ko.utils.domNodeDisposal.addDisposeCallback(state.disposeWhenNodeIsRemoved, state.domNodeDisposalCallback = function () {
1946
+ computedObservable.dispose();
1947
+ });
1948
+ }
1949
+
1950
+ return computedObservable;
1951
+ };
1952
+
1953
+ // Utility function that disposes a given dependencyTracking entry
1954
+ function computedDisposeDependencyCallback(id, entryToDispose) {
1955
+ if (entryToDispose !== null && entryToDispose.dispose) {
1956
+ entryToDispose.dispose();
1957
+ }
1958
+ }
1959
+
1960
+ // This function gets called each time a dependency is detected while evaluating a computed.
1961
+ // It's factored out as a shared function to avoid creating unnecessary function instances during evaluation.
1962
+ function computedBeginDependencyDetectionCallback(subscribable, id) {
1963
+ var computedObservable = this.computedObservable,
1964
+ state = computedObservable[computedState];
1965
+ if (!state.isDisposed) {
1966
+ if (this.disposalCount && this.disposalCandidates[id]) {
1967
+ // Don't want to dispose this subscription, as it's still being used
1968
+ computedObservable.addDependencyTracking(id, subscribable, this.disposalCandidates[id]);
1969
+ this.disposalCandidates[id] = null; // No need to actually delete the property - disposalCandidates is a transient object anyway
1970
+ --this.disposalCount;
1971
+ } else if (!state.dependencyTracking[id]) {
1972
+ // Brand new subscription - add it
1973
+ computedObservable.addDependencyTracking(id, subscribable, state.isSleeping ? { _target: subscribable } : computedObservable.subscribeToDependency(subscribable));
1974
+ }
1639
1975
  }
1976
+ }
1977
+
1978
+ var computedFn = {
1979
+ "equalityComparer": valuesArePrimitiveAndEqual,
1980
+ getDependenciesCount: function () {
1981
+ return this[computedState].dependenciesCount;
1982
+ },
1983
+ addDependencyTracking: function (id, target, trackingObj) {
1984
+ if (this[computedState].pure && target === this) {
1985
+ throw Error("A 'pure' computed must not be called recursively");
1986
+ }
1640
1987
 
1641
- function haveDependenciesChanged() {
1642
- var id, dependency;
1988
+ this[computedState].dependencyTracking[id] = trackingObj;
1989
+ trackingObj._order = this[computedState].dependenciesCount++;
1990
+ trackingObj._version = target.getVersion();
1991
+ },
1992
+ haveDependenciesChanged: function () {
1993
+ var id, dependency, dependencyTracking = this[computedState].dependencyTracking;
1643
1994
  for (id in dependencyTracking) {
1644
1995
  if (dependencyTracking.hasOwnProperty(id)) {
1645
1996
  dependency = dependencyTracking[id];
@@ -1648,38 +1999,57 @@ ko.computed = ko.dependentObservable = function (evaluatorFunctionOrOptions, eva
1648
1999
  }
1649
2000
  }
1650
2001
  }
1651
- }
1652
-
1653
- function disposeComputed() {
1654
- if (!isSleeping && dependencyTracking) {
1655
- ko.utils.objectForEach(dependencyTracking, function (id, dependency) {
1656
- if (dependency.dispose)
1657
- dependency.dispose();
1658
- });
2002
+ },
2003
+ markDirty: function () {
2004
+ // Process "dirty" events if we can handle delayed notifications
2005
+ if (this._evalDelayed && !this[computedState].isBeingEvaluated) {
2006
+ this._evalDelayed();
1659
2007
  }
1660
- dependencyTracking = null;
1661
- _dependenciesCount = 0;
1662
- _isDisposed = true;
1663
- _needsEvaluation = false;
1664
- isSleeping = false;
1665
- }
1666
-
1667
- function evaluatePossiblyAsync() {
1668
- var throttleEvaluationTimeout = dependentObservable['throttleEvaluation'];
2008
+ },
2009
+ isActive: function () {
2010
+ return this[computedState].isStale || this[computedState].dependenciesCount > 0;
2011
+ },
2012
+ respondToChange: function () {
2013
+ // Ignore "change" events if we've already scheduled a delayed notification
2014
+ if (!this._notificationIsPending) {
2015
+ this.evaluatePossiblyAsync();
2016
+ }
2017
+ },
2018
+ subscribeToDependency: function (target) {
2019
+ if (target._deferUpdates && !this[computedState].disposeWhenNodeIsRemoved) {
2020
+ var dirtySub = target.subscribe(this.markDirty, this, 'dirty'),
2021
+ changeSub = target.subscribe(this.respondToChange, this);
2022
+ return {
2023
+ _target: target,
2024
+ dispose: function () {
2025
+ dirtySub.dispose();
2026
+ changeSub.dispose();
2027
+ }
2028
+ };
2029
+ } else {
2030
+ return target.subscribe(this.evaluatePossiblyAsync, this);
2031
+ }
2032
+ },
2033
+ evaluatePossiblyAsync: function () {
2034
+ var computedObservable = this,
2035
+ throttleEvaluationTimeout = computedObservable['throttleEvaluation'];
1669
2036
  if (throttleEvaluationTimeout && throttleEvaluationTimeout >= 0) {
1670
- clearTimeout(evaluationTimeoutInstance);
1671
- evaluationTimeoutInstance = setTimeout(function () {
1672
- evaluateImmediate(true /*notifyChange*/);
2037
+ clearTimeout(this[computedState].evaluationTimeoutInstance);
2038
+ this[computedState].evaluationTimeoutInstance = ko.utils.setTimeout(function () {
2039
+ computedObservable.evaluateImmediate(true /*notifyChange*/);
1673
2040
  }, throttleEvaluationTimeout);
1674
- } else if (dependentObservable._evalRateLimited) {
1675
- dependentObservable._evalRateLimited();
2041
+ } else if (computedObservable._evalDelayed) {
2042
+ computedObservable._evalDelayed();
1676
2043
  } else {
1677
- evaluateImmediate(true /*notifyChange*/);
2044
+ computedObservable.evaluateImmediate(true /*notifyChange*/);
1678
2045
  }
1679
- }
2046
+ },
2047
+ evaluateImmediate: function (notifyChange) {
2048
+ var computedObservable = this,
2049
+ state = computedObservable[computedState],
2050
+ disposeWhen = state.disposeWhen;
1680
2051
 
1681
- function evaluateImmediate(notifyChange) {
1682
- if (_isBeingEvaluated) {
2052
+ if (state.isBeingEvaluated) {
1683
2053
  // If the evaluation of a ko.computed causes side effects, it's possible that it will trigger its own re-evaluation.
1684
2054
  // This is not desirable (it's hard for a developer to realise a chain of dependencies might cause this, and they almost
1685
2055
  // certainly didn't intend infinite re-evaluations). So, for predictability, we simply prevent ko.computeds from causing
@@ -1688,297 +2058,239 @@ ko.computed = ko.dependentObservable = function (evaluatorFunctionOrOptions, eva
1688
2058
  }
1689
2059
 
1690
2060
  // Do not evaluate (and possibly capture new dependencies) if disposed
1691
- if (_isDisposed) {
2061
+ if (state.isDisposed) {
1692
2062
  return;
1693
2063
  }
1694
2064
 
1695
- if (disposeWhen && disposeWhen()) {
1696
- // See comment below about _suppressDisposalUntilDisposeWhenReturnsFalse
1697
- if (!_suppressDisposalUntilDisposeWhenReturnsFalse) {
1698
- dispose();
2065
+ if (state.disposeWhenNodeIsRemoved && !ko.utils.domNodeIsAttachedToDocument(state.disposeWhenNodeIsRemoved) || disposeWhen && disposeWhen()) {
2066
+ // See comment above about suppressDisposalUntilDisposeWhenReturnsFalse
2067
+ if (!state.suppressDisposalUntilDisposeWhenReturnsFalse) {
2068
+ computedObservable.dispose();
1699
2069
  return;
1700
2070
  }
1701
2071
  } else {
1702
2072
  // It just did return false, so we can stop suppressing now
1703
- _suppressDisposalUntilDisposeWhenReturnsFalse = false;
2073
+ state.suppressDisposalUntilDisposeWhenReturnsFalse = false;
1704
2074
  }
1705
2075
 
1706
- _isBeingEvaluated = true;
1707
-
2076
+ state.isBeingEvaluated = true;
1708
2077
  try {
1709
- // Initially, we assume that none of the subscriptions are still being used (i.e., all are candidates for disposal).
1710
- // Then, during evaluation, we cross off any that are in fact still being used.
1711
- var disposalCandidates = dependencyTracking,
1712
- disposalCount = _dependenciesCount,
1713
- isInitial = pure ? undefined : !_dependenciesCount; // If we're evaluating when there are no previous dependencies, it must be the first time
1714
-
1715
- ko.dependencyDetection.begin({
1716
- callback: function(subscribable, id) {
1717
- if (!_isDisposed) {
1718
- if (disposalCount && disposalCandidates[id]) {
1719
- // Don't want to dispose this subscription, as it's still being used
1720
- addDependencyTracking(id, subscribable, disposalCandidates[id]);
1721
- delete disposalCandidates[id];
1722
- --disposalCount;
1723
- } else if (!dependencyTracking[id]) {
1724
- // Brand new subscription - add it
1725
- addDependencyTracking(id, subscribable, isSleeping ? { _target: subscribable } : subscribable.subscribe(evaluatePossiblyAsync));
1726
- }
1727
- }
1728
- },
1729
- computed: dependentObservable,
1730
- isInitial: isInitial
1731
- });
2078
+ this.evaluateImmediate_CallReadWithDependencyDetection(notifyChange);
2079
+ } finally {
2080
+ state.isBeingEvaluated = false;
2081
+ }
1732
2082
 
1733
- dependencyTracking = {};
1734
- _dependenciesCount = 0;
2083
+ if (!state.dependenciesCount) {
2084
+ computedObservable.dispose();
2085
+ }
2086
+ },
2087
+ evaluateImmediate_CallReadWithDependencyDetection: function (notifyChange) {
2088
+ // This function is really just part of the evaluateImmediate logic. You would never call it from anywhere else.
2089
+ // Factoring it out into a separate function means it can be independent of the try/catch block in evaluateImmediate,
2090
+ // which contributes to saving about 40% off the CPU overhead of computed evaluation (on V8 at least).
2091
+
2092
+ var computedObservable = this,
2093
+ state = computedObservable[computedState];
2094
+
2095
+ // Initially, we assume that none of the subscriptions are still being used (i.e., all are candidates for disposal).
2096
+ // Then, during evaluation, we cross off any that are in fact still being used.
2097
+ var isInitial = state.pure ? undefined : !state.dependenciesCount, // If we're evaluating when there are no previous dependencies, it must be the first time
2098
+ dependencyDetectionContext = {
2099
+ computedObservable: computedObservable,
2100
+ disposalCandidates: state.dependencyTracking,
2101
+ disposalCount: state.dependenciesCount
2102
+ };
1735
2103
 
1736
- try {
1737
- var newValue = evaluatorFunctionTarget ? readFunction.call(evaluatorFunctionTarget) : readFunction();
2104
+ ko.dependencyDetection.begin({
2105
+ callbackTarget: dependencyDetectionContext,
2106
+ callback: computedBeginDependencyDetectionCallback,
2107
+ computed: computedObservable,
2108
+ isInitial: isInitial
2109
+ });
1738
2110
 
1739
- } finally {
1740
- ko.dependencyDetection.end();
2111
+ state.dependencyTracking = {};
2112
+ state.dependenciesCount = 0;
1741
2113
 
1742
- // For each subscription no longer being used, remove it from the active subscriptions list and dispose it
1743
- if (disposalCount && !isSleeping) {
1744
- ko.utils.objectForEach(disposalCandidates, function(id, toDispose) {
1745
- if (toDispose.dispose)
1746
- toDispose.dispose();
1747
- });
1748
- }
2114
+ var newValue = this.evaluateImmediate_CallReadThenEndDependencyDetection(state, dependencyDetectionContext);
1749
2115
 
1750
- _needsEvaluation = false;
2116
+ if (computedObservable.isDifferent(state.latestValue, newValue)) {
2117
+ if (!state.isSleeping) {
2118
+ computedObservable["notifySubscribers"](state.latestValue, "beforeChange");
1751
2119
  }
1752
2120
 
1753
- if (dependentObservable.isDifferent(_latestValue, newValue)) {
1754
- if (!isSleeping) {
1755
- notify(_latestValue, "beforeChange");
1756
- }
1757
-
1758
- _latestValue = newValue;
1759
- if (DEBUG) dependentObservable._latestValue = _latestValue;
2121
+ state.latestValue = newValue;
1760
2122
 
1761
- if (isSleeping) {
1762
- dependentObservable.updateVersion();
1763
- } else if (notifyChange) {
1764
- notify(_latestValue);
1765
- }
2123
+ if (state.isSleeping) {
2124
+ computedObservable.updateVersion();
2125
+ } else if (notifyChange) {
2126
+ computedObservable["notifySubscribers"](state.latestValue);
1766
2127
  }
2128
+ }
1767
2129
 
1768
- if (isInitial) {
1769
- notify(_latestValue, "awake");
1770
- }
1771
- } finally {
1772
- _isBeingEvaluated = false;
2130
+ if (isInitial) {
2131
+ computedObservable["notifySubscribers"](state.latestValue, "awake");
1773
2132
  }
2133
+ },
2134
+ evaluateImmediate_CallReadThenEndDependencyDetection: function (state, dependencyDetectionContext) {
2135
+ // This function is really part of the evaluateImmediate_CallReadWithDependencyDetection logic.
2136
+ // You'd never call it from anywhere else. Factoring it out means that evaluateImmediate_CallReadWithDependencyDetection
2137
+ // can be independent of try/finally blocks, which contributes to saving about 40% off the CPU
2138
+ // overhead of computed evaluation (on V8 at least).
1774
2139
 
1775
- if (!_dependenciesCount)
1776
- dispose();
1777
- }
2140
+ try {
2141
+ var readFunction = state.readFunction;
2142
+ return state.evaluatorFunctionTarget ? readFunction.call(state.evaluatorFunctionTarget) : readFunction();
2143
+ } finally {
2144
+ ko.dependencyDetection.end();
1778
2145
 
1779
- function dependentObservable() {
1780
- if (arguments.length > 0) {
1781
- if (typeof writeFunction === "function") {
1782
- // Writing a value
1783
- writeFunction.apply(evaluatorFunctionTarget, arguments);
1784
- } else {
1785
- throw new Error("Cannot write a value to a ko.computed unless you specify a 'write' option. If you wish to read the current value, don't pass any parameters.");
2146
+ // For each subscription no longer being used, remove it from the active subscriptions list and dispose it
2147
+ if (dependencyDetectionContext.disposalCount && !state.isSleeping) {
2148
+ ko.utils.objectForEach(dependencyDetectionContext.disposalCandidates, computedDisposeDependencyCallback);
1786
2149
  }
1787
- return this; // Permits chained assignments
1788
- } else {
1789
- // Reading the value
1790
- ko.dependencyDetection.registerDependency(dependentObservable);
1791
- if (_needsEvaluation || (isSleeping && haveDependenciesChanged())) {
1792
- evaluateImmediate();
1793
- }
1794
- return _latestValue;
1795
- }
1796
- }
1797
2150
 
1798
- function peek() {
2151
+ state.isStale = false;
2152
+ }
2153
+ },
2154
+ peek: function () {
1799
2155
  // Peek won't re-evaluate, except while the computed is sleeping or to get the initial value when "deferEvaluation" is set.
1800
- if ((_needsEvaluation && !_dependenciesCount) || (isSleeping && haveDependenciesChanged())) {
1801
- evaluateImmediate();
2156
+ var state = this[computedState];
2157
+ if ((state.isStale && !state.dependenciesCount) || (state.isSleeping && this.haveDependenciesChanged())) {
2158
+ this.evaluateImmediate();
1802
2159
  }
1803
- return _latestValue;
1804
- }
1805
-
1806
- function isActive() {
1807
- return _needsEvaluation || _dependenciesCount > 0;
1808
- }
1809
-
1810
- function notify(value, event) {
1811
- dependentObservable["notifySubscribers"](value, event);
1812
- }
1813
-
1814
- // By here, "options" is always non-null
1815
- var writeFunction = options["write"],
1816
- disposeWhenNodeIsRemoved = options["disposeWhenNodeIsRemoved"] || options.disposeWhenNodeIsRemoved || null,
1817
- disposeWhenOption = options["disposeWhen"] || options.disposeWhen,
1818
- disposeWhen = disposeWhenOption,
1819
- dispose = disposeComputed,
1820
- dependencyTracking = {},
1821
- _dependenciesCount = 0,
1822
- evaluationTimeoutInstance = null;
1823
-
1824
- if (!evaluatorFunctionTarget)
1825
- evaluatorFunctionTarget = options["owner"];
1826
-
1827
- ko.subscribable.call(dependentObservable);
1828
- ko.utils.setPrototypeOfOrExtend(dependentObservable, ko.dependentObservable['fn']);
1829
-
1830
- dependentObservable.peek = peek;
1831
- dependentObservable.getDependenciesCount = function () { return _dependenciesCount; };
1832
- dependentObservable.hasWriteFunction = typeof writeFunction === "function";
1833
- dependentObservable.dispose = function () { dispose(); };
1834
- dependentObservable.isActive = isActive;
1835
-
1836
- // Replace the limit function with one that delays evaluation as well.
1837
- var originalLimit = dependentObservable.limit;
1838
- dependentObservable.limit = function(limitFunction) {
1839
- originalLimit.call(dependentObservable, limitFunction);
1840
- dependentObservable._evalRateLimited = function() {
1841
- dependentObservable._rateLimitedBeforeChange(_latestValue);
2160
+ return state.latestValue;
2161
+ },
2162
+ limit: function (limitFunction) {
2163
+ // Override the limit function with one that delays evaluation as well
2164
+ ko.subscribable['fn'].limit.call(this, limitFunction);
2165
+ this._evalDelayed = function () {
2166
+ this._limitBeforeChange(this[computedState].latestValue);
1842
2167
 
1843
- _needsEvaluation = true; // Mark as dirty
2168
+ this[computedState].isStale = true; // Mark as dirty
1844
2169
 
1845
- // Pass the observable to the rate-limit code, which will access it when
2170
+ // Pass the observable to the "limit" code, which will access it when
1846
2171
  // it's time to do the notification.
1847
- dependentObservable._rateLimitedChange(dependentObservable);
2172
+ this._limitChange(this);
1848
2173
  }
1849
- };
1850
-
1851
- if (options['pure']) {
1852
- pure = true;
1853
- isSleeping = true; // Starts off sleeping; will awake on the first subscription
1854
- dependentObservable.beforeSubscriptionAdd = function (event) {
1855
- // If asleep, wake up the computed by subscribing to any dependencies.
1856
- if (!_isDisposed && isSleeping && event == 'change') {
1857
- isSleeping = false;
1858
- if (_needsEvaluation || haveDependenciesChanged()) {
1859
- dependencyTracking = null;
1860
- _dependenciesCount = 0;
1861
- _needsEvaluation = true;
1862
- evaluateImmediate();
1863
- } else {
1864
- // First put the dependencies in order
1865
- var dependeciesOrder = [];
1866
- ko.utils.objectForEach(dependencyTracking, function (id, dependency) {
1867
- dependeciesOrder[dependency._order] = id;
1868
- });
1869
- // Next, subscribe to each one
1870
- ko.utils.arrayForEach(dependeciesOrder, function(id, order) {
1871
- var dependency = dependencyTracking[id],
1872
- subscription = dependency._target.subscribe(evaluatePossiblyAsync);
1873
- subscription._order = order;
1874
- subscription._version = dependency._version;
1875
- dependencyTracking[id] = subscription;
1876
- });
1877
- }
1878
- if (!_isDisposed) { // test since evaluating could trigger disposal
1879
- notify(_latestValue, "awake");
1880
- }
1881
- }
1882
- };
2174
+ },
2175
+ dispose: function () {
2176
+ var state = this[computedState];
2177
+ if (!state.isSleeping && state.dependencyTracking) {
2178
+ ko.utils.objectForEach(state.dependencyTracking, function (id, dependency) {
2179
+ if (dependency.dispose)
2180
+ dependency.dispose();
2181
+ });
2182
+ }
2183
+ if (state.disposeWhenNodeIsRemoved && state.domNodeDisposalCallback) {
2184
+ ko.utils.domNodeDisposal.removeDisposeCallback(state.disposeWhenNodeIsRemoved, state.domNodeDisposalCallback);
2185
+ }
2186
+ state.dependencyTracking = null;
2187
+ state.dependenciesCount = 0;
2188
+ state.isDisposed = true;
2189
+ state.isStale = false;
2190
+ state.isSleeping = false;
2191
+ state.disposeWhenNodeIsRemoved = null;
2192
+ }
2193
+ };
1883
2194
 
1884
- dependentObservable.afterSubscriptionRemove = function (event) {
1885
- if (!_isDisposed && event == 'change' && !dependentObservable.hasSubscriptionsForEvent('change')) {
1886
- ko.utils.objectForEach(dependencyTracking, function (id, dependency) {
1887
- if (dependency.dispose) {
1888
- dependencyTracking[id] = {
1889
- _target: dependency._target,
1890
- _order: dependency._order,
1891
- _version: dependency._version
1892
- };
1893
- dependency.dispose();
1894
- }
2195
+ var pureComputedOverrides = {
2196
+ beforeSubscriptionAdd: function (event) {
2197
+ // If asleep, wake up the computed by subscribing to any dependencies.
2198
+ var computedObservable = this,
2199
+ state = computedObservable[computedState];
2200
+ if (!state.isDisposed && state.isSleeping && event == 'change') {
2201
+ state.isSleeping = false;
2202
+ if (state.isStale || computedObservable.haveDependenciesChanged()) {
2203
+ state.dependencyTracking = null;
2204
+ state.dependenciesCount = 0;
2205
+ state.isStale = true;
2206
+ computedObservable.evaluateImmediate();
2207
+ } else {
2208
+ // First put the dependencies in order
2209
+ var dependeciesOrder = [];
2210
+ ko.utils.objectForEach(state.dependencyTracking, function (id, dependency) {
2211
+ dependeciesOrder[dependency._order] = id;
2212
+ });
2213
+ // Next, subscribe to each one
2214
+ ko.utils.arrayForEach(dependeciesOrder, function (id, order) {
2215
+ var dependency = state.dependencyTracking[id],
2216
+ subscription = computedObservable.subscribeToDependency(dependency._target);
2217
+ subscription._order = order;
2218
+ subscription._version = dependency._version;
2219
+ state.dependencyTracking[id] = subscription;
1895
2220
  });
1896
- isSleeping = true;
1897
- notify(undefined, "asleep");
1898
2221
  }
1899
- };
1900
-
2222
+ if (!state.isDisposed) { // test since evaluating could trigger disposal
2223
+ computedObservable["notifySubscribers"](state.latestValue, "awake");
2224
+ }
2225
+ }
2226
+ },
2227
+ afterSubscriptionRemove: function (event) {
2228
+ var state = this[computedState];
2229
+ if (!state.isDisposed && event == 'change' && !this.hasSubscriptionsForEvent('change')) {
2230
+ ko.utils.objectForEach(state.dependencyTracking, function (id, dependency) {
2231
+ if (dependency.dispose) {
2232
+ state.dependencyTracking[id] = {
2233
+ _target: dependency._target,
2234
+ _order: dependency._order,
2235
+ _version: dependency._version
2236
+ };
2237
+ dependency.dispose();
2238
+ }
2239
+ });
2240
+ state.isSleeping = true;
2241
+ this["notifySubscribers"](undefined, "asleep");
2242
+ }
2243
+ },
2244
+ getVersion: function () {
1901
2245
  // Because a pure computed is not automatically updated while it is sleeping, we can't
1902
2246
  // simply return the version number. Instead, we check if any of the dependencies have
1903
2247
  // changed and conditionally re-evaluate the computed observable.
1904
- dependentObservable._originalGetVersion = dependentObservable.getVersion;
1905
- dependentObservable.getVersion = function () {
1906
- if (isSleeping && (_needsEvaluation || haveDependenciesChanged())) {
1907
- evaluateImmediate();
1908
- }
1909
- return dependentObservable._originalGetVersion();
1910
- };
1911
- } else if (options['deferEvaluation']) {
1912
- // This will force a computed with deferEvaluation to evaluate when the first subscriptions is registered.
1913
- dependentObservable.beforeSubscriptionAdd = function (event) {
1914
- if (event == 'change' || event == 'beforeChange') {
1915
- peek();
1916
- }
2248
+ var state = this[computedState];
2249
+ if (state.isSleeping && (state.isStale || this.haveDependenciesChanged())) {
2250
+ this.evaluateImmediate();
1917
2251
  }
2252
+ return ko.subscribable['fn'].getVersion.call(this);
1918
2253
  }
2254
+ };
1919
2255
 
1920
- ko.exportProperty(dependentObservable, 'peek', dependentObservable.peek);
1921
- ko.exportProperty(dependentObservable, 'dispose', dependentObservable.dispose);
1922
- ko.exportProperty(dependentObservable, 'isActive', dependentObservable.isActive);
1923
- ko.exportProperty(dependentObservable, 'getDependenciesCount', dependentObservable.getDependenciesCount);
1924
-
1925
- // Add a "disposeWhen" callback that, on each evaluation, disposes if the node was removed without using ko.removeNode.
1926
- if (disposeWhenNodeIsRemoved) {
1927
- // Since this computed is associated with a DOM node, and we don't want to dispose the computed
1928
- // until the DOM node is *removed* from the document (as opposed to never having been in the document),
1929
- // we'll prevent disposal until "disposeWhen" first returns false.
1930
- _suppressDisposalUntilDisposeWhenReturnsFalse = true;
1931
-
1932
- // Only watch for the node's disposal if the value really is a node. It might not be,
1933
- // e.g., { disposeWhenNodeIsRemoved: true } can be used to opt into the "only dispose
1934
- // after first false result" behaviour even if there's no specific node to watch. This
1935
- // technique is intended for KO's internal use only and shouldn't be documented or used
1936
- // by application code, as it's likely to change in a future version of KO.
1937
- if (disposeWhenNodeIsRemoved.nodeType) {
1938
- disposeWhen = function () {
1939
- return !ko.utils.domNodeIsAttachedToDocument(disposeWhenNodeIsRemoved) || (disposeWhenOption && disposeWhenOption());
1940
- };
2256
+ var deferEvaluationOverrides = {
2257
+ beforeSubscriptionAdd: function (event) {
2258
+ // This will force a computed with deferEvaluation to evaluate when the first subscription is registered.
2259
+ if (event == 'change' || event == 'beforeChange') {
2260
+ this.peek();
1941
2261
  }
1942
2262
  }
1943
-
1944
- // Evaluate, unless sleeping or deferEvaluation is true
1945
- if (!isSleeping && !options['deferEvaluation'])
1946
- evaluateImmediate();
1947
-
1948
- // Attach a DOM node disposal callback so that the computed will be proactively disposed as soon as the node is
1949
- // removed using ko.removeNode. But skip if isActive is false (there will never be any dependencies to dispose).
1950
- if (disposeWhenNodeIsRemoved && isActive() && disposeWhenNodeIsRemoved.nodeType) {
1951
- dispose = function() {
1952
- ko.utils.domNodeDisposal.removeDisposeCallback(disposeWhenNodeIsRemoved, dispose);
1953
- disposeComputed();
1954
- };
1955
- ko.utils.domNodeDisposal.addDisposeCallback(disposeWhenNodeIsRemoved, dispose);
1956
- }
1957
-
1958
- return dependentObservable;
1959
2263
  };
1960
2264
 
1961
- ko.isComputed = function(instance) {
1962
- return ko.hasPrototype(instance, ko.dependentObservable);
1963
- };
2265
+ // Note that for browsers that don't support proto assignment, the
2266
+ // inheritance chain is created manually in the ko.computed constructor
2267
+ if (ko.utils.canSetPrototype) {
2268
+ ko.utils.setPrototypeOf(computedFn, ko.subscribable['fn']);
2269
+ }
1964
2270
 
2271
+ // Set the proto chain values for ko.hasPrototype
1965
2272
  var protoProp = ko.observable.protoProperty; // == "__ko_proto__"
1966
- ko.dependentObservable[protoProp] = ko.observable;
2273
+ ko.computed[protoProp] = ko.observable;
2274
+ computedFn[protoProp] = ko.computed;
1967
2275
 
1968
- ko.dependentObservable['fn'] = {
1969
- "equalityComparer": valuesArePrimitiveAndEqual
2276
+ ko.isComputed = function (instance) {
2277
+ return ko.hasPrototype(instance, ko.computed);
1970
2278
  };
1971
- ko.dependentObservable['fn'][protoProp] = ko.dependentObservable;
1972
2279
 
1973
- // Note that for browsers that don't support proto assignment, the
1974
- // inheritance chain is created manually in the ko.dependentObservable constructor
1975
- if (ko.utils.canSetPrototype) {
1976
- ko.utils.setPrototypeOf(ko.dependentObservable['fn'], ko.subscribable['fn']);
1977
- }
2280
+ ko.isPureComputed = function (instance) {
2281
+ return ko.hasPrototype(instance, ko.computed)
2282
+ && instance[computedState] && instance[computedState].pure;
2283
+ };
1978
2284
 
1979
- ko.exportSymbol('dependentObservable', ko.dependentObservable);
1980
- ko.exportSymbol('computed', ko.dependentObservable); // Make "ko.computed" an alias for "ko.dependentObservable"
2285
+ ko.exportSymbol('computed', ko.computed);
2286
+ ko.exportSymbol('dependentObservable', ko.computed); // export ko.dependentObservable for backwards compatibility (1.x)
1981
2287
  ko.exportSymbol('isComputed', ko.isComputed);
2288
+ ko.exportSymbol('isPureComputed', ko.isPureComputed);
2289
+ ko.exportSymbol('computed.fn', computedFn);
2290
+ ko.exportProperty(computedFn, 'peek', computedFn.peek);
2291
+ ko.exportProperty(computedFn, 'dispose', computedFn.dispose);
2292
+ ko.exportProperty(computedFn, 'isActive', computedFn.isActive);
2293
+ ko.exportProperty(computedFn, 'getDependenciesCount', computedFn.getDependenciesCount);
1982
2294
 
1983
2295
  ko.pureComputed = function (evaluatorFunctionOrOptions, evaluatorFunctionTarget) {
1984
2296
  if (typeof evaluatorFunctionOrOptions === 'function') {
@@ -2016,7 +2328,7 @@ ko.exportSymbol('pureComputed', ko.pureComputed);
2016
2328
  visitedObjects = visitedObjects || new objectLookup();
2017
2329
 
2018
2330
  rootObject = mapInputCallback(rootObject);
2019
- var canHaveProperties = (typeof rootObject == "object") && (rootObject !== null) && (rootObject !== undefined) && (!(rootObject instanceof Date)) && (!(rootObject instanceof String)) && (!(rootObject instanceof Number)) && (!(rootObject instanceof Boolean));
2331
+ var canHaveProperties = (typeof rootObject == "object") && (rootObject !== null) && (rootObject !== undefined) && (!(rootObject instanceof RegExp)) && (!(rootObject instanceof Date)) && (!(rootObject instanceof String)) && (!(rootObject instanceof Number)) && (!(rootObject instanceof Boolean));
2020
2332
  if (!canHaveProperties)
2021
2333
  return rootObject;
2022
2334
 
@@ -2629,14 +2941,16 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
2629
2941
  (function () {
2630
2942
  ko.bindingHandlers = {};
2631
2943
 
2632
- // The following element types will not be recursed into during binding. In the future, we
2633
- // may consider adding <template> to this list, because such elements' contents are always
2634
- // intended to be bound in a different context from where they appear in the document.
2944
+ // The following element types will not be recursed into during binding.
2635
2945
  var bindingDoesNotRecurseIntoElementTypes = {
2636
2946
  // Don't want bindings that operate on text nodes to mutate <script> and <textarea> contents,
2637
- // because it's unexpected and a potential XSS issue
2947
+ // because it's unexpected and a potential XSS issue.
2948
+ // Also bindings should not operate on <template> elements since this breaks in Internet Explorer
2949
+ // and because such elements' contents are always intended to be bound in a different context
2950
+ // from where they appear in the document.
2638
2951
  'script': true,
2639
- 'textarea': true
2952
+ 'textarea': true,
2953
+ 'template': true
2640
2954
  };
2641
2955
 
2642
2956
  // Use an overridable method for retrieving binding handlers so that a plugins may support dynamically created handlers
@@ -2653,7 +2967,7 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
2653
2967
  // any child contexts, must be updated when the view model is changed.
2654
2968
  function updateContext() {
2655
2969
  // Most of the time, the context will directly get a view model object, but if a function is given,
2656
- // we call the function to retrieve the view model. If the function accesses any obsevables or returns
2970
+ // we call the function to retrieve the view model. If the function accesses any observables or returns
2657
2971
  // an observable, the dependency is tracked, and those observables can later cause the binding
2658
2972
  // context to be updated.
2659
2973
  var dataItemOrObservable = isFunc ? dataItemOrAccessor() : dataItemOrAccessor,
@@ -2735,7 +3049,7 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
2735
3049
  }
2736
3050
 
2737
3051
  // Extend the binding context hierarchy with a new view model object. If the parent context is watching
2738
- // any obsevables, the new child context will automatically get a dependency on the parent context.
3052
+ // any observables, the new child context will automatically get a dependency on the parent context.
2739
3053
  // But this does not mean that the $data value of the child context will also get updated. If the child
2740
3054
  // view model also depends on the parent view model, you must provide a function that returns the correct
2741
3055
  // view model on each update.
@@ -2929,7 +3243,7 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
2929
3243
  var bindingsUpdater = ko.dependentObservable(
2930
3244
  function() {
2931
3245
  bindings = sourceBindings ? sourceBindings(bindingContext, node) : getBindings.call(provider, node, bindingContext);
2932
- // Register a dependency on the binding context to support obsevable view models.
3246
+ // Register a dependency on the binding context to support observable view models.
2933
3247
  if (bindings && bindingContext._subscribable)
2934
3248
  bindingContext._subscribable();
2935
3249
  return bindings;
@@ -3107,7 +3421,7 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
3107
3421
  callback(cachedDefinition.definition);
3108
3422
  });
3109
3423
  } else {
3110
- setTimeout(function() { callback(cachedDefinition.definition); }, 0);
3424
+ ko.tasks.schedule(function() { callback(cachedDefinition.definition); });
3111
3425
  }
3112
3426
  } else {
3113
3427
  // Join the loading process that is already underway, or start a new one.
@@ -3140,19 +3454,19 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
3140
3454
  delete loadingSubscribablesCache[componentName];
3141
3455
 
3142
3456
  // For API consistency, all loads complete asynchronously. However we want to avoid
3143
- // adding an extra setTimeout if it's unnecessary (i.e., the completion is already
3144
- // async) since setTimeout(..., 0) still takes about 16ms or more on most browsers.
3457
+ // adding an extra task schedule if it's unnecessary (i.e., the completion is already
3458
+ // async).
3145
3459
  //
3146
- // You can bypass the 'always synchronous' feature by putting the synchronous:true
3460
+ // You can bypass the 'always asynchronous' feature by putting the synchronous:true
3147
3461
  // flag on your component configuration when you register it.
3148
3462
  if (completedAsync || isSynchronousComponent) {
3149
3463
  // Note that notifySubscribers ignores any dependencies read within the callback.
3150
3464
  // See comment in loaderRegistryBehaviors.js for reasoning
3151
3465
  subscribable['notifySubscribers'](definition);
3152
3466
  } else {
3153
- setTimeout(function() {
3467
+ ko.tasks.schedule(function() {
3154
3468
  subscribable['notifySubscribers'](definition);
3155
- }, 0);
3469
+ });
3156
3470
  }
3157
3471
  });
3158
3472
  completedAsync = true;
@@ -3257,16 +3571,16 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
3257
3571
  }
3258
3572
 
3259
3573
  defaultConfigRegistry[componentName] = config;
3260
- }
3574
+ };
3261
3575
 
3262
3576
  ko.components.isRegistered = function(componentName) {
3263
- return componentName in defaultConfigRegistry;
3264
- }
3577
+ return defaultConfigRegistry.hasOwnProperty(componentName);
3578
+ };
3265
3579
 
3266
3580
  ko.components.unregister = function(componentName) {
3267
3581
  delete defaultConfigRegistry[componentName];
3268
3582
  ko.components.clearCachedDefinition(componentName);
3269
- }
3583
+ };
3270
3584
 
3271
3585
  ko.components.defaultLoader = {
3272
3586
  'getConfig': function(componentName, callback) {
@@ -3464,7 +3778,12 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
3464
3778
  // you can for example map specific tagNames to components that are not preregistered.
3465
3779
  ko.components['getComponentNameForNode'] = function(node) {
3466
3780
  var tagNameLower = ko.utils.tagNameLower(node);
3467
- return ko.components.isRegistered(tagNameLower) && tagNameLower;
3781
+ if (ko.components.isRegistered(tagNameLower)) {
3782
+ // Try to determine that this node can be considered a *custom* element; see https://github.com/knockout/knockout/issues/1603
3783
+ if (tagNameLower.indexOf('-') != -1 || ('' + node) == "[object HTMLUnknownElement]" || (ko.utils.ieVersion <= 8 && node.tagName === tagNameLower)) {
3784
+ return tagNameLower;
3785
+ }
3786
+ }
3468
3787
  };
3469
3788
 
3470
3789
  ko.components.addBindingsForCustomElement = function(allBindings, node, bindingContext, valueAccessors) {
@@ -3581,7 +3900,7 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
3581
3900
  if (typeof currentViewModelDispose === 'function') {
3582
3901
  currentViewModelDispose.call(currentViewModel);
3583
3902
  }
3584
-
3903
+ currentViewModel = null;
3585
3904
  // Any in-flight loading operation is no longer relevant, so make sure we ignore its completion
3586
3905
  currentLoadingOperationId = null;
3587
3906
  },
@@ -3725,21 +4044,25 @@ ko.bindingHandlers['checked'] = {
3725
4044
  }
3726
4045
 
3727
4046
  var modelValue = ko.dependencyDetection.ignore(valueAccessor);
3728
- if (isValueArray) {
4047
+ if (valueIsArray) {
4048
+ var writableValue = rawValueIsNonArrayObservable ? modelValue.peek() : modelValue;
3729
4049
  if (oldElemValue !== elemValue) {
3730
4050
  // When we're responding to the checkedValue changing, and the element is
3731
4051
  // currently checked, replace the old elem value with the new elem value
3732
4052
  // in the model array.
3733
4053
  if (isChecked) {
3734
- ko.utils.addOrRemoveItem(modelValue, elemValue, true);
3735
- ko.utils.addOrRemoveItem(modelValue, oldElemValue, false);
4054
+ ko.utils.addOrRemoveItem(writableValue, elemValue, true);
4055
+ ko.utils.addOrRemoveItem(writableValue, oldElemValue, false);
3736
4056
  }
3737
4057
 
3738
4058
  oldElemValue = elemValue;
3739
4059
  } else {
3740
4060
  // When we're responding to the user having checked/unchecked a checkbox,
3741
4061
  // add/remove the element value to the model array.
3742
- ko.utils.addOrRemoveItem(modelValue, elemValue, isChecked);
4062
+ ko.utils.addOrRemoveItem(writableValue, elemValue, isChecked);
4063
+ }
4064
+ if (rawValueIsNonArrayObservable && ko.isWriteableObservable(modelValue)) {
4065
+ modelValue(writableValue);
3743
4066
  }
3744
4067
  } else {
3745
4068
  ko.expressionRewriting.writeValueToProperty(modelValue, allBindings, 'checked', elemValue, true);
@@ -3751,7 +4074,7 @@ ko.bindingHandlers['checked'] = {
3751
4074
  // It runs in response to changes in the bound (checked) value.
3752
4075
  var modelValue = ko.utils.unwrapObservable(valueAccessor());
3753
4076
 
3754
- if (isValueArray) {
4077
+ if (valueIsArray) {
3755
4078
  // When a checkbox is bound to an array, being checked represents its value being present in that array
3756
4079
  element.checked = ko.utils.arrayIndexOf(modelValue, checkedValue()) >= 0;
3757
4080
  } else if (isCheckbox) {
@@ -3771,9 +4094,11 @@ ko.bindingHandlers['checked'] = {
3771
4094
  return;
3772
4095
  }
3773
4096
 
3774
- var isValueArray = isCheckbox && (ko.utils.unwrapObservable(valueAccessor()) instanceof Array),
3775
- oldElemValue = isValueArray ? checkedValue() : undefined,
3776
- useCheckedValue = isRadio || isValueArray;
4097
+ var rawValue = valueAccessor(),
4098
+ valueIsArray = isCheckbox && (ko.utils.unwrapObservable(rawValue) instanceof Array),
4099
+ rawValueIsNonArrayObservable = !(valueIsArray && rawValue.push && rawValue.splice),
4100
+ oldElemValue = valueIsArray ? checkedValue() : undefined,
4101
+ useCheckedValue = isRadio || valueIsArray;
3777
4102
 
3778
4103
  // IE 6 won't allow radio buttons to be selected unless they have a name
3779
4104
  if (isRadio && !element.name)
@@ -3787,6 +4112,8 @@ ko.bindingHandlers['checked'] = {
3787
4112
 
3788
4113
  // The second responds to changes in the model value (the one associated with the checked binding)
3789
4114
  ko.computed(updateView, null, { disposeWhenNodeIsRemoved: element });
4115
+
4116
+ rawValue = undefined;
3790
4117
  }
3791
4118
  };
3792
4119
  ko.expressionRewriting.twoWayBindings['checked'] = true;
@@ -3807,7 +4134,7 @@ ko.bindingHandlers['css'] = {
3807
4134
  ko.utils.toggleDomNodeCssClass(element, className, shouldHaveClass);
3808
4135
  });
3809
4136
  } else {
3810
- value = String(value || ''); // Make sure we don't try to store or set a non-string value
4137
+ value = ko.utils.stringTrim(String(value || '')); // Make sure we don't try to store or set a non-string value
3811
4138
  ko.utils.toggleDomNodeCssClass(element, element[classesWrittenByBindingKey], false);
3812
4139
  element[classesWrittenByBindingKey] = value;
3813
4140
  ko.utils.toggleDomNodeCssClass(element, value, true);
@@ -3958,10 +4285,20 @@ ko.bindingHandlers['hasfocus'] = {
3958
4285
  ko.utils.registerEventHandler(element, "focusout", handleElementFocusOut); // For IE
3959
4286
  },
3960
4287
  'update': function(element, valueAccessor) {
3961
- var value = !!ko.utils.unwrapObservable(valueAccessor()); //force boolean to compare with last value
4288
+ var value = !!ko.utils.unwrapObservable(valueAccessor());
4289
+
3962
4290
  if (!element[hasfocusUpdatingProperty] && element[hasfocusLastValue] !== value) {
3963
4291
  value ? element.focus() : element.blur();
3964
- ko.dependencyDetection.ignore(ko.utils.triggerEvent, null, [element, value ? "focusin" : "focusout"]); // For IE, which doesn't reliably fire "focus" or "blur" events synchronously
4292
+
4293
+ // In IE, the blur method doesn't always cause the element to lose focus (for example, if the window is not in focus).
4294
+ // Setting focus to the body element does seem to be reliable in IE, but should only be used if we know that the current
4295
+ // element was focused already.
4296
+ if (!value && element[hasfocusLastValue]) {
4297
+ element.ownerDocument.body.focus();
4298
+ }
4299
+
4300
+ // For IE, which doesn't reliably fire "focus" or "blur" events synchronously
4301
+ ko.dependencyDetection.ignore(ko.utils.triggerEvent, null, [element, value ? "focusin" : "focusout"]);
3965
4302
  }
3966
4303
  }
3967
4304
  };
@@ -4206,13 +4543,19 @@ ko.bindingHandlers['selectedOptions'] = {
4206
4543
  if (ko.utils.tagNameLower(element) != "select")
4207
4544
  throw new Error("values binding applies only to SELECT elements");
4208
4545
 
4209
- var newValue = ko.utils.unwrapObservable(valueAccessor());
4546
+ var newValue = ko.utils.unwrapObservable(valueAccessor()),
4547
+ previousScrollTop = element.scrollTop;
4548
+
4210
4549
  if (newValue && typeof newValue.length == "number") {
4211
4550
  ko.utils.arrayForEach(element.getElementsByTagName("option"), function(node) {
4212
4551
  var isSelected = ko.utils.arrayIndexOf(newValue, ko.selectExtensions.readValue(node)) >= 0;
4213
- ko.utils.setOptionNodeSelectionState(node, isSelected);
4552
+ if (node.selected != isSelected) { // This check prevents flashing of the select element in IE
4553
+ ko.utils.setOptionNodeSelectionState(node, isSelected);
4554
+ }
4214
4555
  });
4215
4556
  }
4557
+
4558
+ element.scrollTop = previousScrollTop;
4216
4559
  }
4217
4560
  };
4218
4561
  ko.expressionRewriting.twoWayBindings['selectedOptions'] = true;
@@ -4331,10 +4674,14 @@ ko.bindingHandlers['textInput'] = {
4331
4674
  // such as rateLimit. Such updates, if not ignored, can cause keystrokes to be lost.
4332
4675
  elementValueBeforeEvent = element.value;
4333
4676
  var handler = DEBUG ? updateModel.bind(element, {type: event.type}) : updateModel;
4334
- timeoutHandle = setTimeout(handler, 4);
4677
+ timeoutHandle = ko.utils.setTimeout(handler, 4);
4335
4678
  }
4336
4679
  };
4337
4680
 
4681
+ // IE9 will mess up the DOM if you handle events synchronously which results in DOM changes (such as other bindings);
4682
+ // so we'll make sure all updates are asynchronous
4683
+ var ieUpdateModel = ko.utils.ieVersion == 9 ? deferUpdateModel : updateModel;
4684
+
4338
4685
  var updateView = function () {
4339
4686
  var modelValue = ko.utils.unwrapObservable(valueAccessor());
4340
4687
 
@@ -4343,7 +4690,7 @@ ko.bindingHandlers['textInput'] = {
4343
4690
  }
4344
4691
 
4345
4692
  if (elementValueBeforeEvent !== undefined && modelValue === elementValueBeforeEvent) {
4346
- setTimeout(updateView, 4);
4693
+ ko.utils.setTimeout(updateView, 4);
4347
4694
  return;
4348
4695
  }
4349
4696
 
@@ -4376,7 +4723,7 @@ ko.bindingHandlers['textInput'] = {
4376
4723
  // when using autocomplete, we'll use 'propertychange' for it also.
4377
4724
  onEvent('propertychange', function(event) {
4378
4725
  if (event.propertyName === 'value') {
4379
- updateModel(event);
4726
+ ieUpdateModel(event);
4380
4727
  }
4381
4728
  });
4382
4729
 
@@ -4393,7 +4740,7 @@ ko.bindingHandlers['textInput'] = {
4393
4740
  // out of the field, and cutting or deleting text using the context menu. 'selectionchange'
4394
4741
  // can detect all of those except dragging text out of the field, for which we use 'dragend'.
4395
4742
  // These are also needed in IE8 because of the bug described above.
4396
- registerForSelectionChangeEvent(element, updateModel); // 'selectionchange' covers cut, paste, drop, delete, etc.
4743
+ registerForSelectionChangeEvent(element, ieUpdateModel); // 'selectionchange' covers cut, paste, drop, delete, etc.
4397
4744
  onEvent('dragend', deferUpdateModel);
4398
4745
  }
4399
4746
  } else {
@@ -4506,7 +4853,7 @@ ko.bindingHandlers['value'] = {
4506
4853
  // techniques like rateLimit can trigger model changes at critical moments that will
4507
4854
  // override the user's inputs, causing keystrokes to be lost.
4508
4855
  elementValueBeforeEvent = ko.selectExtensions.readValue(element);
4509
- setTimeout(valueUpdateHandler, 0);
4856
+ ko.utils.setTimeout(valueUpdateHandler, 0);
4510
4857
  };
4511
4858
  eventName = eventName.substring("after".length);
4512
4859
  }
@@ -4518,7 +4865,7 @@ ko.bindingHandlers['value'] = {
4518
4865
  var elementValue = ko.selectExtensions.readValue(element);
4519
4866
 
4520
4867
  if (elementValueBeforeEvent !== null && newValue === elementValueBeforeEvent) {
4521
- setTimeout(updateFromModel, 0);
4868
+ ko.utils.setTimeout(updateFromModel, 0);
4522
4869
  return;
4523
4870
  }
4524
4871
 
@@ -4540,7 +4887,7 @@ ko.bindingHandlers['value'] = {
4540
4887
  // Workaround for IE6 bug: It won't reliably apply values to SELECT nodes during the same execution thread
4541
4888
  // right after you've changed the set of OPTION nodes on it. So for that node type, we'll schedule a second thread
4542
4889
  // to apply the value as well.
4543
- setTimeout(applyValueAction, 0);
4890
+ ko.utils.setTimeout(applyValueAction, 0);
4544
4891
  }
4545
4892
  } else {
4546
4893
  ko.selectExtensions.writeValue(element, newValue);
@@ -4732,14 +5079,29 @@ ko.exportSymbol('__tr_ambtns', ko.templateRewriting.applyMemoizedBindingsToNextS
4732
5079
 
4733
5080
  // ---- ko.templateSources.domElement -----
4734
5081
 
5082
+ // template types
5083
+ var templateScript = 1,
5084
+ templateTextArea = 2,
5085
+ templateTemplate = 3,
5086
+ templateElement = 4;
5087
+
4735
5088
  ko.templateSources.domElement = function(element) {
4736
5089
  this.domElement = element;
5090
+
5091
+ if (element) {
5092
+ var tagNameLower = ko.utils.tagNameLower(element);
5093
+ this.templateType =
5094
+ tagNameLower === "script" ? templateScript :
5095
+ tagNameLower === "textarea" ? templateTextArea :
5096
+ // For browsers with proper <template> element support, where the .content property gives a document fragment
5097
+ tagNameLower == "template" && element.content && element.content.nodeType === 11 ? templateTemplate :
5098
+ templateElement;
5099
+ }
4737
5100
  }
4738
5101
 
4739
5102
  ko.templateSources.domElement.prototype['text'] = function(/* valueToWrite */) {
4740
- var tagNameLower = ko.utils.tagNameLower(this.domElement),
4741
- elemContentsProperty = tagNameLower === "script" ? "text"
4742
- : tagNameLower === "textarea" ? "value"
5103
+ var elemContentsProperty = this.templateType === templateScript ? "text"
5104
+ : this.templateType === templateTextArea ? "value"
4743
5105
  : "innerHTML";
4744
5106
 
4745
5107
  if (arguments.length == 0) {
@@ -4762,12 +5124,34 @@ ko.exportSymbol('__tr_ambtns', ko.templateRewriting.applyMemoizedBindingsToNextS
4762
5124
  }
4763
5125
  };
4764
5126
 
5127
+ var templatesDomDataKey = ko.utils.domData.nextKey();
5128
+ function getTemplateDomData(element) {
5129
+ return ko.utils.domData.get(element, templatesDomDataKey) || {};
5130
+ }
5131
+ function setTemplateDomData(element, data) {
5132
+ ko.utils.domData.set(element, templatesDomDataKey, data);
5133
+ }
5134
+
5135
+ ko.templateSources.domElement.prototype['nodes'] = function(/* valueToWrite */) {
5136
+ var element = this.domElement;
5137
+ if (arguments.length == 0) {
5138
+ var templateData = getTemplateDomData(element),
5139
+ containerData = templateData.containerData;
5140
+ return containerData || (
5141
+ this.templateType === templateTemplate ? element.content :
5142
+ this.templateType === templateElement ? element :
5143
+ undefined);
5144
+ } else {
5145
+ var valueToWrite = arguments[0];
5146
+ setTemplateDomData(element, {containerData: valueToWrite});
5147
+ }
5148
+ };
5149
+
4765
5150
  // ---- ko.templateSources.anonymousTemplate -----
4766
5151
  // Anonymous templates are normally saved/retrieved as DOM nodes through "nodes".
4767
5152
  // For compatibility, you can also read "text"; it will be serialized from the nodes on demand.
4768
5153
  // Writing to "text" is still supported, but then the template data will not be available as DOM nodes.
4769
5154
 
4770
- var anonymousTemplatesDomDataKey = ko.utils.domData.nextKey();
4771
5155
  ko.templateSources.anonymousTemplate = function(element) {
4772
5156
  this.domElement = element;
4773
5157
  }
@@ -4775,22 +5159,13 @@ ko.exportSymbol('__tr_ambtns', ko.templateRewriting.applyMemoizedBindingsToNextS
4775
5159
  ko.templateSources.anonymousTemplate.prototype.constructor = ko.templateSources.anonymousTemplate;
4776
5160
  ko.templateSources.anonymousTemplate.prototype['text'] = function(/* valueToWrite */) {
4777
5161
  if (arguments.length == 0) {
4778
- var templateData = ko.utils.domData.get(this.domElement, anonymousTemplatesDomDataKey) || {};
5162
+ var templateData = getTemplateDomData(this.domElement);
4779
5163
  if (templateData.textData === undefined && templateData.containerData)
4780
5164
  templateData.textData = templateData.containerData.innerHTML;
4781
5165
  return templateData.textData;
4782
5166
  } else {
4783
5167
  var valueToWrite = arguments[0];
4784
- ko.utils.domData.set(this.domElement, anonymousTemplatesDomDataKey, {textData: valueToWrite});
4785
- }
4786
- };
4787
- ko.templateSources.domElement.prototype['nodes'] = function(/* valueToWrite */) {
4788
- if (arguments.length == 0) {
4789
- var templateData = ko.utils.domData.get(this.domElement, anonymousTemplatesDomDataKey) || {};
4790
- return templateData.containerData;
4791
- } else {
4792
- var valueToWrite = arguments[0];
4793
- ko.utils.domData.set(this.domElement, anonymousTemplatesDomDataKey, {containerData: valueToWrite});
5168
+ setTemplateDomData(this.domElement, {textData: valueToWrite});
4794
5169
  }
4795
5170
  };
4796
5171
 
@@ -5132,7 +5507,7 @@ ko.utils.compareArrays = (function () {
5132
5507
  oldArray = oldArray || [];
5133
5508
  newArray = newArray || [];
5134
5509
 
5135
- if (oldArray.length <= newArray.length)
5510
+ if (oldArray.length < newArray.length)
5136
5511
  return compareSmallArrayToBigArray(oldArray, newArray, statusNotInOld, statusNotInNew, options);
5137
5512
  else
5138
5513
  return compareSmallArrayToBigArray(newArray, oldArray, statusNotInNew, statusNotInOld, options);
@@ -5195,7 +5570,7 @@ ko.utils.compareArrays = (function () {
5195
5570
 
5196
5571
  // Set a limit on the number of consecutive non-matching comparisons; having it a multiple of
5197
5572
  // smlIndexMax keeps the time complexity of this algorithm linear.
5198
- ko.utils.findMovesInArrayComparison(notInSml, notInBig, smlIndexMax * 10);
5573
+ ko.utils.findMovesInArrayComparison(notInBig, notInSml, !options['dontLimitMoves'] && smlIndexMax * 10);
5199
5574
 
5200
5575
  return editScript.reverse();
5201
5576
  }
@@ -5236,7 +5611,8 @@ ko.exportSymbol('utils.compareArrays', ko.utils.compareArrays);
5236
5611
  return { mappedNodes : mappedNodes, dependentObservable : (dependentObservable.isActive() ? dependentObservable : undefined) };
5237
5612
  }
5238
5613
 
5239
- var lastMappingResultDomDataKey = ko.utils.domData.nextKey();
5614
+ var lastMappingResultDomDataKey = ko.utils.domData.nextKey(),
5615
+ deletedItemDummyValue = ko.utils.domData.nextKey();
5240
5616
 
5241
5617
  ko.utils.setDomNodeChildrenFromArrayMapping = function (domNode, array, mapping, options, callbackAfterAddingNodes) {
5242
5618
  // Compare the provided array against the previous one
@@ -5290,14 +5666,25 @@ ko.exportSymbol('utils.compareArrays', ko.utils.compareArrays);
5290
5666
  mapData = lastMappingResult[lastMappingResultIndex];
5291
5667
 
5292
5668
  // Stop tracking changes to the mapping for these nodes
5293
- if (mapData.dependentObservable)
5669
+ if (mapData.dependentObservable) {
5294
5670
  mapData.dependentObservable.dispose();
5671
+ mapData.dependentObservable = undefined;
5672
+ }
5295
5673
 
5296
5674
  // Queue these nodes for later removal
5297
- nodesToDelete.push.apply(nodesToDelete, ko.utils.fixUpContinuousNodeArray(mapData.mappedNodes, domNode));
5298
- if (options['beforeRemove']) {
5299
- itemsForBeforeRemoveCallbacks[i] = mapData;
5300
- itemsToProcess.push(mapData);
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
+ }
5684
+ }
5685
+ if (mapData) {
5686
+ nodesToDelete.push.apply(nodesToDelete, mapData.mappedNodes);
5687
+ }
5301
5688
  }
5302
5689
  }
5303
5690
  lastMappingResultIndex++;
@@ -5321,6 +5708,9 @@ ko.exportSymbol('utils.compareArrays', ko.utils.compareArrays);
5321
5708
  }
5322
5709
  }
5323
5710
 
5711
+ // Store a copy of the array items we just considered so we can difference it next time
5712
+ ko.utils.domData.set(domNode, lastMappingResultDomDataKey, newMappingResult);
5713
+
5324
5714
  // Call beforeMove first before any changes have been made to the DOM
5325
5715
  callCallback(options['beforeMove'], itemsForMoveCallbacks);
5326
5716
 
@@ -5353,12 +5743,18 @@ ko.exportSymbol('utils.compareArrays', ko.utils.compareArrays);
5353
5743
  // Perhaps we'll make that change in the future if this scenario becomes more common.
5354
5744
  callCallback(options['beforeRemove'], itemsForBeforeRemoveCallbacks);
5355
5745
 
5746
+ // Replace the stored values of deleted items with a dummy value. This provides two benefits: it marks this item
5747
+ // as already "removed" so we won't call beforeRemove for it again, and it ensures that the item won't match up
5748
+ // with an actual item in the array and appear as "retained" or "moved".
5749
+ for (i = 0; i < itemsForBeforeRemoveCallbacks.length; ++i) {
5750
+ if (itemsForBeforeRemoveCallbacks[i]) {
5751
+ itemsForBeforeRemoveCallbacks[i].arrayEntry = deletedItemDummyValue;
5752
+ }
5753
+ }
5754
+
5356
5755
  // Finally call afterMove and afterAdd callbacks
5357
5756
  callCallback(options['afterMove'], itemsForMoveCallbacks);
5358
5757
  callCallback(options['afterAdd'], itemsForAfterAddCallbacks);
5359
-
5360
- // Store a copy of the array items we just considered so we can difference it next time
5361
- ko.utils.domData.set(domNode, lastMappingResultDomDataKey, newMappingResult);
5362
5758
  }
5363
5759
  })();
5364
5760