This repository has been archived on 2025-01-19. You can view files and clone it, but cannot push or open issues or pull requests.
drupalcampbristol/core/misc/autocomplete.js

251 lines
6.2 KiB
JavaScript

/**
* @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
*
* @return {Array}
*/
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
*
* @return {string}
*/
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
*
* @return {bool}
*/
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
* @param {function} 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
*/
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
*/
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}
*/
function focusHandler() {
return false;
}
/**
* Handles an autocompleteselect event.
*
* @param {jQuery.Event} event
* @param {object} ui
*
* @return {bool}
*/
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 {object} ul
* @param {object} item
*
* @return {object}
*/
function renderItem(ul, item) {
return $("<li>")
.append($("<a>").html(item.label))
.appendTo(ul);
}
/**
* Attaches the autocomplete behavior to all required fields.
*
* @type {Drupal~behavior}
*/
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)
.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);