UNPKG

hellojs-xiaotian

Version:

A clientside Javascript library for standardizing requests to OAuth2 web services (and OAuth1 - with a shim)

181 lines (155 loc) 9.86 kB
(function () { // Objective: // * Given an input array, a container DOM node, and a function from array elements to arrays of DOM nodes, // map the array elements to arrays of DOM nodes, concatenate together all these arrays, and use them to populate the container DOM node // * Next time we're given the same combination of things (with the array possibly having mutated), update the container DOM node // so that its children is again the concatenation of the mappings of the array elements, but don't re-map any array elements that we // previously mapped - retain those nodes, and just insert/delete other ones // "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 mapNodeAndRefreshWhenChanged(containerNode, mapping, valueToMap, callbackAfterAddingNodes, index) { // Map this array value inside a dependentObservable so we re-map when any dependency changes var mappedNodes = []; var dependentObservable = ko.dependentObservable(function() { var newMappedNodes = mapping(valueToMap, index, ko.utils.fixUpContinuousNodeArray(mappedNodes, containerNode)) || []; // On subsequent evaluations, just replace the previously-inserted DOM nodes if (mappedNodes.length > 0) { ko.utils.replaceDomNodes(mappedNodes, newMappedNodes); if (callbackAfterAddingNodes) ko.dependencyDetection.ignore(callbackAfterAddingNodes, null, [valueToMap, newMappedNodes, index]); } // Replace the contents of the mappedNodes array, thereby updating the record // of which nodes would be deleted if valueToMap was itself later removed mappedNodes.length = 0; ko.utils.arrayPushAll(mappedNodes, newMappedNodes); }, null, { disposeWhenNodeIsRemoved: containerNode, disposeWhen: function() { return !ko.utils.anyDomNodeIsAttachedToDocument(mappedNodes); } }); return { mappedNodes : mappedNodes, dependentObservable : (dependentObservable.isActive() ? dependentObservable : undefined) }; } var lastMappingResultDomDataKey = ko.utils.domData.nextKey(), deletedItemDummyValue = ko.utils.domData.nextKey(); ko.utils.setDomNodeChildrenFromArrayMapping = function (domNode, array, mapping, options, callbackAfterAddingNodes) { // Compare the provided array against the previous one array = array || []; options = options || {}; var isFirstExecution = ko.utils.domData.get(domNode, lastMappingResultDomDataKey) === undefined; var lastMappingResult = ko.utils.domData.get(domNode, lastMappingResultDomDataKey) || []; var lastArray = ko.utils.arrayMap(lastMappingResult, function (x) { return x.arrayEntry; }); var editScript = ko.utils.compareArrays(lastArray, array, options['dontLimitMoves']); // Build the new mapping result var newMappingResult = []; var lastMappingResultIndex = 0; var newMappingResultIndex = 0; 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 fixUpContinuousNodeArray mapData.indexObservable(newMappingResultIndex++); ko.utils.fixUpContinuousNodeArray(mapData.mappedNodes, domNode); 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); }); } } } } 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 if (mapData.dependentObservable) { mapData.dependentObservable.dispose(); mapData.dependentObservable = undefined; } // Queue these nodes for later removal if (ko.utils.fixUpContinuousNodeArray(mapData.mappedNodes, domNode).length) { if (options['beforeRemove']) { newMappingResult.push(mapData); itemsToProcess.push(mapData); if (mapData.arrayEntry === deletedItemDummyValue) { mapData = null; } else { itemsForBeforeRemoveCallbacks[i] = mapData; } } if (mapData) { nodesToDelete.push.apply(nodesToDelete, mapData.mappedNodes); } } } lastMappingResultIndex++; break; case "retained": itemMovedOrRetained(i, lastMappingResultIndex++); break; case "added": 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; } break; } } // Store a copy of the array items we just considered so we can difference it next time ko.utils.domData.set(domNode, lastMappingResultDomDataKey, newMappingResult); // Call beforeMove first before any changes have been made to the DOM callCallback(options['beforeMove'], itemsForMoveCallbacks); // Next remove nodes for deleted items (or just clean if there's a beforeRemove callback) ko.utils.arrayForEach(nodesToDelete, options['beforeRemove'] ? ko.cleanNode : ko.removeNode); // 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); } // 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; } } // If there's a beforeRemove callback, call it after reordering. // Note that we assume that the beforeRemove callback will usually be used to remove the nodes using // some sort of animation, which is why we first reorder the nodes that will be removed. If the // callback instead removes the nodes right away, it would be more efficient to skip reordering them. // Perhaps we'll make that change in the future if this scenario becomes more common. callCallback(options['beforeRemove'], itemsForBeforeRemoveCallbacks); // 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; } } // Finally call afterMove and afterAdd callbacks callCallback(options['afterMove'], itemsForMoveCallbacks); callCallback(options['afterAdd'], itemsForAfterAddCallbacks); } })(); ko.exportSymbol('utils.setDomNodeChildrenFromArrayMapping', ko.utils.setDomNodeChildrenFromArrayMapping);