PageSourceSearch

https://www.springerpub.com/static/version1789146322/base/Magento/…/js/lib/knockout/bindings/optgroup.js

js springerpub.com collected 2026-09-24 08:47:42 UTC 13,647 bytes, 330 lines download raw bytes

1/**
2 * Copyright © Magento, Inc. All rights reserved.
3 * See COPYING.txt for license details.
4 */
5
6define([
7    'ko',
8    'mageUtils'
9    ], function (ko, utils) {
10    'use strict';
11
12    var captionPlaceholder = {},
13        optgroupTmpl = '<optgroup label="${ $.label }"></optgroup>',
14        nbspRe = /&nbsp;/g,
15        optionsText,
16        optionsValue,
17        optionTitle;
18
19    ko.bindingHandlers.optgroup = {
20        /**
21         * @param {*} element
22         */
23        init: function (element) {
24            if (ko.utils.tagNameLower(element) !== 'select') {
25                throw new Error('options binding applies only to SELECT elements');
26            }
27
28            // Remove all existing <option>s.
29            while (element.length > 0) {
30                element.remove(0);
31            }
32        },
33
34        /**
35         * @param {*} element
36         * @param {*} valueAccessor
37         * @param {*} allBindings
38         */
39        update: function (element, valueAccessor, allBindings) {
40            var selectWasPreviouslyEmpty = element.length === 0,
41                previousScrollTop = !selectWasPreviouslyEmpty && element.multiple ? element.scrollTop : null,
42                includeDestroyed = allBindings.get('optionsIncludeDestroyed'),
43                arrayToDomNodeChildrenOptions = {},
44                captionValue,
45                unwrappedArray = ko.utils.unwrapObservable(valueAccessor()),
46                filteredArray,
47                previousSelectedValues,
48                itemUpdate = false,
49                callback = setSelectionCallback,//eslint-disable-line no-use-before-define
50                nestedOptionsLevel = -1;
51
52            optionsText = ko.utils.unwrapObservable(allBindings.get('optionsText')) || 'text';
53            optionsValue = ko.utils.unwrapObservable(allBindings.get('optionsValue')) || 'value';
54            optionTitle = optionsText + 'title';
55
56            if (element.multiple) {
57                previousSelectedValues = ko.utils.arrayMap(
58                    selectedOptions(),//eslint-disable-line no-use-before-define
59                    ko.selectExtensions.readValue
60                );
61            } else {
62                previousSelectedValues = element.selectedIndex >= 0 ?
63                    [ko.selectExtensions.readValue(element.options[element.selectedIndex])] :
64                    [];
65            }
66
67            if (unwrappedArray) {
68                if (typeof unwrappedArray.length === 'undefined') { // Coerce single value into array
69                    unwrappedArray = [unwrappedArray];
70                }
71
72                // Filter out any entries marked as destroyed
73                filteredArray = ko.utils.arrayFilter(unwrappedArray, function (item) {
74                    if (item && !item.label) {
75                        return false;
76                    }
77
78                    return includeDestroyed ||
79                        item === undefined ||
80                        item === null ||
81                        !ko.utils.unwrapObservable(item._destroy);
82                });
83                filteredArray.map(recursivePathBuilder, null);//eslint-disable-line no-use-before-define
84            }
85
86            /**
87             * @param {*} option
88             */
89            arrayToDomNodeChildrenOptions.beforeRemove = function (option) {
90                element.removeChild(option);
91            };
92
93            if (allBindings.has('optionsAfterRender')) {
94
95                /**
96                 * @param {*} arrayEntry
97                 * @param {*} newOptions
98                 */
99                callback = function (arrayEntry, newOptions) {
100                    setSelectionCallback(arrayEntry, newOptions);//eslint-disable-line no-use-before-define
101                    ko.dependencyDetection.ignore(
102                        allBindings.get('optionsAfterRender'),
103                        null,
104                        [newOptions[0],
105                        arrayEntry !== captionPlaceholder ? arrayEntry : undefined]
106                    );
107                };
108            }
109
110            filteredArray = formatOptions(filteredArray);//eslint-disable-line no-use-before-define
111            ko.utils.setDomNodeChildrenFromArrayMapping(
112                element,
113                filteredArray,
114                optionNodeFromArray,//eslint-disable-line no-use-before-define
115                arrayToDomNodeChildrenOptions,
116                callback
117            );
118
119            ko.dependencyDetection.ignore(function () {
120                var selectionChanged;
121
122                if (allBindings.get('valueAllowUnset') && allBindings.has('value')) {
123                    // The model value is authoritative, so make sure its value is the one selected
124                    ko.selectExtensions.writeValue(
125                        element,
126                        ko.utils.unwrapObservable(allBindings.get('value')),
127                        true /* allowUnset */
128                    );
129                } else {
130                    // Determine if the selection has changed as a result of updating the options list
131                    if (element.multiple) {
132                        // For a multiple-select box, compare the new selection count to the previous one
133                        // But if nothing was selected before, the selection can't have changed
134                        selectionChanged = previousSelectedValues.length &&
135                            selectedOptions().length < //eslint-disable-line no-use-before-define
136                            previousSelectedValues.length;
137                    } else {
138                        // For a single-select box, compare the current value to the previous value
139                        // But if nothing was selected before or nothing is selected now,
140                        // just look for a change in selection
141                        selectionChanged = previousSelectedValues.length && element.selectedIndex >= 0 ?
142                            ko.selectExtensions.readValue(element.options[element.selectedIndex]) !==
143                            previousSelectedValues[0] : previousSelectedValues.length || element.selectedIndex >= 0;
144                    }
145
146                    // Ensure consistency between model value and selected option.
147                    // If the dropdown was changed so that selection is no longer the same,
148                    // notify the value or selectedOptions binding.
149                    if (selectionChanged) {
150                        ko.utils.triggerEvent(element, 'change');
151                    }
152                }
153            });
154
155            /*eslint-enable max-len, no-use-before-define*/
156
157            if (previousScrollTop && Math.abs(previousScrollTop - element.scrollTop) > 20) {
158                element.scrollTop = previousScrollTop;
159            }
160
161            /**
162             * @returns {*}
163             */
164            function selectedOptions() {
165                return ko.utils.arrayFilter(element.options, function (node) {
166                    return node.selected;
167                });
168            }
169
170            /**
171             * @param {*} object
172             * @param {*} predicate
173             * @param {*} defaultValue
174             * @returns {*}
175             */
176            function applyToObject(object, predicate, defaultValue) {
177                var predicateType = typeof predicate;
178
179                if (predicateType === 'function') {   // run it against the data value
180                    return predicate(object);
181                } else if (predicateType === 'string') { // treat it as a property name on the data value
182                    return object[predicate];
183                }
184
185                return defaultValue;
186            }
187
188            /**
189             * @param {*} obj
190             */
191            function recursivePathBuilder(obj) {
192
193                obj[optionTitle] = (this && this[optionTitle] ? this[optionTitle] + '/' : '') + obj[optionsText].trim();
194
195                if (Array.isArray(obj[optionsValue])) {
196                    obj[optionsValue].map(recursivePathBuilder, obj);
197                }
198            }
199
200            /**
201             * @param {Array} arrayEntry
202             * @param {*} oldOptions
203             * @returns {*[]}
204             */
205            function optionNodeFromArray(arrayEntry, oldOptions) {
206                var option;
207
208                if (oldOptions.length) {
209                    previousSelectedValues = oldOptions[0].selected ?
210                        [ko.selectExtensions.readValue(oldOptions[0])] : [];
211                    itemUpdate = true;
212                }
213
214                if (arrayEntry === captionPlaceholder) { // empty value, label === caption
215                    option = element.ownerDocument.createElement('option');
216                    ko.utils.setTextContent(option, allBindings.get('optionsCaption'));
217                    ko.selectExtensions.writeValue(option, undefined);
218                } else if (typeof arrayEntry[optionsValue] === 'undefined') { // empty value === optgroup
219                    if (arrayEntry.__disableTmpl) {
220                        option = '<optgroup label="' + arrayEntry[optionsText] + '"></optgroup>';
221                    } else {
222                        option = utils.template(optgroupTmpl, {
223                            label: arrayEntry[optionsText],
224                            title: arrayEntry[optionsText + 'title']
225                        });
226                    }
227                    option = ko.utils.parseHtmlFragment(option)[0];
228
229                } else {
230                    option = element.ownerDocument.createElement('option');
231                    option.setAttribute('data-title', arrayEntry[optionsText + 'title']);
232                    ko.selectExtensions.writeValue(option, arrayEntry[optionsValue]);
233                    ko.utils.setTextContent(option, arrayEntry[optionsText]);
234                }
235
236                return [option];
237            }
238
239            /**
240             * @param {*} newOptions
241             */
242            function setSelectionCallback(newOptions) {
243                var isSelected;
244
245                // IE6 doesn't like us to assign selection to OPTION nodes before they're added to the document.
246                // That's why we first added them without selection. Now it's time to set the selection.
247                if (previousSelectedValues.length && newOptions.value) {
248                    isSelected = ko.utils.arrayIndexOf(
249                        previousSelectedValues,
250                        ko.selectExtensions.readValue(newOptions.value)
251                    ) >= 0;
252
253                    ko.utils.setOptionNodeSelectionState(newOptions.value, isSelected);
254
255                    // If this option was changed from being selected during a single-item update, notify the change
256                    if (itemUpdate && !isSelected) {
257                        ko.dependencyDetection.ignore(ko.utils.triggerEvent, null, [element, 'change']);
258                    }
259                }
260            }
261
262            /**
263             * @param {*} string
264             * @param {Number} times
265             * @returns {Array}
266             */
267            function strPad(string, times) {
268                return new Array(times + 1).join(string);
269            }
270
271            /**
272             * @param {*} options
273             * @returns {Array}
274             */
275            function formatOptions(options) {
276                var res = [];
277
278                nestedOptionsLevel++;
279
280                if (!nestedOptionsLevel) { // zero level
281                    // If caption is included, add it to the array
282                    if (allBindings.has('optionsCaption')) {
283                        captionValue = ko.utils.unwrapObservable(allBindings.get('optionsCaption'));
284                        // If caption value is null or undefined, don't show a caption
285                        if (//eslint-disable-line max-depth
286                            captionValue !== null &&
287                            captionValue !== undefined &&
288                            captionValue !== false
289                        ) {
290                            res.push(captionPlaceholder);
291                        }
292                    }
293                }
294
295                ko.utils.arrayForEach(options, function (option) {
296                    var value = applyToObject(option, optionsValue, option),
297                        label = applyToObject(option, optionsText, value) || '',
298                        disabled = applyToObject(option, 'disabled', false) || false,
299                        obj = {},
300                        space = '\u2007\u2007\u2007';
301
302                    obj[optionTitle] = applyToObject(option, optionsText + 'title', value);
303
304                    if (disabled) {
305                        obj.disabled = disabled;
306                    }
307
308                    if (option.hasOwnProperty('__disableTmpl')) {
309                        obj.__disableTmpl = option.__disableTmpl;
310                    }
311
312                    label = label.replace(nbspRe, '').trim();
313
314                    if (Array.isArray(value)) {
315                        obj[optionsText] = strPad('&nbsp;', nestedOptionsLevel * 4) + label;
316                        res.push(obj);
317                        res = res.concat(formatOptions(value));
318                    } else {
319                        obj[optionsText] = strPad(space, nestedOptionsLevel * 2) + label;
320                        obj[optionsValue] = value;
321                        res.push(obj);
322                    }
323                });
324                nestedOptionsLevel--;
325
326                return res;
327            }
328        }
329    };
330});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.