Tab layout, first commit with new third party packages
[openemr.git] / public / assets / knockout-3-4-0 / src / templating / templating.js
blob9c85d23a2222602a1f456a52cfe97a7ce2ea3097
1 (function () {
2     var _templateEngine;
3     ko.setTemplateEngine = function (templateEngine) {
4         if ((templateEngine != undefined) && !(templateEngine instanceof ko.templateEngine))
5             throw new Error("templateEngine must inherit from ko.templateEngine");
6         _templateEngine = templateEngine;
7     }
9     function invokeForEachNodeInContinuousRange(firstNode, lastNode, action) {
10         var node, nextInQueue = firstNode, firstOutOfRangeNode = ko.virtualElements.nextSibling(lastNode);
11         while (nextInQueue && ((node = nextInQueue) !== firstOutOfRangeNode)) {
12             nextInQueue = ko.virtualElements.nextSibling(node);
13             action(node, nextInQueue);
14         }
15     }
17     function activateBindingsOnContinuousNodeArray(continuousNodeArray, bindingContext) {
18         // To be used on any nodes that have been rendered by a template and have been inserted into some parent element
19         // Walks through continuousNodeArray (which *must* be continuous, i.e., an uninterrupted sequence of sibling nodes, because
20         // the algorithm for walking them relies on this), and for each top-level item in the virtual-element sense,
21         // (1) Does a regular "applyBindings" to associate bindingContext with this node and to activate any non-memoized bindings
22         // (2) Unmemoizes any memos in the DOM subtree (e.g., to activate bindings that had been memoized during template rewriting)
24         if (continuousNodeArray.length) {
25             var firstNode = continuousNodeArray[0],
26                 lastNode = continuousNodeArray[continuousNodeArray.length - 1],
27                 parentNode = firstNode.parentNode,
28                 provider = ko.bindingProvider['instance'],
29                 preprocessNode = provider['preprocessNode'];
31             if (preprocessNode) {
32                 invokeForEachNodeInContinuousRange(firstNode, lastNode, function(node, nextNodeInRange) {
33                     var nodePreviousSibling = node.previousSibling;
34                     var newNodes = preprocessNode.call(provider, node);
35                     if (newNodes) {
36                         if (node === firstNode)
37                             firstNode = newNodes[0] || nextNodeInRange;
38                         if (node === lastNode)
39                             lastNode = newNodes[newNodes.length - 1] || nodePreviousSibling;
40                     }
41                 });
43                 // Because preprocessNode can change the nodes, including the first and last nodes, update continuousNodeArray to match.
44                 // We need the full set, including inner nodes, because the unmemoize step might remove the first node (and so the real
45                 // first node needs to be in the array).
46                 continuousNodeArray.length = 0;
47                 if (!firstNode) { // preprocessNode might have removed all the nodes, in which case there's nothing left to do
48                     return;
49                 }
50                 if (firstNode === lastNode) {
51                     continuousNodeArray.push(firstNode);
52                 } else {
53                     continuousNodeArray.push(firstNode, lastNode);
54                     ko.utils.fixUpContinuousNodeArray(continuousNodeArray, parentNode);
55                 }
56             }
58             // Need to applyBindings *before* unmemoziation, because unmemoization might introduce extra nodes (that we don't want to re-bind)
59             // whereas a regular applyBindings won't introduce new memoized nodes
60             invokeForEachNodeInContinuousRange(firstNode, lastNode, function(node) {
61                 if (node.nodeType === 1 || node.nodeType === 8)
62                     ko.applyBindings(bindingContext, node);
63             });
64             invokeForEachNodeInContinuousRange(firstNode, lastNode, function(node) {
65                 if (node.nodeType === 1 || node.nodeType === 8)
66                     ko.memoization.unmemoizeDomNodeAndDescendants(node, [bindingContext]);
67             });
69             // Make sure any changes done by applyBindings or unmemoize are reflected in the array
70             ko.utils.fixUpContinuousNodeArray(continuousNodeArray, parentNode);
71         }
72     }
74     function getFirstNodeFromPossibleArray(nodeOrNodeArray) {
75         return nodeOrNodeArray.nodeType ? nodeOrNodeArray
76                                         : nodeOrNodeArray.length > 0 ? nodeOrNodeArray[0]
77                                         : null;
78     }
80     function executeTemplate(targetNodeOrNodeArray, renderMode, template, bindingContext, options) {
81         options = options || {};
82         var firstTargetNode = targetNodeOrNodeArray && getFirstNodeFromPossibleArray(targetNodeOrNodeArray);
83         var templateDocument = (firstTargetNode || template || {}).ownerDocument;
84         var templateEngineToUse = (options['templateEngine'] || _templateEngine);
85         ko.templateRewriting.ensureTemplateIsRewritten(template, templateEngineToUse, templateDocument);
86         var renderedNodesArray = templateEngineToUse['renderTemplate'](template, bindingContext, options, templateDocument);
88         // Loosely check result is an array of DOM nodes
89         if ((typeof renderedNodesArray.length != "number") || (renderedNodesArray.length > 0 && typeof renderedNodesArray[0].nodeType != "number"))
90             throw new Error("Template engine must return an array of DOM nodes");
92         var haveAddedNodesToParent = false;
93         switch (renderMode) {
94             case "replaceChildren":
95                 ko.virtualElements.setDomNodeChildren(targetNodeOrNodeArray, renderedNodesArray);
96                 haveAddedNodesToParent = true;
97                 break;
98             case "replaceNode":
99                 ko.utils.replaceDomNodes(targetNodeOrNodeArray, renderedNodesArray);
100                 haveAddedNodesToParent = true;
101                 break;
102             case "ignoreTargetNode": break;
103             default:
104                 throw new Error("Unknown renderMode: " + renderMode);
105         }
107         if (haveAddedNodesToParent) {
108             activateBindingsOnContinuousNodeArray(renderedNodesArray, bindingContext);
109             if (options['afterRender'])
110                 ko.dependencyDetection.ignore(options['afterRender'], null, [renderedNodesArray, bindingContext['$data']]);
111         }
113         return renderedNodesArray;
114     }
116     function resolveTemplateName(template, data, context) {
117         // The template can be specified as:
118         if (ko.isObservable(template)) {
119             // 1. An observable, with string value
120             return template();
121         } else if (typeof template === 'function') {
122             // 2. A function of (data, context) returning a string
123             return template(data, context);
124         } else {
125             // 3. A string
126             return template;
127         }
128     }
130     ko.renderTemplate = function (template, dataOrBindingContext, options, targetNodeOrNodeArray, renderMode) {
131         options = options || {};
132         if ((options['templateEngine'] || _templateEngine) == undefined)
133             throw new Error("Set a template engine before calling renderTemplate");
134         renderMode = renderMode || "replaceChildren";
136         if (targetNodeOrNodeArray) {
137             var firstTargetNode = getFirstNodeFromPossibleArray(targetNodeOrNodeArray);
139             var whenToDispose = function () { return (!firstTargetNode) || !ko.utils.domNodeIsAttachedToDocument(firstTargetNode); }; // Passive disposal (on next evaluation)
140             var activelyDisposeWhenNodeIsRemoved = (firstTargetNode && renderMode == "replaceNode") ? firstTargetNode.parentNode : firstTargetNode;
142             return ko.dependentObservable( // So the DOM is automatically updated when any dependency changes
143                 function () {
144                     // Ensure we've got a proper binding context to work with
145                     var bindingContext = (dataOrBindingContext && (dataOrBindingContext instanceof ko.bindingContext))
146                         ? dataOrBindingContext
147                         : new ko.bindingContext(ko.utils.unwrapObservable(dataOrBindingContext));
149                     var templateName = resolveTemplateName(template, bindingContext['$data'], bindingContext),
150                         renderedNodesArray = executeTemplate(targetNodeOrNodeArray, renderMode, templateName, bindingContext, options);
152                     if (renderMode == "replaceNode") {
153                         targetNodeOrNodeArray = renderedNodesArray;
154                         firstTargetNode = getFirstNodeFromPossibleArray(targetNodeOrNodeArray);
155                     }
156                 },
157                 null,
158                 { disposeWhen: whenToDispose, disposeWhenNodeIsRemoved: activelyDisposeWhenNodeIsRemoved }
159             );
160         } else {
161             // We don't yet have a DOM node to evaluate, so use a memo and render the template later when there is a DOM node
162             return ko.memoization.memoize(function (domNode) {
163                 ko.renderTemplate(template, dataOrBindingContext, options, domNode, "replaceNode");
164             });
165         }
166     };
168     ko.renderTemplateForEach = function (template, arrayOrObservableArray, options, targetNode, parentBindingContext) {
169         // Since setDomNodeChildrenFromArrayMapping always calls executeTemplateForArrayItem and then
170         // activateBindingsCallback for added items, we can store the binding context in the former to use in the latter.
171         var arrayItemContext;
173         // This will be called by setDomNodeChildrenFromArrayMapping to get the nodes to add to targetNode
174         var executeTemplateForArrayItem = function (arrayValue, index) {
175             // Support selecting template as a function of the data being rendered
176             arrayItemContext = parentBindingContext['createChildContext'](arrayValue, options['as'], function(context) {
177                 context['$index'] = index;
178             });
180             var templateName = resolveTemplateName(template, arrayValue, arrayItemContext);
181             return executeTemplate(null, "ignoreTargetNode", templateName, arrayItemContext, options);
182         }
184         // This will be called whenever setDomNodeChildrenFromArrayMapping has added nodes to targetNode
185         var activateBindingsCallback = function(arrayValue, addedNodesArray, index) {
186             activateBindingsOnContinuousNodeArray(addedNodesArray, arrayItemContext);
187             if (options['afterRender'])
188                 options['afterRender'](addedNodesArray, arrayValue);
190             // release the "cache" variable, so that it can be collected by
191             // the GC when its value isn't used from within the bindings anymore.
192             arrayItemContext = null;
193         };
195         return ko.dependentObservable(function () {
196             var unwrappedArray = ko.utils.unwrapObservable(arrayOrObservableArray) || [];
197             if (typeof unwrappedArray.length == "undefined") // Coerce single value into array
198                 unwrappedArray = [unwrappedArray];
200             // Filter out any entries marked as destroyed
201             var filteredArray = ko.utils.arrayFilter(unwrappedArray, function(item) {
202                 return options['includeDestroyed'] || item === undefined || item === null || !ko.utils.unwrapObservable(item['_destroy']);
203             });
205             // Call setDomNodeChildrenFromArrayMapping, ignoring any observables unwrapped within (most likely from a callback function).
206             // If the array items are observables, though, they will be unwrapped in executeTemplateForArrayItem and managed within setDomNodeChildrenFromArrayMapping.
207             ko.dependencyDetection.ignore(ko.utils.setDomNodeChildrenFromArrayMapping, null, [targetNode, filteredArray, executeTemplateForArrayItem, options, activateBindingsCallback]);
209         }, null, { disposeWhenNodeIsRemoved: targetNode });
210     };
212     var templateComputedDomDataKey = ko.utils.domData.nextKey();
213     function disposeOldComputedAndStoreNewOne(element, newComputed) {
214         var oldComputed = ko.utils.domData.get(element, templateComputedDomDataKey);
215         if (oldComputed && (typeof(oldComputed.dispose) == 'function'))
216             oldComputed.dispose();
217         ko.utils.domData.set(element, templateComputedDomDataKey, (newComputed && newComputed.isActive()) ? newComputed : undefined);
218     }
220     ko.bindingHandlers['template'] = {
221         'init': function(element, valueAccessor) {
222             // Support anonymous templates
223             var bindingValue = ko.utils.unwrapObservable(valueAccessor());
224             if (typeof bindingValue == "string" || bindingValue['name']) {
225                 // It's a named template - clear the element
226                 ko.virtualElements.emptyNode(element);
227             } else if ('nodes' in bindingValue) {
228                 // We've been given an array of DOM nodes. Save them as the template source.
229                 // There is no known use case for the node array being an observable array (if the output
230                 // varies, put that behavior *into* your template - that's what templates are for), and
231                 // the implementation would be a mess, so assert that it's not observable.
232                 var nodes = bindingValue['nodes'] || [];
233                 if (ko.isObservable(nodes)) {
234                     throw new Error('The "nodes" option must be a plain, non-observable array.');
235                 }
236                 var container = ko.utils.moveCleanedNodesToContainerElement(nodes); // This also removes the nodes from their current parent
237                 new ko.templateSources.anonymousTemplate(element)['nodes'](container);
238             } else {
239                 // It's an anonymous template - store the element contents, then clear the element
240                 var templateNodes = ko.virtualElements.childNodes(element),
241                     container = ko.utils.moveCleanedNodesToContainerElement(templateNodes); // This also removes the nodes from their current parent
242                 new ko.templateSources.anonymousTemplate(element)['nodes'](container);
243             }
244             return { 'controlsDescendantBindings': true };
245         },
246         'update': function (element, valueAccessor, allBindings, viewModel, bindingContext) {
247             var value = valueAccessor(),
248                 dataValue,
249                 options = ko.utils.unwrapObservable(value),
250                 shouldDisplay = true,
251                 templateComputed = null,
252                 templateName;
254             if (typeof options == "string") {
255                 templateName = value;
256                 options = {};
257             } else {
258                 templateName = options['name'];
260                 // Support "if"/"ifnot" conditions
261                 if ('if' in options)
262                     shouldDisplay = ko.utils.unwrapObservable(options['if']);
263                 if (shouldDisplay && 'ifnot' in options)
264                     shouldDisplay = !ko.utils.unwrapObservable(options['ifnot']);
266                 dataValue = ko.utils.unwrapObservable(options['data']);
267             }
269             if ('foreach' in options) {
270                 // Render once for each data point (treating data set as empty if shouldDisplay==false)
271                 var dataArray = (shouldDisplay && options['foreach']) || [];
272                 templateComputed = ko.renderTemplateForEach(templateName || element, dataArray, options, element, bindingContext);
273             } else if (!shouldDisplay) {
274                 ko.virtualElements.emptyNode(element);
275             } else {
276                 // Render once for this single data point (or use the viewModel if no data was provided)
277                 var innerBindingContext = ('data' in options) ?
278                     bindingContext['createChildContext'](dataValue, options['as']) :  // Given an explitit 'data' value, we create a child binding context for it
279                     bindingContext;                                                        // Given no explicit 'data' value, we retain the same binding context
280                 templateComputed = ko.renderTemplate(templateName || element, innerBindingContext, options, element);
281             }
283             // It only makes sense to have a single template computed per element (otherwise which one should have its output displayed?)
284             disposeOldComputedAndStoreNewOne(element, templateComputed);
285         }
286     };
288     // Anonymous templates can't be rewritten. Give a nice error message if you try to do it.
289     ko.expressionRewriting.bindingRewriteValidators['template'] = function(bindingValue) {
290         var parsedBindingValue = ko.expressionRewriting.parseObjectLiteral(bindingValue);
292         if ((parsedBindingValue.length == 1) && parsedBindingValue[0]['unknown'])
293             return null; // It looks like a string literal, not an object literal, so treat it as a named template (which is allowed for rewriting)
295         if (ko.expressionRewriting.keyValueArrayContainsKey(parsedBindingValue, "name"))
296             return null; // Named templates can be rewritten, so return "no error"
297         return "This template engine does not support anonymous templates nested within its templates";
298     };
300     ko.virtualElements.allowedBindings['template'] = true;
301 })();
303 ko.exportSymbol('setTemplateEngine', ko.setTemplateEngine);
304 ko.exportSymbol('renderTemplate', ko.renderTemplate);