Пример #1
0
export function setDomNodeChildrenFromArrayMapping(domNode, array, mapping, options, callbackAfterAddingNodes) {
    // Compare the provided array against the previous one
    array = array || [];
    options = options || {};
    var isFirstExecution = domData.get(domNode, lastMappingResultDomDataKey) === undefined;
    var lastMappingResult = domData.get(domNode, lastMappingResultDomDataKey) || [];
    var lastArray = arrayMap(lastMappingResult, function (x) { return x.arrayEntry; });
    var editScript = 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++);
        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]) {
                    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 (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: 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
    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)
    arrayForEach(nodesToDelete, options['beforeRemove'] ? cleanNode : removeNode);

    // Next add/reorder the remaining items (will include deleted items if there's a beforeRemove callback)
    i = 0;
    for (var nextNode = virtualElements.firstChild(domNode), lastNode, node; mapData = itemsToProcess[i]; i++) {
        // Get nodes for newly added items
        if (!mapData.mappedNodes)
            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)
                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);
}
Пример #2
0
        // On subsequent evaluations, just replace the previously-inserted DOM nodes
        if (mappedNodes.length > 0) {
            replaceDomNodes(mappedNodes, newMappedNodes);
            if (callbackAfterAddingNodes)
                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;
        arrayPushAll(mappedNodes, newMappedNodes);
    }, null, { disposeWhenNodeIsRemoved: containerNode, disposeWhen: function() { return !anyDomNodeIsAttachedToDocument(mappedNodes); } });
    return { mappedNodes : mappedNodes, dependentObservable : (dependentObservable.isActive() ? dependentObservable : undefined) };
}

var lastMappingResultDomDataKey = domData.nextKey(),
    deletedItemDummyValue = domData.nextKey();

export function setDomNodeChildrenFromArrayMapping(domNode, array, mapping, options, callbackAfterAddingNodes) {
    // Compare the provided array against the previous one
    array = array || [];
    options = options || {};
    var isFirstExecution = domData.get(domNode, lastMappingResultDomDataKey) === undefined;
    var lastMappingResult = domData.get(domNode, lastMappingResultDomDataKey) || [];
    var lastArray = arrayMap(lastMappingResult, function (x) { return x.arrayEntry; });
    var editScript = compareArrays(lastArray, array, options['dontLimitMoves']);

    // Build the new mapping result
    var newMappingResult = [];
    var lastMappingResultIndex = 0;
    var newMappingResultIndex = 0;