annotate core/misc/autocomplete.es6.js @ 0:c75dbcec494b

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