3 * Autocomplete based on jQuery UI.
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 === '"') {
33 } else if (character === ',' && !quote) {
34 result.push(current.trim());
40 if (value.length > 0) {
41 result.push($.trim(current));
48 * Returns the last value of an multi-value textfield.
50 * @function Drupal.autocomplete.extractLastTerm
52 * @param {string} terms
53 * The value of the field.
56 * The last value of the input field.
58 function extractLastTerm(terms) {
59 return autocomplete.splitValues(terms).pop();
63 * The search handler is called before a search is performed.
65 * @function Drupal.autocomplete.options.search
67 * @param {object} event
68 * The event triggered.
71 * Whether to perform a search or not.
73 function searchHandler(event) {
74 const options = autocomplete.options;
76 if (options.isComposing) {
80 const term = autocomplete.extractLastTerm(event.target.value);
81 // Abort search if the first character is in firstCharacterBlacklist.
84 options.firstCharacterBlacklist.indexOf(term[0]) !== -1
88 // Only search when the term is at least the minimum length.
89 return term.length >= options.minLength;
93 * JQuery UI autocomplete source callback.
95 * @param {object} request
97 * @param {function} response
98 * The function to call with the response.
100 function sourceData(request, response) {
101 const elementId = this.element.attr('id');
103 if (!(elementId in autocomplete.cache)) {
104 autocomplete.cache[elementId] = {};
108 * Filter through the suggestions removing all terms already tagged and
109 * display the available terms to the user.
111 * @param {object} suggestions
112 * Suggestions returned by the server.
114 function showSuggestions(suggestions) {
115 const tagged = autocomplete.splitValues(request.term);
116 const il = tagged.length;
117 for (let i = 0; i < il; i++) {
118 const index = suggestions.indexOf(tagged[i]);
120 suggestions.splice(index, 1);
123 response(suggestions);
126 // Get the desired term and construct the autocomplete URL for it.
127 const term = autocomplete.extractLastTerm(request.term);
130 * Transforms the data object into an array and update autocomplete results.
132 * @param {object} data
133 * The data sent back from the server.
135 function sourceCallbackHandler(data) {
136 autocomplete.cache[elementId][term] = data;
138 // Send the new string array of terms to the jQuery UI list.
139 showSuggestions(data);
142 // Check if the term is already cached.
143 if (autocomplete.cache[elementId].hasOwnProperty(term)) {
144 showSuggestions(autocomplete.cache[elementId][term]);
146 const options = $.extend(
147 { success: sourceCallbackHandler, data: { q: term } },
150 $.ajax(this.element.attr('data-autocomplete-path'), options);
155 * Handles an autocompletefocus event.
158 * Always returns false.
160 function focusHandler() {
165 * Handles an autocompleteselect event.
167 * @param {jQuery.Event} event
168 * The event triggered.
170 * The jQuery UI settings object.
173 * Returns false to indicate the event status.
175 function selectHandler(event, ui) {
176 const terms = autocomplete.splitValues(event.target.value);
177 // Remove the current input.
179 // Add the selected item.
180 terms.push(ui.item.value);
182 event.target.value = terms.join(', ');
183 // Return false to tell jQuery UI that we've filled in the value already.
188 * Override jQuery UI _renderItem function to output HTML by default.
191 * jQuery collection of the ul element.
192 * @param {object} item
193 * The list item to append.
196 * jQuery collection of the ul element.
198 function renderItem(ul, item) {
200 .append($('<a>').html(item.label))
205 * Attaches the autocomplete behavior to all required fields.
207 * @type {Drupal~behavior}
209 * @prop {Drupal~behaviorAttach} attach
210 * Attaches the autocomplete behaviors.
211 * @prop {Drupal~behaviorDetach} detach
212 * Detaches the autocomplete behaviors.
214 Drupal.behaviors.autocomplete = {
216 // Act on textfields with the "form-autocomplete" class.
217 const $autocomplete = $(context)
218 .find('input.form-autocomplete')
219 .once('autocomplete');
220 if ($autocomplete.length) {
221 // Allow options to be overridden per instance.
222 const blacklist = $autocomplete.attr(
223 'data-autocomplete-first-character-blacklist',
225 $.extend(autocomplete.options, {
226 firstCharacterBlacklist: blacklist || '',
228 // Use jQuery UI Autocomplete on the textfield.
229 $autocomplete.autocomplete(autocomplete.options).each(function() {
230 $(this).data('ui-autocomplete')._renderItem =
231 autocomplete.options.renderItem;
234 // Use CompositionEvent to handle IME inputs. It requests remote server on "compositionend" event only.
235 $autocomplete.on('compositionstart.autocomplete', () => {
236 autocomplete.options.isComposing = true;
238 $autocomplete.on('compositionend.autocomplete', () => {
239 autocomplete.options.isComposing = false;
243 detach(context, settings, trigger) {
244 if (trigger === 'unload') {
246 .find('input.form-autocomplete')
247 .removeOnce('autocomplete')
248 .autocomplete('destroy');
254 * Autocomplete object implementation.
256 * @namespace Drupal.autocomplete
260 // Exposes options to allow overriding by contrib.
261 splitValues: autocompleteSplitValues,
263 // jQuery UI autocomplete options.
266 * JQuery UI option object.
268 * @name Drupal.autocomplete.options
273 search: searchHandler,
274 select: selectHandler,
277 // Custom options, used by Drupal.autocomplete.
278 firstCharacterBlacklist: '',
279 // Custom options, indicate IME usage status.
287 Drupal.autocomplete = autocomplete;