annotate core/misc/autocomplete.es6.js @ 19:fa3358dc1485 tip

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