ko.utils.domNodeDisposal = new (function () { var domDataKey = ko.utils.domData.nextKey(); var cleanableNodeTypes = { 1: true, 8: true, 9: true }; // Element, Comment, Document var cleanableNodeTypesWithDescendants = { 1: true, 9: true }; // Element, Document function getDisposeCallbacksCollection(node, createIfNotFound) { var allDisposeCallbacks = ko.utils.domData.get(node, domDataKey); if ((allDisposeCallbacks === undefined) && createIfNotFound) { allDisposeCallbacks = []; ko.utils.domData.set(node, domDataKey, allDisposeCallbacks); } return allDisposeCallbacks; } function destroyCallbacksCollection(node) { ko.utils.domData.set(node, domDataKey, undefined); } function cleanSingleNode(node) { // Run all the dispose callbacks var callbacks = getDisposeCallbacksCollection(node, false); if (callbacks) { callbacks = callbacks.slice(0); // Clone, as the array may be modified during iteration (typically, callbacks will remove themselves) for (var i = 0; i < callbacks.length; i++) callbacks[i](node); } // Erase the DOM data ko.utils.domData.clear(node); // Clear any immediate-child comment nodes, as these wouldn't have been found by // node.getElementsByTagName("*") in cleanNode() (comment nodes aren't elements) if (cleanableNodeTypesWithDescendants[node.nodeType]) { cleanNodesInList(node.childNodes, true/*onlyComments*/); } } function cleanNodesInList(nodeList, onlyComments) { var cleanedNodes = [], lastCleanedNode; for (var i = 0; i < nodeList.length; i++) { if (!onlyComments || nodeList[i].nodeType === 8) { cleanSingleNode(cleanedNodes[cleanedNodes.length] = lastCleanedNode = nodeList[i]); if (nodeList[i] !== lastCleanedNode) { while (i-- && !cleanedNodes.includes(nodeList[i])); } } } } return { addDisposeCallback : (node, callback) => { if (typeof callback != "function") throw new Error("Callback must be a function"); getDisposeCallbacksCollection(node, true).push(callback); }, removeDisposeCallback : (node, callback) => { var callbacksCollection = getDisposeCallbacksCollection(node, false); if (callbacksCollection) { ko.utils.arrayRemoveItem(callbacksCollection, callback); if (callbacksCollection.length == 0) destroyCallbacksCollection(node); } }, cleanNode : node => { ko.dependencyDetection.ignore(() => { // First clean this node, where applicable if (cleanableNodeTypes[node.nodeType]) { cleanSingleNode(node); // ... then its descendants, where applicable if (cleanableNodeTypesWithDescendants[node.nodeType]) { cleanNodesInList(node.getElementsByTagName("*")); } } }); return node; }, removeNode : node => { ko.cleanNode(node); if (node.parentNode) node.parentNode.removeChild(node); } }; })(); ko.cleanNode = ko.utils.domNodeDisposal.cleanNode; // Shorthand name for convenience ko.removeNode = ko.utils.domNodeDisposal.removeNode; // Shorthand name for convenience ko.exportSymbol('utils.domNodeDisposal', ko.utils.domNodeDisposal); ko.exportSymbol('utils.domNodeDisposal.addDisposeCallback', ko.utils.domNodeDisposal.addDisposeCallback);