Tab layout, first commit with new third party packages
[openemr.git] / public / assets / knockout-3-4-0 / src / binding / editDetection / arrayToDomNodeChildren.js
blobafec30d67a0c74b1b46edabf112e15a59c02db25
1 (function () {
2     // Objective:
3     // * Given an input array, a container DOM node, and a function from array elements to arrays of DOM nodes,
4     //   map the array elements to arrays of DOM nodes, concatenate together all these arrays, and use them to populate the container DOM node
5     // * Next time we're given the same combination of things (with the array possibly having mutated), update the container DOM node
6     //   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
7     //   previously mapped - retain those nodes, and just insert/delete other ones
9     // "callbackAfterAddingNodes" will be invoked after any "mapping"-generated nodes are inserted into the container node
10     // You can use this, for example, to activate bindings on those nodes.
12     function mapNodeAndRefreshWhenChanged(containerNode, mapping, valueToMap, callbackAfterAddingNodes, index) {
13         // Map this array value inside a dependentObservable so we re-map when any dependency changes
14         var mappedNodes = [];
15         var dependentObservable = ko.dependentObservable(function() {
16             var newMappedNodes = mapping(valueToMap, index, ko.utils.fixUpContinuousNodeArray(mappedNodes, containerNode)) || [];
18             // On subsequent evaluations, just replace the previously-inserted DOM nodes
19             if (mappedNodes.length > 0) {
20                 ko.utils.replaceDomNodes(mappedNodes, newMappedNodes);
21                 if (callbackAfterAddingNodes)
22                     ko.dependencyDetection.ignore(callbackAfterAddingNodes, null, [valueToMap, newMappedNodes, index]);
23             }
25             // Replace the contents of the mappedNodes array, thereby updating the record
26             // of which nodes would be deleted if valueToMap was itself later removed
27             mappedNodes.length = 0;
28             ko.utils.arrayPushAll(mappedNodes, newMappedNodes);
29         }, null, { disposeWhenNodeIsRemoved: containerNode, disposeWhen: function() { return !ko.utils.anyDomNodeIsAttachedToDocument(mappedNodes); } });
30         return { mappedNodes : mappedNodes, dependentObservable : (dependentObservable.isActive() ? dependentObservable : undefined) };
31     }
33     var lastMappingResultDomDataKey = ko.utils.domData.nextKey(),
34         deletedItemDummyValue = ko.utils.domData.nextKey();
36     ko.utils.setDomNodeChildrenFromArrayMapping = function (domNode, array, mapping, options, callbackAfterAddingNodes) {
37         // Compare the provided array against the previous one
38         array = array || [];
39         options = options || {};
40         var isFirstExecution = ko.utils.domData.get(domNode, lastMappingResultDomDataKey) === undefined;
41         var lastMappingResult = ko.utils.domData.get(domNode, lastMappingResultDomDataKey) || [];
42         var lastArray = ko.utils.arrayMap(lastMappingResult, function (x) { return x.arrayEntry; });
43         var editScript = ko.utils.compareArrays(lastArray, array, options['dontLimitMoves']);
45         // Build the new mapping result
46         var newMappingResult = [];
47         var lastMappingResultIndex = 0;
48         var newMappingResultIndex = 0;
50         var nodesToDelete = [];
51         var itemsToProcess = [];
52         var itemsForBeforeRemoveCallbacks = [];
53         var itemsForMoveCallbacks = [];
54         var itemsForAfterAddCallbacks = [];
55         var mapData;
57         function itemMovedOrRetained(editScriptIndex, oldPosition) {
58             mapData = lastMappingResult[oldPosition];
59             if (newMappingResultIndex !== oldPosition)
60                 itemsForMoveCallbacks[editScriptIndex] = mapData;
61             // Since updating the index might change the nodes, do so before calling fixUpContinuousNodeArray
62             mapData.indexObservable(newMappingResultIndex++);
63             ko.utils.fixUpContinuousNodeArray(mapData.mappedNodes, domNode);
64             newMappingResult.push(mapData);
65             itemsToProcess.push(mapData);
66         }
68         function callCallback(callback, items) {
69             if (callback) {
70                 for (var i = 0, n = items.length; i < n; i++) {
71                     if (items[i]) {
72                         ko.utils.arrayForEach(items[i].mappedNodes, function(node) {
73                             callback(node, i, items[i].arrayEntry);
74                         });
75                     }
76                 }
77             }
78         }
80         for (var i = 0, editScriptItem, movedIndex; editScriptItem = editScript[i]; i++) {
81             movedIndex = editScriptItem['moved'];
82             switch (editScriptItem['status']) {
83                 case "deleted":
84                     if (movedIndex === undefined) {
85                         mapData = lastMappingResult[lastMappingResultIndex];
87                         // Stop tracking changes to the mapping for these nodes
88                         if (mapData.dependentObservable) {
89                             mapData.dependentObservable.dispose();
90                             mapData.dependentObservable = undefined;
91                         }
93                         // Queue these nodes for later removal
94                         if (ko.utils.fixUpContinuousNodeArray(mapData.mappedNodes, domNode).length) {
95                             if (options['beforeRemove']) {
96                                 newMappingResult.push(mapData);
97                                 itemsToProcess.push(mapData);
98                                 if (mapData.arrayEntry === deletedItemDummyValue) {
99                                     mapData = null;
100                                 } else {
101                                     itemsForBeforeRemoveCallbacks[i] = mapData;
102                                 }
103                             }
104                             if (mapData) {
105                                 nodesToDelete.push.apply(nodesToDelete, mapData.mappedNodes);
106                             }
107                         }
108                     }
109                     lastMappingResultIndex++;
110                     break;
112                 case "retained":
113                     itemMovedOrRetained(i, lastMappingResultIndex++);
114                     break;
116                 case "added":
117                     if (movedIndex !== undefined) {
118                         itemMovedOrRetained(i, movedIndex);
119                     } else {
120                         mapData = { arrayEntry: editScriptItem['value'], indexObservable: ko.observable(newMappingResultIndex++) };
121                         newMappingResult.push(mapData);
122                         itemsToProcess.push(mapData);
123                         if (!isFirstExecution)
124                             itemsForAfterAddCallbacks[i] = mapData;
125                     }
126                     break;
127             }
128         }
130         // Store a copy of the array items we just considered so we can difference it next time
131         ko.utils.domData.set(domNode, lastMappingResultDomDataKey, newMappingResult);
133         // Call beforeMove first before any changes have been made to the DOM
134         callCallback(options['beforeMove'], itemsForMoveCallbacks);
136         // Next remove nodes for deleted items (or just clean if there's a beforeRemove callback)
137         ko.utils.arrayForEach(nodesToDelete, options['beforeRemove'] ? ko.cleanNode : ko.removeNode);
139         // Next add/reorder the remaining items (will include deleted items if there's a beforeRemove callback)
140         for (var i = 0, nextNode = ko.virtualElements.firstChild(domNode), lastNode, node; mapData = itemsToProcess[i]; i++) {
141             // Get nodes for newly added items
142             if (!mapData.mappedNodes)
143                 ko.utils.extend(mapData, mapNodeAndRefreshWhenChanged(domNode, mapping, mapData.arrayEntry, callbackAfterAddingNodes, mapData.indexObservable));
145             // Put nodes in the right place if they aren't there already
146             for (var j = 0; node = mapData.mappedNodes[j]; nextNode = node.nextSibling, lastNode = node, j++) {
147                 if (node !== nextNode)
148                     ko.virtualElements.insertAfter(domNode, node, lastNode);
149             }
151             // Run the callbacks for newly added nodes (for example, to apply bindings, etc.)
152             if (!mapData.initialized && callbackAfterAddingNodes) {
153                 callbackAfterAddingNodes(mapData.arrayEntry, mapData.mappedNodes, mapData.indexObservable);
154                 mapData.initialized = true;
155             }
156         }
158         // If there's a beforeRemove callback, call it after reordering.
159         // Note that we assume that the beforeRemove callback will usually be used to remove the nodes using
160         // some sort of animation, which is why we first reorder the nodes that will be removed. If the
161         // callback instead removes the nodes right away, it would be more efficient to skip reordering them.
162         // Perhaps we'll make that change in the future if this scenario becomes more common.
163         callCallback(options['beforeRemove'], itemsForBeforeRemoveCallbacks);
165         // Replace the stored values of deleted items with a dummy value. This provides two benefits: it marks this item
166         // as already "removed" so we won't call beforeRemove for it again, and it ensures that the item won't match up
167         // with an actual item in the array and appear as "retained" or "moved".
168         for (i = 0; i < itemsForBeforeRemoveCallbacks.length; ++i) {
169             if (itemsForBeforeRemoveCallbacks[i]) {
170                 itemsForBeforeRemoveCallbacks[i].arrayEntry = deletedItemDummyValue;
171             }
172         }
174         // Finally call afterMove and afterAdd callbacks
175         callCallback(options['afterMove'], itemsForMoveCallbacks);
176         callCallback(options['afterAdd'], itemsForAfterAddCallbacks);
177     }
178 })();
180 ko.exportSymbol('utils.setDomNodeChildrenFromArrayMapping', ko.utils.setDomNodeChildrenFromArrayMapping);