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 = / /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(' ', 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.