More Optional chaining in Knockout.js

This commit is contained in:
the-djmaze 2022-09-13 22:00:31 +02:00
parent d18f93d87f
commit a566f6ed5a
10 changed files with 391 additions and 476 deletions

View file

@ -49,55 +49,57 @@
array = [array];
options = options || {};
var lastMappingResult = ko.utils.domData.get(domNode, lastMappingResultDomDataKey);
var isFirstExecution = !lastMappingResult;
var lastMappingResult = ko.utils.domData.get(domNode, lastMappingResultDomDataKey),
isFirstExecution = !lastMappingResult,
// Build the new mapping result
var newMappingResult = [];
var lastMappingResultIndex = 0;
var currentArrayIndex = 0;
// Build the new mapping result
newMappingResult = [],
lastMappingResultIndex = 0,
currentArrayIndex = 0,
var nodesToDelete = [];
var itemsToMoveFirstIndexes = [];
var itemsForBeforeRemoveCallbacks = [];
var mapData;
var countWaitingForRemove = 0;
nodesToDelete = [],
itemsToMoveFirstIndexes = [],
itemsForBeforeRemoveCallbacks = [],
mapData,
countWaitingForRemove = 0,
function itemAdded(value) {
mapData = { arrayEntry: value, indexObservable: ko.observable(currentArrayIndex++) };
newMappingResult.push(mapData);
}
itemAdded = value => {
mapData = { arrayEntry: value, indexObservable: ko.observable(currentArrayIndex++) };
newMappingResult.push(mapData);
},
function itemMovedOrRetained(oldPosition) {
mapData = lastMappingResult[oldPosition];
// Since updating the index might change the nodes, do so before calling fixUpContinuousNodeArray
mapData.indexObservable(currentArrayIndex++);
ko.utils.fixUpContinuousNodeArray(mapData.mappedNodes, domNode);
newMappingResult.push(mapData);
}
itemMovedOrRetained = oldPosition => {
mapData = lastMappingResult[oldPosition];
// Since updating the index might change the nodes, do so before calling fixUpContinuousNodeArray
mapData.indexObservable(currentArrayIndex++);
ko.utils.fixUpContinuousNodeArray(mapData.mappedNodes, domNode);
newMappingResult.push(mapData);
},
function callCallback(callback, items) {
if (callback) {
for (var i = 0, n = items.length; i < n; i++) {
items[i] && items[i].mappedNodes.forEach(node => callback(node, i, items[i].arrayEntry));
callCallback = (callback, items) => {
if (callback) {
for (var i = 0, n = items.length; i < n; i++) {
items[i] && items[i].mappedNodes.forEach(node => callback(node, i, items[i].arrayEntry));
}
}
}
}
};
if (isFirstExecution) {
array.forEach(itemAdded);
} else {
if (!editScript || (lastMappingResult && lastMappingResult['_countWaitingForRemove'])) {
// Compare the provided array against the previous one
var lastArray = Array.prototype.map.call(lastMappingResult, x => x.arrayEntry),
compareOptions = {
editScript = ko.utils.compareArrays(
Array.prototype.map.call(lastMappingResult, x => x.arrayEntry),
array,
{
'dontLimitMoves': options['dontLimitMoves'],
'sparse': true
};
editScript = ko.utils.compareArrays(lastArray, array, compareOptions);
});
}
for (let i = 0, editScriptItem, movedIndex, itemIndex; editScriptItem = editScript[i]; i++) {
let movedIndex, itemIndex;
editScript.forEach(editScriptItem => {
movedIndex = editScriptItem['moved'];
itemIndex = editScriptItem['index'];
switch (editScriptItem['status']) {
@ -145,7 +147,7 @@
}
break;
}
}
});
while (currentArrayIndex < array.length) {
itemMovedOrRetained(lastMappingResultIndex++);
@ -162,7 +164,11 @@
// Next remove nodes for deleted items (or just clean if there's a beforeRemove callback)
nodesToDelete.forEach(options['beforeRemove'] ? ko.cleanNode : ko.removeNode);
var i, j, lastNode, nodeToInsert, mappedNodes, activeElement;
var i, lastNode, mappedNodes, activeElement,
insertNode = nodeToInsert => {
ko.virtualElements.insertAfter(domNode, nodeToInsert, lastNode);
lastNode = nodeToInsert;
};
// Since most browsers remove the focus from an element when it's moved to another location,
// save the focused element and try to restore it later.
@ -173,27 +179,24 @@
while ((i = itemsToMoveFirstIndexes.shift()) != undefined) {
mapData = newMappingResult[i];
for (lastNode = undefined; i; ) {
if ((mappedNodes = newMappingResult[--i].mappedNodes) && mappedNodes.length) {
mappedNodes = newMappingResult[--i].mappedNodes;
if (mappedNodes?.length) {
lastNode = mappedNodes[mappedNodes.length - 1];
break;
}
}
for (j = 0; nodeToInsert = mapData.mappedNodes[j]; lastNode = nodeToInsert, j++) {
ko.virtualElements.insertAfter(domNode, nodeToInsert, lastNode);
}
mapData.mappedNodes.forEach(insertNode);
}
}
// Next add/reorder the remaining items (will include deleted items if there's a beforeRemove callback)
for (i = 0; mapData = newMappingResult[i]; i++) {
newMappingResult.forEach(mapData => {
// Get nodes for newly added items
if (!mapData.mappedNodes)
ko.utils.extend(mapData, mapNodeAndRefreshWhenChanged(domNode, mapping, mapData.arrayEntry, callbackAfterAddingNodes, mapData.indexObservable));
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 (j = 0; nodeToInsert = mapData.mappedNodes[j]; lastNode = nodeToInsert, j++) {
ko.virtualElements.insertAfter(domNode, nodeToInsert, lastNode);
}
mapData.mappedNodes.forEach(insertNode);
// Run the callbacks for newly added nodes (for example, to apply bindings, etc.)
if (!mapData.initialized && callbackAfterAddingNodes) {
@ -201,11 +204,11 @@
mapData.initialized = true;
lastNode = mapData.mappedNodes[mapData.mappedNodes.length - 1]; // get the last node again since it may have been changed by a preprocessor
}
}
});
// Restore the focused element if it had lost focus
if (activeElement && domNode.ownerDocument.activeElement != activeElement) {
activeElement.focus();
if (domNode.ownerDocument.activeElement != activeElement) {
activeElement?.focus();
}
// If there's a beforeRemove callback, call it after reordering.
@ -218,10 +221,6 @@
// Replace the stored values of deleted items with a dummy value. This provides two benefits: it marks this item
// as already "removed" so we won't call beforeRemove for it again, and it ensures that the item won't match up
// with an actual item in the array and appear as "retained" or "moved".
for (i = 0; i < itemsForBeforeRemoveCallbacks.length; ++i) {
if (itemsForBeforeRemoveCallbacks[i]) {
itemsForBeforeRemoveCallbacks[i].arrayEntry = deletedItemDummyValue;
}
}
itemsForBeforeRemoveCallbacks.forEach(callback => callback && (callback.arrayEntry = deletedItemDummyValue));
}
})();

