Tab layout, first commit with new third party packages
[openemr.git] / public / assets / knockout-3-4-0 / src / binding / defaultBindings / options.js
blob009eb7dba4872e6b71dbdc1c612da92415593901
1 var captionPlaceholder = {};
2 ko.bindingHandlers['options'] = {
3     'init': function(element) {
4         if (ko.utils.tagNameLower(element) !== "select")
5             throw new Error("options binding applies only to SELECT elements");
7         // Remove all existing <option>s.
8         while (element.length > 0) {
9             element.remove(0);
10         }
12         // Ensures that the binding processor doesn't try to bind the options
13         return { 'controlsDescendantBindings': true };
14     },
15     'update': function (element, valueAccessor, allBindings) {
16         function selectedOptions() {
17             return ko.utils.arrayFilter(element.options, function (node) { return node.selected; });
18         }
20         var selectWasPreviouslyEmpty = element.length == 0,
21             multiple = element.multiple,
22             previousScrollTop = (!selectWasPreviouslyEmpty && multiple) ? element.scrollTop : null,
23             unwrappedArray = ko.utils.unwrapObservable(valueAccessor()),
24             valueAllowUnset = allBindings.get('valueAllowUnset') && allBindings['has']('value'),
25             includeDestroyed = allBindings.get('optionsIncludeDestroyed'),
26             arrayToDomNodeChildrenOptions = {},
27             captionValue,
28             filteredArray,
29             previousSelectedValues = [];
31         if (!valueAllowUnset) {
32             if (multiple) {
33                 previousSelectedValues = ko.utils.arrayMap(selectedOptions(), ko.selectExtensions.readValue);
34             } else if (element.selectedIndex >= 0) {
35                 previousSelectedValues.push(ko.selectExtensions.readValue(element.options[element.selectedIndex]));
36             }
37         }
39         if (unwrappedArray) {
40             if (typeof unwrappedArray.length == "undefined") // Coerce single value into array
41                 unwrappedArray = [unwrappedArray];
43             // Filter out any entries marked as destroyed
44             filteredArray = ko.utils.arrayFilter(unwrappedArray, function(item) {
45                 return includeDestroyed || item === undefined || item === null || !ko.utils.unwrapObservable(item['_destroy']);
46             });
48             // If caption is included, add it to the array
49             if (allBindings['has']('optionsCaption')) {
50                 captionValue = ko.utils.unwrapObservable(allBindings.get('optionsCaption'));
51                 // If caption value is null or undefined, don't show a caption
52                 if (captionValue !== null && captionValue !== undefined) {
53                     filteredArray.unshift(captionPlaceholder);
54                 }
55             }
56         } else {
57             // If a falsy value is provided (e.g. null), we'll simply empty the select element
58         }
60         function applyToObject(object, predicate, defaultValue) {
61             var predicateType = typeof predicate;
62             if (predicateType == "function")    // Given a function; run it against the data value
63                 return predicate(object);
64             else if (predicateType == "string") // Given a string; treat it as a property name on the data value
65                 return object[predicate];
66             else                                // Given no optionsText arg; use the data value itself
67                 return defaultValue;
68         }
70         // The following functions can run at two different times:
71         // The first is when the whole array is being updated directly from this binding handler.
72         // The second is when an observable value for a specific array entry is updated.
73         // oldOptions will be empty in the first case, but will be filled with the previously generated option in the second.
74         var itemUpdate = false;
75         function optionForArrayItem(arrayEntry, index, oldOptions) {
76             if (oldOptions.length) {
77                 previousSelectedValues = !valueAllowUnset && oldOptions[0].selected ? [ ko.selectExtensions.readValue(oldOptions[0]) ] : [];
78                 itemUpdate = true;
79             }
80             var option = element.ownerDocument.createElement("option");
81             if (arrayEntry === captionPlaceholder) {
82                 ko.utils.setTextContent(option, allBindings.get('optionsCaption'));
83                 ko.selectExtensions.writeValue(option, undefined);
84             } else {
85                 // Apply a value to the option element
86                 var optionValue = applyToObject(arrayEntry, allBindings.get('optionsValue'), arrayEntry);
87                 ko.selectExtensions.writeValue(option, ko.utils.unwrapObservable(optionValue));
89                 // Apply some text to the option element
90                 var optionText = applyToObject(arrayEntry, allBindings.get('optionsText'), optionValue);
91                 ko.utils.setTextContent(option, optionText);
92             }
93             return [option];
94         }
96         // By using a beforeRemove callback, we delay the removal until after new items are added. This fixes a selection
97         // problem in IE<=8 and Firefox. See https://github.com/knockout/knockout/issues/1208
98         arrayToDomNodeChildrenOptions['beforeRemove'] =
99             function (option) {
100                 element.removeChild(option);
101             };
103         function setSelectionCallback(arrayEntry, newOptions) {
104             if (itemUpdate && valueAllowUnset) {
105                 // The model value is authoritative, so make sure its value is the one selected
106                 // There is no need to use dependencyDetection.ignore since setDomNodeChildrenFromArrayMapping does so already.
107                 ko.selectExtensions.writeValue(element, ko.utils.unwrapObservable(allBindings.get('value')), true /* allowUnset */);
108             } else if (previousSelectedValues.length) {
109                 // IE6 doesn't like us to assign selection to OPTION nodes before they're added to the document.
110                 // That's why we first added them without selection. Now it's time to set the selection.
111                 var isSelected = ko.utils.arrayIndexOf(previousSelectedValues, ko.selectExtensions.readValue(newOptions[0])) >= 0;
112                 ko.utils.setOptionNodeSelectionState(newOptions[0], isSelected);
114                 // If this option was changed from being selected during a single-item update, notify the change
115                 if (itemUpdate && !isSelected) {
116                     ko.dependencyDetection.ignore(ko.utils.triggerEvent, null, [element, "change"]);
117                 }
118             }
119         }
121         var callback = setSelectionCallback;
122         if (allBindings['has']('optionsAfterRender') && typeof allBindings.get('optionsAfterRender') == "function") {
123             callback = function(arrayEntry, newOptions) {
124                 setSelectionCallback(arrayEntry, newOptions);
125                 ko.dependencyDetection.ignore(allBindings.get('optionsAfterRender'), null, [newOptions[0], arrayEntry !== captionPlaceholder ? arrayEntry : undefined]);
126             }
127         }
129         ko.utils.setDomNodeChildrenFromArrayMapping(element, filteredArray, optionForArrayItem, arrayToDomNodeChildrenOptions, callback);
131         ko.dependencyDetection.ignore(function () {
132             if (valueAllowUnset) {
133                 // The model value is authoritative, so make sure its value is the one selected
134                 ko.selectExtensions.writeValue(element, ko.utils.unwrapObservable(allBindings.get('value')), true /* allowUnset */);
135             } else {
136                 // Determine if the selection has changed as a result of updating the options list
137                 var selectionChanged;
138                 if (multiple) {
139                     // For a multiple-select box, compare the new selection count to the previous one
140                     // But if nothing was selected before, the selection can't have changed
141                     selectionChanged = previousSelectedValues.length && selectedOptions().length < previousSelectedValues.length;
142                 } else {
143                     // For a single-select box, compare the current value to the previous value
144                     // But if nothing was selected before or nothing is selected now, just look for a change in selection
145                     selectionChanged = (previousSelectedValues.length && element.selectedIndex >= 0)
146                         ? (ko.selectExtensions.readValue(element.options[element.selectedIndex]) !== previousSelectedValues[0])
147                         : (previousSelectedValues.length || element.selectedIndex >= 0);
148                 }
150                 // Ensure consistency between model value and selected option.
151                 // If the dropdown was changed so that selection is no longer the same,
152                 // notify the value or selectedOptions binding.
153                 if (selectionChanged) {
154                     ko.utils.triggerEvent(element, "change");
155                 }
156             }
157         });
159         // Workaround for IE bug
160         ko.utils.ensureSelectElementIsRenderedCorrectly(element);
162         if (previousScrollTop && Math.abs(previousScrollTop - element.scrollTop) > 20)
163             element.scrollTop = previousScrollTop;
164     }
166 ko.bindingHandlers['options'].optionValueDomDataKey = ko.utils.domData.nextKey();