3 * Autocomplete based on jQuery UI.
6 (function ($, Drupal) {
13 * Helper splitting terms from the autocomplete value.
15 * @function Drupal.autocomplete.splitValues
17 * @param {string} value
18 * The value being entered by the user.
21 * Array of values, split by comma.
23 function autocompleteSplitValues(value) {
24 // We will match the value against comma-separated terms.
28 var valueLength = value.length;
31 for (var i = 0; i < valueLength; i++) {
32 character = value.charAt(i);
33 if (character === '"') {
37 else if (character === ',' && !quote) {
38 result.push(current.trim());
45 if (value.length > 0) {
46 result.push($.trim(current));
53 * Returns the last value of an multi-value textfield.
55 * @function Drupal.autocomplete.extractLastTerm
57 * @param {string} terms
58 * The value of the field.
61 * The last value of the input field.
63 function extractLastTerm(terms) {
64 return autocomplete.splitValues(terms).pop();
68 * The search handler is called before a search is performed.
70 * @function Drupal.autocomplete.options.search
72 * @param {object} event
73 * The event triggered.
76 * Whether to perform a search or not.
78 function searchHandler(event) {
79 var options = autocomplete.options;
81 if (options.isComposing) {
85 var term = autocomplete.extractLastTerm(event.target.value);
86 // Abort search if the first character is in firstCharacterBlacklist.
87 if (term.length > 0 && options.firstCharacterBlacklist.indexOf(term[0]) !== -1) {
90 // Only search when the term is at least the minimum length.
91 return term.length >= options.minLength;
95 * JQuery UI autocomplete source callback.
97 * @param {object} request
99 * @param {function} response
100 * The function to call with the response.
102 function sourceData(request, response) {
103 var elementId = this.element.attr('id');
105 if (!(elementId in autocomplete.cache)) {
106 autocomplete.cache[elementId] = {};
110 * Filter through the suggestions removing all terms already tagged and
111 * display the available terms to the user.
113 * @param {object} suggestions
114 * Suggestions returned by the server.
116 function showSuggestions(suggestions) {
117 var tagged = autocomplete.splitValues(request.term);
118 var il = tagged.length;
119 for (var i = 0; i < il; i++) {
120 var index = suggestions.indexOf(tagged[i]);
122 suggestions.splice(index, 1);
125 response(suggestions);
129 * Transforms the data object into an array and update autocomplete results.
131 * @param {object} data
132 * The data sent back from the server.
134 function sourceCallbackHandler(data) {
135 autocomplete.cache[elementId][term] = data;
137 // Send the new string array of terms to the jQuery UI list.
138 showSuggestions(data);
141 // Get the desired term and construct the autocomplete URL for it.
142 var term = autocomplete.extractLastTerm(request.term);
144 // Check if the term is already cached.
145 if (autocomplete.cache[elementId].hasOwnProperty(term)) {
146 showSuggestions(autocomplete.cache[elementId][term]);
149 var options = $.extend({success: sourceCallbackHandler, data: {q: term}}, autocomplete.ajax);
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 var 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 = {
215 attach: function (context) {
216 // Act on textfields with the "form-autocomplete" class.
217 var $autocomplete = $(context).find('input.form-autocomplete').once('autocomplete');
218 if ($autocomplete.length) {
219 // Allow options to be overriden per instance.
220 var blacklist = $autocomplete.attr('data-autocomplete-first-character-blacklist');
221 $.extend(autocomplete.options, {
222 firstCharacterBlacklist: (blacklist) ? blacklist : ''
224 // Use jQuery UI Autocomplete on the textfield.
225 $autocomplete.autocomplete(autocomplete.options)
227 $(this).data('ui-autocomplete')._renderItem = autocomplete.options.renderItem;
230 // Use CompositionEvent to handle IME inputs. It requests remote server on "compositionend" event only.
231 $autocomplete.on('compositionstart.autocomplete', function () {
232 autocomplete.options.isComposing = true;
234 $autocomplete.on('compositionend.autocomplete', function () {
235 autocomplete.options.isComposing = false;
239 detach: function (context, settings, trigger) {
240 if (trigger === 'unload') {
241 $(context).find('input.form-autocomplete')
242 .removeOnce('autocomplete')
243 .autocomplete('destroy');
249 * Autocomplete object implementation.
251 * @namespace Drupal.autocomplete
255 // Exposes options to allow overriding by contrib.
256 splitValues: autocompleteSplitValues,
257 extractLastTerm: extractLastTerm,
258 // jQuery UI autocomplete options.
261 * JQuery UI option object.
263 * @name Drupal.autocomplete.options
268 search: searchHandler,
269 select: selectHandler,
270 renderItem: renderItem,
272 // Custom options, used by Drupal.autocomplete.
273 firstCharacterBlacklist: '',
274 // Custom options, indicate IME usage status.
282 Drupal.autocomplete = autocomplete;