3 * Autocomplete based on jQuery UI.
6 (function ($, Drupal) {
10 * Helper splitting terms from the autocomplete value.
12 * @function Drupal.autocomplete.splitValues
14 * @param {string} value
15 * The value being entered by the user.
18 * Array of values, split by comma.
20 function autocompleteSplitValues(value) {
21 // We will match the value against comma-separated terms.
25 const valueLength = value.length;
28 for (let i = 0; i < valueLength; i++) {
29 character = value.charAt(i);
30 if (character === '"') {
34 else if (character === ',' && !quote) {
35 result.push(current.trim());
42 if (value.length > 0) {
43 result.push($.trim(current));
50 * Returns the last value of an multi-value textfield.
52 * @function Drupal.autocomplete.extractLastTerm
54 * @param {string} terms
55 * The value of the field.
58 * The last value of the input field.
60 function extractLastTerm(terms) {
61 return autocomplete.splitValues(terms).pop();
65 * The search handler is called before a search is performed.
67 * @function Drupal.autocomplete.options.search
69 * @param {object} event
70 * The event triggered.
73 * Whether to perform a search or not.
75 function searchHandler(event) {
76 const options = autocomplete.options;
78 if (options.isComposing) {
82 const term = autocomplete.extractLastTerm(event.target.value);
83 // Abort search if the first character is in firstCharacterBlacklist.
84 if (term.length > 0 && options.firstCharacterBlacklist.indexOf(term[0]) !== -1) {
87 // Only search when the term is at least the minimum length.
88 return term.length >= options.minLength;
92 * JQuery UI autocomplete source callback.
94 * @param {object} request
96 * @param {function} response
97 * The function to call with the response.
99 function sourceData(request, response) {
100 const elementId = this.element.attr('id');
102 if (!(elementId in autocomplete.cache)) {
103 autocomplete.cache[elementId] = {};
107 * Filter through the suggestions removing all terms already tagged and
108 * display the available terms to the user.
110 * @param {object} suggestions
111 * Suggestions returned by the server.
113 function showSuggestions(suggestions) {
114 const tagged = autocomplete.splitValues(request.term);
115 const il = tagged.length;
116 for (let i = 0; i < il; i++) {
117 const index = suggestions.indexOf(tagged[i]);
119 suggestions.splice(index, 1);
122 response(suggestions);
126 * Transforms the data object into an array and update autocomplete results.
128 * @param {object} data
129 * The data sent back from the server.
131 function sourceCallbackHandler(data) {
132 autocomplete.cache[elementId][term] = data;
134 // Send the new string array of terms to the jQuery UI list.
135 showSuggestions(data);
138 // Get the desired term and construct the autocomplete URL for it.
139 const term = autocomplete.extractLastTerm(request.term);
141 // Check if the term is already cached.
142 if (autocomplete.cache[elementId].hasOwnProperty(term)) {
143 showSuggestions(autocomplete.cache[elementId][term]);
146 const options = $.extend({ success: sourceCallbackHandler, data: { q: term } }, autocomplete.ajax);
147 $.ajax(this.element.attr('data-autocomplete-path'), options);
152 * Handles an autocompletefocus event.
155 * Always returns false.
157 function focusHandler() {
162 * Handles an autocompleteselect event.
164 * @param {jQuery.Event} event
165 * The event triggered.
167 * The jQuery UI settings object.
170 * Returns false to indicate the event status.
172 function selectHandler(event, ui) {
173 const terms = autocomplete.splitValues(event.target.value);
174 // Remove the current input.
176 // Add the selected item.
177 terms.push(ui.item.value);
179 event.target.value = terms.join(', ');
180 // Return false to tell jQuery UI that we've filled in the value already.
185 * Override jQuery UI _renderItem function to output HTML by default.
188 * jQuery collection of the ul element.
189 * @param {object} item
190 * The list item to append.
193 * jQuery collection of the ul element.
195 function renderItem(ul, item) {
197 .append($('<a>').html(item.label))
202 * Attaches the autocomplete behavior to all required fields.
204 * @type {Drupal~behavior}
206 * @prop {Drupal~behaviorAttach} attach
207 * Attaches the autocomplete behaviors.
208 * @prop {Drupal~behaviorDetach} detach
209 * Detaches the autocomplete behaviors.
211 Drupal.behaviors.autocomplete = {
213 // Act on textfields with the "form-autocomplete" class.
214 const $autocomplete = $(context).find('input.form-autocomplete').once('autocomplete');
215 if ($autocomplete.length) {
216 // Allow options to be overriden per instance.
217 const blacklist = $autocomplete.attr('data-autocomplete-first-character-blacklist');
218 $.extend(autocomplete.options, {
219 firstCharacterBlacklist: (blacklist) || '',
221 // Use jQuery UI Autocomplete on the textfield.
222 $autocomplete.autocomplete(autocomplete.options)
224 $(this).data('ui-autocomplete')._renderItem = autocomplete.options.renderItem;
227 // Use CompositionEvent to handle IME inputs. It requests remote server on "compositionend" event only.
228 $autocomplete.on('compositionstart.autocomplete', () => {
229 autocomplete.options.isComposing = true;
231 $autocomplete.on('compositionend.autocomplete', () => {
232 autocomplete.options.isComposing = false;
236 detach(context, settings, trigger) {
237 if (trigger === 'unload') {
238 $(context).find('input.form-autocomplete')
239 .removeOnce('autocomplete')
240 .autocomplete('destroy');
246 * Autocomplete object implementation.
248 * @namespace Drupal.autocomplete
252 // Exposes options to allow overriding by contrib.
253 splitValues: autocompleteSplitValues,
255 // jQuery UI autocomplete options.
258 * JQuery UI option object.
260 * @name Drupal.autocomplete.options
265 search: searchHandler,
266 select: selectHandler,
269 // Custom options, used by Drupal.autocomplete.
270 firstCharacterBlacklist: '',
271 // Custom options, indicate IME usage status.
279 Drupal.autocomplete = autocomplete;