| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288 | /** * @file * Autocomplete based on jQuery UI. */(function ($, Drupal) {  'use strict';  var autocomplete;  /**   * Helper splitting terms from the autocomplete value.   *   * @function Drupal.autocomplete.splitValues   *   * @param {string} value   *   The value being entered by the user.   *   * @return {Array}   *   Array of values, split by comma.   */  function autocompleteSplitValues(value) {    // We will match the value against comma-separated terms.    var result = [];    var quote = false;    var current = '';    var valueLength = value.length;    var character;    for (var i = 0; i < valueLength; i++) {      character = value.charAt(i);      if (character === '"') {        current += character;        quote = !quote;      }      else if (character === ',' && !quote) {        result.push(current.trim());        current = '';      }      else {        current += character;      }    }    if (value.length > 0) {      result.push($.trim(current));    }    return result;  }  /**   * Returns the last value of an multi-value textfield.   *   * @function Drupal.autocomplete.extractLastTerm   *   * @param {string} terms   *   The value of the field.   *   * @return {string}   *   The last value of the input field.   */  function extractLastTerm(terms) {    return autocomplete.splitValues(terms).pop();  }  /**   * The search handler is called before a search is performed.   *   * @function Drupal.autocomplete.options.search   *   * @param {object} event   *   The event triggered.   *   * @return {bool}   *   Whether to perform a search or not.   */  function searchHandler(event) {    var options = autocomplete.options;    if (options.isComposing) {      return false;    }    var term = autocomplete.extractLastTerm(event.target.value);    // Abort search if the first character is in firstCharacterBlacklist.    if (term.length > 0 && options.firstCharacterBlacklist.indexOf(term[0]) !== -1) {      return false;    }    // Only search when the term is at least the minimum length.    return term.length >= options.minLength;  }  /**   * JQuery UI autocomplete source callback.   *   * @param {object} request   *   The request object.   * @param {function} response   *   The function to call with the response.   */  function sourceData(request, response) {    var elementId = this.element.attr('id');    if (!(elementId in autocomplete.cache)) {      autocomplete.cache[elementId] = {};    }    /**     * Filter through the suggestions removing all terms already tagged and     * display the available terms to the user.     *     * @param {object} suggestions     *   Suggestions returned by the server.     */    function showSuggestions(suggestions) {      var tagged = autocomplete.splitValues(request.term);      var il = tagged.length;      for (var i = 0; i < il; i++) {        var index = suggestions.indexOf(tagged[i]);        if (index >= 0) {          suggestions.splice(index, 1);        }      }      response(suggestions);    }    /**     * Transforms the data object into an array and update autocomplete results.     *     * @param {object} data     *   The data sent back from the server.     */    function sourceCallbackHandler(data) {      autocomplete.cache[elementId][term] = data;      // Send the new string array of terms to the jQuery UI list.      showSuggestions(data);    }    // Get the desired term and construct the autocomplete URL for it.    var term = autocomplete.extractLastTerm(request.term);    // Check if the term is already cached.    if (autocomplete.cache[elementId].hasOwnProperty(term)) {      showSuggestions(autocomplete.cache[elementId][term]);    }    else {      var options = $.extend({success: sourceCallbackHandler, data: {q: term}}, autocomplete.ajax);      $.ajax(this.element.attr('data-autocomplete-path'), options);    }  }  /**   * Handles an autocompletefocus event.   *   * @return {bool}   *   Always returns false.   */  function focusHandler() {    return false;  }  /**   * Handles an autocompleteselect event.   *   * @param {jQuery.Event} event   *   The event triggered.   * @param {object} ui   *   The jQuery UI settings object.   *   * @return {bool}   *   Returns false to indicate the event status.   */  function selectHandler(event, ui) {    var terms = autocomplete.splitValues(event.target.value);    // Remove the current input.    terms.pop();    // Add the selected item.    if (ui.item.value.search(',') > 0) {      terms.push('"' + ui.item.value + '"');    }    else {      terms.push(ui.item.value);    }    event.target.value = terms.join(', ');    // Return false to tell jQuery UI that we've filled in the value already.    return false;  }  /**   * Override jQuery UI _renderItem function to output HTML by default.   *   * @param {jQuery} ul   *   jQuery collection of the ul element.   * @param {object} item   *   The list item to append.   *   * @return {jQuery}   *   jQuery collection of the ul element.   */  function renderItem(ul, item) {    return $('<li>')      .append($('<a>').html(item.label))      .appendTo(ul);  }  /**   * Attaches the autocomplete behavior to all required fields.   *   * @type {Drupal~behavior}   *   * @prop {Drupal~behaviorAttach} attach   *   Attaches the autocomplete behaviors.   * @prop {Drupal~behaviorDetach} detach   *   Detaches the autocomplete behaviors.   */  Drupal.behaviors.autocomplete = {    attach: function (context) {      // Act on textfields with the "form-autocomplete" class.      var $autocomplete = $(context).find('input.form-autocomplete').once('autocomplete');      if ($autocomplete.length) {        // Allow options to be overriden per instance.        var blacklist = $autocomplete.attr('data-autocomplete-first-character-blacklist');        $.extend(autocomplete.options, {          firstCharacterBlacklist: (blacklist) ? blacklist : ''        });        // Use jQuery UI Autocomplete on the textfield.        $autocomplete.autocomplete(autocomplete.options)          .each(function () {            $(this).data('ui-autocomplete')._renderItem = autocomplete.options.renderItem;          });        // Use CompositionEvent to handle IME inputs. It requests remote server on "compositionend" event only.        $autocomplete.on('compositionstart.autocomplete', function () {          autocomplete.options.isComposing = true;        });        $autocomplete.on('compositionend.autocomplete', function () {          autocomplete.options.isComposing = false;        });      }    },    detach: function (context, settings, trigger) {      if (trigger === 'unload') {        $(context).find('input.form-autocomplete')          .removeOnce('autocomplete')          .autocomplete('destroy');      }    }  };  /**   * Autocomplete object implementation.   *   * @namespace Drupal.autocomplete   */  autocomplete = {    cache: {},    // Exposes options to allow overriding by contrib.    splitValues: autocompleteSplitValues,    extractLastTerm: extractLastTerm,    // jQuery UI autocomplete options.    /**     * JQuery UI option object.     *     * @name Drupal.autocomplete.options     */    options: {      source: sourceData,      focus: focusHandler,      search: searchHandler,      select: selectHandler,      renderItem: renderItem,      minLength: 1,      // Custom options, used by Drupal.autocomplete.      firstCharacterBlacklist: '',      // Custom options, indicate IME usage status.      isComposing: false    },    ajax: {      dataType: 'json'    }  };  Drupal.autocomplete = autocomplete;})(jQuery, Drupal);
 |