Browse files

Merge branch 'master' into 564-split-bindings-into-own-files

  • Loading branch information...
2 parents ee10e4f + 39ddae8 commit 3daa496a800208d2d90e1f79d0777bcb0216ac02 @mbest mbest committed Aug 29, 2012
View
2 README.md
@@ -15,7 +15,7 @@ You can [download released versions of Knockout](https://github.com/SteveSanders
Or, if you prefer to build the source yourself, clone the repo from Github, and then:
-* To build on Linux or Mac, run `build/build-linux`
+* To build on Linux or Mac, run `cd build` then `./build-linux`
* To build on Windows, run `build\build-windows.bat`
##License
View
2 spec/defaultBindings/optionsBehaviors.js
@@ -29,7 +29,7 @@ describe('Binding: Options', {
var displayedText = ko.utils.arrayMap(testNode.childNodes[0].childNodes, function (node) { return node.innerHTML; });
var displayedValues = ko.utils.arrayMap(testNode.childNodes[0].childNodes, function (node) { return node.value; });
value_of(displayedText).should_be(["bob", "frank"]);
- value_of(displayedValues).should_be([6, 13]);
+ value_of(displayedValues).should_be(["6", "13"]);
},
'Should accept function in optionsText param to display subproperties of the model values': function() {
View
10 spec/defaultBindings/valueBehaviors.js
@@ -4,7 +4,7 @@ describe('Binding: Value', {
'Should assign the value to the node': function () {
testNode.innerHTML = "<input data-bind='value:123' />";
ko.applyBindings(null, testNode);
- value_of(testNode.childNodes[0].value).should_be(123);
+ value_of(testNode.childNodes[0].value).should_be("123");
},
'Should treat null values as empty strings': function () {
@@ -29,9 +29,9 @@ describe('Binding: Value', {
var myobservable = new ko.observable(123);
testNode.innerHTML = "<input data-bind='value:someProp' />";
ko.applyBindings({ someProp: myobservable }, testNode);
- value_of(testNode.childNodes[0].value).should_be(123);
+ value_of(testNode.childNodes[0].value).should_be("123");
myobservable(456);
- value_of(testNode.childNodes[0].value).should_be(456);
+ value_of(testNode.childNodes[0].value).should_be("456");
},
'For writeable observable values, should catch the node\'s onchange and write values back to the observable': function () {
@@ -47,11 +47,11 @@ describe('Binding: Value', {
var model = { modelProperty123: 456 };
testNode.innerHTML = "<input data-bind='value: modelProperty123' />";
ko.applyBindings(model, testNode);
- value_of(testNode.childNodes[0].value).should_be(456);
+ value_of(testNode.childNodes[0].value).should_be("456");
testNode.childNodes[0].value = 789;
ko.utils.triggerEvent(testNode.childNodes[0], "change");
- value_of(model.modelProperty123).should_be(789);
+ value_of(model.modelProperty123).should_be("789");
},
'Should be able to read and write to a property of an object returned by a function': function () {
View
44 spec/editDetectionBehaviors.js
@@ -24,10 +24,10 @@ describe('Compare Arrays', {
var compareResult = ko.utils.compareArrays(oldArray, newArray);
value_of(compareResult).should_be([
{ status: "retained", value: "A" },
- { status: "added", value: "A2" },
- { status: "added", value: "A3" },
+ { status: "added", value: "A2", index: 1 },
+ { status: "added", value: "A3", index: 2 },
{ status: "retained", value: "B" },
- { status: "added", value: "B2" }
+ { status: "added", value: "B2", index: 4 }
]);
},
@@ -36,10 +36,10 @@ describe('Compare Arrays', {
var newArray = ["B", "C", "E"];
var compareResult = ko.utils.compareArrays(oldArray, newArray);
value_of(compareResult).should_be([
- { status: "deleted", value: "A" },
+ { status: "deleted", value: "A", index: 0 },
{ status: "retained", value: "B" },
{ status: "retained", value: "C" },
- { status: "deleted", value: "D" },
+ { status: "deleted", value: "D", index: 3 },
{ status: "retained", value: "E" }
]);
},
@@ -49,13 +49,33 @@ describe('Compare Arrays', {
var newArray = [123, "A", "E", "C", "D"];
var compareResult = ko.utils.compareArrays(oldArray, newArray);
value_of(compareResult).should_be([
- { status: "added", value: 123 },
+ { status: "added", value: 123, index: 0 },
{ status: "retained", value: "A" },
- { status: "deleted", value: "B" },
- { status: "added", value: "E" },
+ { status: "deleted", value: "B", index: 1 },
+ { status: "added", value: "E", index: 2, moved: 4 },
{ status: "retained", value: "C" },
{ status: "retained", value: "D" },
- { status: "deleted", value: "E" }
+ { status: "deleted", value: "E", index: 4, moved: 2 }
+ ]);
+ },
+
+ 'Should recognize replaced array': function () {
+ var oldArray = ["A", "B", "C", "D", "E"];
+ var newArray = ["F", "G", "H", "I", "J"];
+ var compareResult = ko.utils.compareArrays(oldArray, newArray);
+ // the order of added and deleted doesn't really matter
+ compareResult.sort(function(a, b) { return a.status.localeCompare(b.status) });
+ value_of(compareResult).should_be([
+ { status: "added", value: "F", index: 0},
+ { status: "added", value: "G", index: 1},
+ { status: "added", value: "H", index: 2},
+ { status: "added", value: "I", index: 3},
+ { status: "added", value: "J", index: 4},
+ { status: "deleted", value: "A", index: 0},
+ { status: "deleted", value: "B", index: 1},
+ { status: "deleted", value: "C", index: 2},
+ { status: "deleted", value: "D", index: 3},
+ { status: "deleted", value: "E", index: 4}
]);
}
});
@@ -213,6 +233,12 @@ describe('Array to DOM node children mapping', {
value_of(countCallbackInvocations).should_be(mappingInvocations.length);
mappingInvocations = [], countCallbackInvocations = 0;
+ ko.utils.setDomNodeChildrenFromArrayMapping(testNode, ["C", "B", "A"], mapping, null, callback); // Move items
+ value_of(ko.utils.arrayMap(testNode.childNodes, function (x) { return x.innerHTML })).should_be(["C", "B", "A"]);
+ value_of(mappingInvocations).should_be([]);
+ value_of(countCallbackInvocations).should_be(mappingInvocations.length);
+
+ mappingInvocations = [], countCallbackInvocations = 0;
var observable = ko.observable(1);
ko.utils.setDomNodeChildrenFromArrayMapping(testNode, [observable, null, "B"], mapping, null, callback); // Add to beginning; delete from end
value_of(ko.utils.arrayMap(testNode.childNodes, function (x) { return x.innerHTML })).should_be(["1", "null", "B"]);
View
166 src/binding/editDetection/arrayToDomNodeChildren.js
@@ -10,8 +10,8 @@
// "callbackAfterAddingNodes" will be invoked after any "mapping"-generated nodes are inserted into the container node
// You can use this, for example, to activate bindings on those nodes.
- function fixUpNodesToBeRemoved(contiguousNodeArray) {
- // Before deleting or replacing a set of nodes that were previously outputted by the "map" function, we have to reconcile
+ function fixUpNodesToBeMovedOrRemoved(contiguousNodeArray) {
+ // Before moving, deleting, or replacing a set of nodes that were previously outputted by the "map" function, we have to reconcile
// them against what is in the DOM right now. It may be that some of the nodes have already been removed from the document,
// or that new nodes might have been inserted in the middle, for example by a binding. Also, there may previously have been
// leading comment nodes (created by rewritten string-based templates) that have since been removed during binding.
@@ -43,6 +43,7 @@
// (The following line replaces the contents of contiguousNodeArray with newContiguousSet)
Array.prototype.splice.apply(contiguousNodeArray, [0, contiguousNodeArray.length].concat(newContiguousSet));
}
+ return contiguousNodeArray;
}
function mapNodeAndRefreshWhenChanged(containerNode, mapping, valueToMap, callbackAfterAddingNodes, index) {
@@ -53,10 +54,9 @@
// On subsequent evaluations, just replace the previously-inserted DOM nodes
if (mappedNodes.length > 0) {
- fixUpNodesToBeRemoved(mappedNodes);
- ko.utils.replaceDomNodes(mappedNodes, newMappedNodes);
+ ko.utils.replaceDomNodes(fixUpNodesToBeMovedOrRemoved(mappedNodes), newMappedNodes);
if (callbackAfterAddingNodes)
- callbackAfterAddingNodes(valueToMap, newMappedNodes);
+ callbackAfterAddingNodes(valueToMap, newMappedNodes, index);
}
// Replace the contents of the mappedNodes array, thereby updating the record
@@ -81,96 +81,106 @@
// Build the new mapping result
var newMappingResult = [];
var lastMappingResultIndex = 0;
- var nodesToDelete = [];
var newMappingResultIndex = 0;
- var nodesAdded = [];
- var insertAfterNode = null;
- for (var i = 0, j = editScript.length; i < j; i++) {
- switch (editScript[i].status) {
- case "retained":
- // Just keep the information - don't touch the nodes
- var dataToRetain = lastMappingResult[lastMappingResultIndex];
- dataToRetain.indexObservable(newMappingResultIndex);
- newMappingResultIndex = newMappingResult.push(dataToRetain);
- if (dataToRetain.domNodes.length > 0)
- insertAfterNode = dataToRetain.domNodes[dataToRetain.domNodes.length - 1];
- lastMappingResultIndex++;
- break;
- case "deleted":
- // Stop tracking changes to the mapping for these nodes
- lastMappingResult[lastMappingResultIndex].dependentObservable.dispose();
-
- // Queue these nodes for later removal
- fixUpNodesToBeRemoved(lastMappingResult[lastMappingResultIndex].domNodes);
- ko.utils.arrayForEach(lastMappingResult[lastMappingResultIndex].domNodes, function (node) {
- nodesToDelete.push({
- element: node,
- index: i,
- value: editScript[i].value
+ var nodesToDelete = [];
+ var itemsToProcess = [];
+ var itemsForBeforeRemoveCallbacks = [];
+ var itemsForMoveCallbacks = [];
+ var itemsForAfterAddCallbacks = [];
+ var mapData;
+
+ function itemMovedOrRetained(editScriptIndex, oldPosition) {
+ mapData = lastMappingResult[oldPosition];
+ if (newMappingResultIndex !== oldPosition)
+ itemsForMoveCallbacks[editScriptIndex] = mapData;
+ // Since updating the index might change the nodes, do so before calling fixUpNodesToBeMovedOrRemoved
+ mapData.indexObservable(newMappingResultIndex++);
+ fixUpNodesToBeMovedOrRemoved(mapData.mappedNodes);
+ newMappingResult.push(mapData);
+ itemsToProcess.push(mapData);
+ }
+
+ function callCallback(callback, items) {
+ if (callback) {
+ for (var i = 0, n = items.length; i < n; i++) {
+ if (items[i]) {
+ ko.utils.arrayForEach(items[i].mappedNodes, function(node) {
+ callback(node, i, items[i].arrayEntry);
});
- insertAfterNode = node;
- });
+ }
+ }
+ }
+ }
+
+ for (var i = 0, editScriptItem, movedIndex; editScriptItem = editScript[i]; i++) {
+ movedIndex = editScriptItem['moved'];
+ switch (editScriptItem['status']) {
+ case "deleted":
+ if (movedIndex === undefined) {
+ mapData = lastMappingResult[lastMappingResultIndex];
+
+ // Stop tracking changes to the mapping for these nodes
+ mapData.dependentObservable.dispose();
+
+ // Queue these nodes for later removal
+ nodesToDelete.push.apply(nodesToDelete, fixUpNodesToBeMovedOrRemoved(mapData.mappedNodes));
+ if (options['beforeRemove']) {
+ itemsForBeforeRemoveCallbacks[i] = mapData;
+ itemsToProcess.push(mapData);
+ }
+ }
lastMappingResultIndex++;
break;
+ case "retained":
+ itemMovedOrRetained(i, lastMappingResultIndex++);
+ break;
+
case "added":
- var valueToMap = editScript[i].value;
- var indexObservable = ko.observable(newMappingResultIndex);
- var mapData = mapNodeAndRefreshWhenChanged(domNode, mapping, valueToMap, callbackAfterAddingNodes, indexObservable);
- var mappedNodes = mapData.mappedNodes;
-
- // On the first evaluation, insert the nodes at the current insertion point
- newMappingResultIndex = newMappingResult.push({
- arrayEntry: editScript[i].value,
- domNodes: mappedNodes,
- dependentObservable: mapData.dependentObservable,
- indexObservable: indexObservable
- });
- for (var nodeIndex = 0, nodeIndexMax = mappedNodes.length; nodeIndex < nodeIndexMax; nodeIndex++) {
- var node = mappedNodes[nodeIndex];
- nodesAdded.push({
- element: node,
- index: i,
- value: editScript[i].value
- });
- if (insertAfterNode == null) {
- // Insert "node" (the newly-created node) as domNode's first child
- ko.virtualElements.prepend(domNode, node);
- } else {
- // Insert "node" into "domNode" immediately after "insertAfterNode"
- ko.virtualElements.insertAfter(domNode, node, insertAfterNode);
- }
- insertAfterNode = node;
+ if (movedIndex !== undefined) {
+ itemMovedOrRetained(i, movedIndex);
+ } else {
+ mapData = { arrayEntry: editScriptItem['value'], indexObservable: ko.observable(newMappingResultIndex++) };
+ newMappingResult.push(mapData);
+ itemsToProcess.push(mapData);
+ if (!isFirstExecution)
+ itemsForAfterAddCallbacks[i] = mapData;
}
- if (callbackAfterAddingNodes)
- callbackAfterAddingNodes(valueToMap, mappedNodes, indexObservable);
break;
}
}
- ko.utils.arrayForEach(nodesToDelete, function (node) { ko.cleanNode(node.element) });
+ // Call beforeMove first before any changes have been made to the DOM
+ callCallback(options['beforeMove'], itemsForMoveCallbacks);
- var invokedBeforeRemoveCallback = false;
- if (!isFirstExecution) {
- if (options['afterAdd']) {
- for (var i = 0; i < nodesAdded.length; i++)
- options['afterAdd'](nodesAdded[i].element, nodesAdded[i].index, nodesAdded[i].value);
- }
- if (options['beforeRemove']) {
- for (var i = 0; i < nodesToDelete.length; i++)
- options['beforeRemove'](nodesToDelete[i].element, nodesToDelete[i].index, nodesToDelete[i].value);
- invokedBeforeRemoveCallback = true;
+ // Next remove nodes for deleted items; or call beforeRemove, which will remove them
+ ko.utils.arrayForEach(nodesToDelete, options['beforeRemove'] ? ko.cleanNode : ko.removeNode);
+ callCallback(options['beforeRemove'], itemsForBeforeRemoveCallbacks);
+
+ // Next add/reorder the remaining items (will include deleted items if there's a beforeRemove callback)
+ for (var i = 0, nextNode = ko.virtualElements.firstChild(domNode), lastNode, node; mapData = itemsToProcess[i]; i++) {
+ // Get nodes for newly added items
+ if (!mapData.mappedNodes)
+ ko.utils.extend(mapData, mapNodeAndRefreshWhenChanged(domNode, mapping, mapData.arrayEntry, callbackAfterAddingNodes, mapData.indexObservable));
+
+ // Put nodes in the right place if they aren't there already
+ for (var j = 0; node = mapData.mappedNodes[j]; nextNode = node.nextSibling, lastNode = node, j++) {
+ if (node !== nextNode)
+ ko.virtualElements.insertAfter(domNode, node, lastNode);
}
- }
- if (!invokedBeforeRemoveCallback && nodesToDelete.length) {
- for (var i = 0; i < nodesToDelete.length; i++) {
- var element = nodesToDelete[i].element;
- if (element.parentNode)
- element.parentNode.removeChild(element);
+
+ // Run the callbacks for newly added nodes (for example, to apply bindings, etc.)
+ if (!mapData.initialized && callbackAfterAddingNodes) {
+ callbackAfterAddingNodes(mapData.arrayEntry, mapData.mappedNodes, mapData.indexObservable);
+ mapData.initialized = true;
}
}
+ // Finally call afterMove and afterAdd callbacks
+ callCallback(options['afterMove'], itemsForMoveCallbacks);
+ callCallback(options['afterAdd'], itemsForAfterAddCallbacks);
+
// Store a copy of the array items we just considered so we can difference it next time
ko.utils.domData.set(domNode, lastMappingResultDomDataKey, newMappingResult);
}
View
138 src/binding/editDetection/compareArrays.js
@@ -1,83 +1,93 @@
-(function () {
+ko.utils.compareArrays = (function () {
+ var statusNotInOld = 'added', statusNotInNew = 'deleted';
+
// Simple calculation based on Levenshtein distance.
- function calculateEditDistanceMatrix(oldArray, newArray, maxAllowedDistance) {
- var distances = [];
- for (var i = 0; i <= newArray.length; i++)
- distances[i] = [];
+ function compareArrays(oldArray, newArray, dontLimitMoves) {
+ oldArray = oldArray || [];
+ newArray = newArray || [];
- // Top row - transform old array into empty array via deletions
- for (var i = 0, j = Math.min(oldArray.length, maxAllowedDistance); i <= j; i++)
- distances[0][i] = i;
+ if (oldArray.length <= newArray.length)
+ return compareSmallArrayToBigArray(oldArray, newArray, statusNotInOld, statusNotInNew, dontLimitMoves);
+ else
+ return compareSmallArrayToBigArray(newArray, oldArray, statusNotInNew, statusNotInOld, dontLimitMoves);
+ }
- // Left row - transform empty array into new array via additions
- for (var i = 1, j = Math.min(newArray.length, maxAllowedDistance); i <= j; i++) {
- distances[i][0] = i;
- }
+ function compareSmallArrayToBigArray(smlArray, bigArray, statusNotInSml, statusNotInBig, dontLimitMoves) {
+ var myMin = Math.min,
+ myMax = Math.max,
+ editDistanceMatrix = [],
+ smlIndex, smlIndexMax = smlArray.length,
+ bigIndex, bigIndexMax = bigArray.length,
+ compareRange = (bigIndexMax - smlIndexMax) || 1,
+ maxDistance = smlIndexMax + bigIndexMax + 1,
+ thisRow, lastRow,
+ bigIndexMaxForRow, bigIndexMinForRow;
- // Fill out the body of the array
- var oldIndex, oldIndexMax = oldArray.length, newIndex, newIndexMax = newArray.length;
- var distanceViaAddition, distanceViaDeletion;
- for (oldIndex = 1; oldIndex <= oldIndexMax; oldIndex++) {
- var newIndexMinForRow = Math.max(1, oldIndex - maxAllowedDistance);
- var newIndexMaxForRow = Math.min(newIndexMax, oldIndex + maxAllowedDistance);
- for (newIndex = newIndexMinForRow; newIndex <= newIndexMaxForRow; newIndex++) {
- if (oldArray[oldIndex - 1] === newArray[newIndex - 1])
- distances[newIndex][oldIndex] = distances[newIndex - 1][oldIndex - 1];
+ for (smlIndex = 0; smlIndex <= smlIndexMax; smlIndex++) {
+ lastRow = thisRow;
+ editDistanceMatrix.push(thisRow = []);
+ bigIndexMaxForRow = myMin(bigIndexMax, smlIndex + compareRange);
+ bigIndexMinForRow = myMax(0, smlIndex - 1);
+ for (bigIndex = bigIndexMinForRow; bigIndex <= bigIndexMaxForRow; bigIndex++) {
+ if (!bigIndex)
+ thisRow[bigIndex] = smlIndex + 1;
+ else if (!smlIndex) // Top row - transform empty array into new array via additions
+ thisRow[bigIndex] = bigIndex + 1;
+ else if (smlArray[smlIndex - 1] === bigArray[bigIndex - 1])
+ thisRow[bigIndex] = lastRow[bigIndex - 1]; // copy value (no edit)
else {
- var northDistance = distances[newIndex - 1][oldIndex] === undefined ? Number.MAX_VALUE : distances[newIndex - 1][oldIndex] + 1;
- var westDistance = distances[newIndex][oldIndex - 1] === undefined ? Number.MAX_VALUE : distances[newIndex][oldIndex - 1] + 1;
- distances[newIndex][oldIndex] = Math.min(northDistance, westDistance);
+ var northDistance = lastRow[bigIndex] || maxDistance; // not in big (deletion)
+ var westDistance = thisRow[bigIndex - 1] || maxDistance; // not in small (addition)
+ thisRow[bigIndex] = myMin(northDistance, westDistance) + 1;
}
}
}
- return distances;
- }
-
- function findEditScriptFromEditDistanceMatrix(editDistanceMatrix, oldArray, newArray) {
- var oldIndex = oldArray.length;
- var newIndex = newArray.length;
- var editScript = [];
- var maxDistance = editDistanceMatrix[newIndex][oldIndex];
- if (maxDistance === undefined)
- return null; // maxAllowedDistance must be too small
- while ((oldIndex > 0) || (newIndex > 0)) {
- var me = editDistanceMatrix[newIndex][oldIndex];
- var distanceViaAdd = (newIndex > 0) ? editDistanceMatrix[newIndex - 1][oldIndex] : maxDistance + 1;
- var distanceViaDelete = (oldIndex > 0) ? editDistanceMatrix[newIndex][oldIndex - 1] : maxDistance + 1;
- var distanceViaRetain = (newIndex > 0) && (oldIndex > 0) ? editDistanceMatrix[newIndex - 1][oldIndex - 1] : maxDistance + 1;
- if ((distanceViaAdd === undefined) || (distanceViaAdd < me - 1)) distanceViaAdd = maxDistance + 1;
- if ((distanceViaDelete === undefined) || (distanceViaDelete < me - 1)) distanceViaDelete = maxDistance + 1;
- if (distanceViaRetain < me - 1) distanceViaRetain = maxDistance + 1;
-
- if ((distanceViaAdd <= distanceViaDelete) && (distanceViaAdd < distanceViaRetain)) {
- editScript.push({ status: "added", value: newArray[newIndex - 1] });
- newIndex--;
- } else if ((distanceViaDelete < distanceViaAdd) && (distanceViaDelete < distanceViaRetain)) {
- editScript.push({ status: "deleted", value: oldArray[oldIndex - 1] });
- oldIndex--;
+ var editScript = [], meMinusOne, notInSml = [], notInBig = [];
+ for (smlIndex = smlIndexMax, bigIndex = bigIndexMax; smlIndex || bigIndex;) {
+ meMinusOne = editDistanceMatrix[smlIndex][bigIndex] - 1;
+ if (bigIndex && meMinusOne === editDistanceMatrix[smlIndex][bigIndex-1]) {
+ notInSml.push(editScript[editScript.length] = { // added
+ 'status': statusNotInSml,
+ 'value': bigArray[--bigIndex],
+ 'index': bigIndex });
+ } else if (smlIndex && meMinusOne === editDistanceMatrix[smlIndex - 1][bigIndex]) {
+ notInBig.push(editScript[editScript.length] = { // deleted
+ 'status': statusNotInBig,
+ 'value': smlArray[--smlIndex],
+ 'index': smlIndex });
} else {
- editScript.push({ status: "retained", value: oldArray[oldIndex - 1] });
- newIndex--;
- oldIndex--;
+ editScript.push({
+ 'status': "retained",
+ 'value': bigArray[--bigIndex] });
+ --smlIndex;
+ }
+ }
+
+ if (notInSml.length && notInBig.length) {
+ // Set a limit on the number of consecutive non-matching comparisons; having it a multiple of
+ // smlIndexMax keeps the time complexity of this algorithm linear.
+ var limitFailedCompares = smlIndexMax * 10, failedCompares,
+ a, d, notInSmlItem, notInBigItem;
+ // Go through the items that have been added and deleted and try to find matches between them.
+ for (failedCompares = a = 0; (dontLimitMoves || failedCompares < limitFailedCompares) && (notInSmlItem = notInSml[a]); a++) {
+ for (d = 0; notInBigItem = notInBig[d]; d++) {
+ if (notInSmlItem['value'] === notInBigItem['value']) {
+ notInSmlItem['moved'] = notInBigItem['index'];
+ notInBigItem['moved'] = notInSmlItem['index'];
+ notInBig.splice(d,1); // This item is marked as moved; so remove it from notInBig list
+ failedCompares = d = 0; // Reset failed compares count because we're checking for consecutive failures
+ break;
+ }
+ }
+ failedCompares += d;
}
}
return editScript.reverse();
}
- ko.utils.compareArrays = function (oldArray, newArray, maxEditsToConsider) {
- if (maxEditsToConsider === undefined) {
- return ko.utils.compareArrays(oldArray, newArray, 1) // First consider likely case where there is at most one edit (very fast)
- || ko.utils.compareArrays(oldArray, newArray, 10) // If that fails, account for a fair number of changes while still being fast
- || ko.utils.compareArrays(oldArray, newArray, Number.MAX_VALUE); // Ultimately give the right answer, even though it may take a long time
- } else {
- oldArray = oldArray || [];
- newArray = newArray || [];
- var editDistanceMatrix = calculateEditDistanceMatrix(oldArray, newArray, maxEditsToConsider);
- return findEditScriptFromEditDistanceMatrix(editDistanceMatrix, oldArray, newArray);
- }
- };
+ return compareArrays;
})();
ko.exportSymbol('utils.compareArrays', ko.utils.compareArrays);
View
4 src/virtualElements.js
@@ -118,7 +118,9 @@
},
insertAfter: function(containerNode, nodeToInsert, insertAfterNode) {
- if (!isStartComment(containerNode)) {
+ if (!insertAfterNode) {
+ ko.virtualElements.prepend(containerNode, nodeToInsert);
+ } else if (!isStartComment(containerNode)) {
// Insert after insertion point
if (insertAfterNode.nextSibling)
containerNode.insertBefore(nodeToInsert, insertAfterNode.nextSibling);

0 comments on commit 3daa496

Please sign in to comment.