1/** 2 * @file 3 * Autocomplete based on jQuery UI. 4 */ 5 6(function ($, Drupal) { 7 let autocomplete; 8 9 /** 10 * Helper splitting terms from the autocomplete value. 11 * 12 * @function Drupal.autocomplete.splitValues 13 * 14 * @param {string} value 15 * The value being entered by the user. 16 * 17 * @return {Array} 18 * Array of values, split by comma. 19 */ 20 function autocompleteSplitValues(value) { 21 // We will match the value against comma-separated terms. 22 const result = []; 23 let quote = false; 24 let current = ''; 25 const valueLength = value.length; 26 let character; 27 28 for (let i = 0; i < valueLength; i++) { 29 character = value.charAt(i); 30 if (character === '"') { 31 current += character; 32 quote = !quote; 33 } else if (character === ',' && !quote) { 34 result.push(current.trim()); 35 current = ''; 36 } else { 37 current += character; 38 } 39 } 40 if (value.length > 0) { 41 result.push(current.trim()); 42 } 43 44 return result; 45 } 46 47 /** 48 * Returns the last value of a multi-value textfield. 49 * 50 * @function Drupal.autocomplete.extractLastTerm 51 * 52 * @param {string} terms 53 * The value of the field. 54 * 55 * @return {string} 56 * The last value of the input field. 57 */ 58 function extractLastTerm(terms) { 59 return autocomplete.splitValues(terms).pop(); 60 } 61 62 /** 63 * The search handler is called before a search is performed. 64 * 65 * @function Drupal.autocomplete.options.search 66 * 67 * @param {object} event 68 * The event triggered. 69 * 70 * @return {boolean} 71 * Whether to perform a search or not. 72 */ 73 function searchHandler(event) { 74 const options = autocomplete.options; 75 76 if (options.isComposing) { 77 return false; 78 } 79 80 const term = autocomplete.extractLastTerm(event.target.value); 81 // Abort search if the first character is in firstCharacterDenyList. 82 if (term.length > 0 && options.firstCharacterDenyList.includes(term[0])) { 83 return false; 84 } 85 // Only search when the term is at least the minimum length. 86 return term.length >= options.minLength; 87 } 88 89 /** 90 * JQuery UI autocomplete source callback. 91 * 92 * @param {object} request 93 * The request object. 94 * @param {function} response 95 * The function to call with the response. 96 */ 97 function sourceData(request, response) { 98 const elementId = this.element.attr('id'); 99 100 if (!(elementId in autocomplete.cache)) { 101 autocomplete.cache[elementId] = {}; 102 } 103 104 /** 105 * Filter through the suggestions removing all terms already tagged and 106 * display the available terms to the user. 107 * 108 * @param {object} suggestions 109 * Suggestions returned by the server. 110 */ 111 function showSuggestions(suggestions) { 112 const tagged = autocomplete.splitValues(request.term); 113 const il = tagged.length; 114 for (let i = 0; i < il; i++) { 115 if (suggestions.includes(tagged[i])) { 116 suggestions.splice(suggestions.indexOf(tagged[i]), 1); 117 } 118 } 119 response(suggestions); 120 } 121 122 // Get the desired term and construct the autocomplete URL for it. 123 const term = autocomplete.extractLastTerm(request.term); 124 125 /** 126 * Transforms the data object into an array and update autocomplete results. 127 * 128 * @param {object} data 129 * The data sent back from the server. 130 */ 131 function sourceCallbackHandler(data) { 132 autocomplete.cache[elementId][term] = data; 133 134 // Send the new string array of terms to the jQuery UI list. 135 showSuggestions(data); 136 } 137 138 // Check if the term is already cached. 139 if (autocomplete.cache[elementId].hasOwnProperty(term)) { 140 showSuggestions(autocomplete.cache[elementId][term]); 141 } else { 142 const options = $.extend( 143 { success: sourceCallbackHandler, data: { q: term } }, 144 autocomplete.ajax, 145 ); 146 $.ajax(this.element.attr('data-autocomplete-path'), options); 147 } 148 } 149 150 /** 151 * Handles an autocomplete focus event. 152 * 153 * @return {boolean} 154 * Always returns false. 155 */ 156 function focusHandler() { 157 return false;
158 } 159 160 /** 161 * Handles an autocomplete select event. 162 * 163 * @param {jQuery.Event} event 164 * The event triggered. 165 * @param {object} ui 166 * The jQuery UI settings object. 167 * 168 * @return {boolean} 169 * Returns false to indicate the event status. 170 */ 171 function selectHandler(event, ui) { 172 const terms = autocomplete.splitValues(event.target.value); 173 // Remove the current input. 174 terms.pop(); 175 // Add the selected item. 176 terms.push(ui.item.value); 177 178 event.target.value = terms.join(', '); 179 // Return false to tell jQuery UI that we've filled in the value already. 180 return false; 181 } 182 183 /** 184 * Override jQuery UI _renderItem function to output HTML by default. 185 * 186 * @param {jQuery} ul 187 * jQuery collection of the ul element. 188 * @param {object} item 189 * The list item to append. 190 * 191 * @return {jQuery} 192 * jQuery collection of the ul element. 193 */ 194 function renderItem(ul, item) { 195 return $('<li>').append($('<a>').html(item.label)).appendTo(ul); 196 } 197 198 /** 199 * Attaches the autocomplete behavior to all required fields. 200 * 201 * @type {Drupal~behavior} 202 * 203 * @prop {Drupal~behaviorAttach} attach 204 * Attaches the autocomplete behaviors. 205 * @prop {Drupal~behaviorDetach} detach 206 * Detaches the autocomplete behaviors. 207 */ 208 Drupal.behaviors.autocomplete = { 209 attach(context) { 210 // Act on textfields with the "form-autocomplete" class. 211 once('autocomplete', 'input.form-autocomplete', context).forEach( 212 (element) => { 213 const $autocomplete = $(element); 214 // cspell:ignore blacklist 215 // Allow options to be overridden per instance. 216 const blacklist = $autocomplete.attr( 217 'data-autocomplete-first-character-blacklist', 218 ); 219 if (blacklist !== undefined) { 220 Drupal.deprecationError({ 221 message: 222 'The data-autocomplete-first-character-blocklist attribute is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use firstCharacterDenyList instead. See https://www.drupal.org/node/3472016.', 223 }); 224 } 225 const denyList = $autocomplete.attr( 226 'data-autocomplete-first-character-denylist', 227 ); 228 Drupal.deprecatedProperty({ 229 target: autocomplete.options, 230 deprecatedProperty: 'firstCharacterBlacklist', 231 message: 232 'The firstCharacterBlacklist property is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use firstCharacterDenyList instead. See https://www.drupal.org/node/3472016.', 233 }); 234 $.extend(autocomplete.options, { 235 firstCharacterDenyList: denyList || blacklist, 236 }); 237 // Use jQuery UI Autocomplete on the textfield. 238 $autocomplete.autocomplete(autocomplete.options).each(function () { 239 $(this).data('ui-autocomplete')._renderItem = 240 autocomplete.options.renderItem; 241 }); 242 243 // Use CompositionEvent to handle IME inputs. It requests remote server on "compositionend" event only. 244 $autocomplete.on('compositionstart.autocomplete', () => { 245 autocomplete.options.isComposing = true; 246 }); 247 $autocomplete.on('compositionend.autocomplete', () => { 248 autocomplete.options.isComposing = false; 249 }); 250 }, 251 ); 252 }, 253 detach(context, settings, trigger) { 254 if (trigger === 'unload') { 255 $( 256 once.remove('autocomplete', 'input.form-autocomplete', context), 257 ).autocomplete('destroy'); 258 } 259 }, 260 }; 261 262 /** 263 * Autocomplete object implementation. 264 * 265 * @namespace Drupal.autocomplete 266 */ 267 autocomplete = { 268 cache: {}, 269 // Exposes options to allow overriding by contrib. 270 splitValues: autocompleteSplitValues, 271 extractLastTerm, 272 // jQuery UI autocomplete options. 273 274 /** 275 * JQuery UI option object. 276 * 277 * @name Drupal.autocomplete.options 278 */ 279 options: { 280 source: sourceData, 281 focus: focusHandler, 282 search: searchHandler, 283 select: selectHandler, 284 renderItem, 285 minLength: 1, 286 // Custom options, used by Drupal.autocomplete. 287 firstCharacterDenyList: '', 288 // Custom options, indicate IME usage status. 289 isComposing: false, 290 }, 291 ajax: { 292 dataType: 'json', 293 jsonp: false, 294 }, 295 }; 296 297 Drupal.autocomplete = autocomplete; 298})(jQuery, Drupal);
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.