PageSourceSearch

https://www.iabuk.com/core/misc/autocomplete.js?v=11.3.14

js iabuk.com collected 2026-09-24 08:25:24 UTC 8,590 bytes, 298 lines download raw bytes

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.