View file

@ -1,20 +1,19 @@
// Go through the items that have been added and deleted and try to find matches between them.
ko.utils.findMovesInArrayComparison = (left, right, limitFailedCompares) => {
if (left.length && right.length) {
var failedCompares, l, r, leftItem, rightItem;
for (failedCompares = l = 0; (!limitFailedCompares || failedCompares < limitFailedCompares) && (leftItem = left[l]); ++l) {
for (r = 0; rightItem = right[r]; ++r) {
if (leftItem['value'] === rightItem['value']) {
leftItem['moved'] = rightItem['index'];
rightItem['moved'] = leftItem['index'];
right.splice(r, 1); // This item is marked as moved; so remove it from right list
failedCompares = r = 0; // Reset failed compares count because we're checking for consecutive failures
break;
}
}
failedCompares += r;
var failedCompares = 0, r, l = right.length;
l && left.every(leftItem => {
r = right.findIndex(rightItem => leftItem['value'] === rightItem['value']);
if (r >= 0) {
leftItem['moved'] = right[r]['index'];
right[r]['moved'] = leftItem['index'];
right.splice(r, 1); // This item is marked as moved; so remove it from right list
// right[r] = null;
failedCompares = r = 0; // Reset failed compares count because we're checking for consecutive failures
--l;
}
}
failedCompares += l;
return l && (!limitFailedCompares || failedCompares < limitFailedCompares);
});
};
ko.utils.compareArrays = (() => {
@ -24,15 +23,15 @@ ko.utils.compareArrays = (() => {
var myMin = Math.min,
myMax = Math.max,
editDistanceMatrix = [],
smlIndex, smlIndexMax = smlArray.length,
smlIndex = -1, smlIndexMax = smlArray.length,
bigIndex, bigIndexMax = bigArray.length,
compareRange = (bigIndexMax - smlIndexMax) || 1,
maxDistance = smlIndexMax + bigIndexMax + 1,
thisRow, lastRow,
thisRow, prevRow,
bigIndexMaxForRow, bigIndexMinForRow;
for (smlIndex = 0; smlIndex <= smlIndexMax; smlIndex++) {
lastRow = thisRow;
while (++smlIndex <= smlIndexMax) {
prevRow = thisRow;
editDistanceMatrix.push(thisRow = []);
bigIndexMaxForRow = myMin(bigIndexMax, smlIndex + compareRange);
bigIndexMinForRow = myMax(0, smlIndex - 1);
@ -42,9 +41,9 @@ ko.utils.compareArrays = (() => {
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)
thisRow[bigIndex] = prevRow[bigIndex - 1]; // copy value (no edit)
else {
var northDistance = lastRow[bigIndex] || maxDistance; // not in big (deletion)
var northDistance = prevRow[bigIndex] || maxDistance; // not in big (deletion)
var westDistance = thisRow[bigIndex - 1] || maxDistance; // not in small (addition)
thisRow[bigIndex] = myMin(northDistance, westDistance) + 1;
}
@ -52,7 +51,9 @@ ko.utils.compareArrays = (() => {
}
var editScript = [], meMinusOne, notInSml = [], notInBig = [];
for (smlIndex = smlIndexMax, bigIndex = bigIndexMax; smlIndex || bigIndex;) {
smlIndex = smlIndexMax;
bigIndex = bigIndexMax
while (smlIndex || bigIndex) {
meMinusOne = editDistanceMatrix[smlIndex][bigIndex] - 1;
if (bigIndex && meMinusOne === editDistanceMatrix[smlIndex][bigIndex-1]) {
notInSml.push(editScript[editScript.length] = { // added