text: /**
   * @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;
      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;
            });
        }
      },
      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: ''
      },
      ajax: {
        dataType: 'json'
      }
    };
  
    Drupal.autocomplete = autocomplete;
  
  })(jQuery, Drupal);
  