autocomplete.js 7.1 KB

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