autocomplete.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. /**
  2. * @file
  3. * Linkit Autocomplete based on jQuery UI.
  4. */
  5. (function ($, Drupal, _) {
  6. 'use strict';
  7. var autocomplete;
  8. /**
  9. * JQuery UI autocomplete source callback.
  10. *
  11. * @param {object} request
  12. * The request object.
  13. * @param {function} response
  14. * The function to call with the response.
  15. */
  16. function sourceData(request, response) {
  17. var elementId = this.element.attr('id');
  18. if (!(elementId in autocomplete.cache)) {
  19. autocomplete.cache[elementId] = {};
  20. }
  21. /**
  22. * Transforms the data object into an array and update autocomplete results.
  23. *
  24. * @param {object} data
  25. * The data sent back from the server.
  26. */
  27. function sourceCallbackHandler(data) {
  28. autocomplete.cache[elementId][term] = data.suggestions;
  29. response(data.suggestions);
  30. }
  31. // Get the desired term and construct the autocomplete URL for it.
  32. var term = request.term;
  33. // Check if the term is already cached.
  34. if (autocomplete.cache[elementId].hasOwnProperty(term)) {
  35. response(autocomplete.cache[elementId][term]);
  36. }
  37. else {
  38. var options = $.extend({
  39. success: sourceCallbackHandler,
  40. data: {q: term}
  41. }, autocomplete.ajax);
  42. $.ajax(this.element.attr('data-autocomplete-path'), options);
  43. }
  44. }
  45. /**
  46. * Handles an autocomplete select event.
  47. *
  48. * @param {jQuery.Event} event
  49. * The event triggered.
  50. * @param {object} ui
  51. * The jQuery UI settings object.
  52. *
  53. * @return {boolean}
  54. * False to prevent further handlers.
  55. */
  56. function selectHandler(event, ui) {
  57. var $form = $(event.target).closest('form');
  58. if (!ui.item.path) {
  59. throw 'Missing path param.' + JSON.stringify(ui.item);
  60. }
  61. $('input[name="href_dirty_check"]', $form).val(ui.item.path);
  62. if (ui.item.entity_type_id || ui.item.entity_uuid || ui.item.substitution_id) {
  63. if (!ui.item.entity_type_id || !ui.item.entity_uuid || !ui.item.substitution_id) {
  64. throw 'Missing path param.' + JSON.stringify(ui.item);
  65. }
  66. $('input[name="attributes[data-entity-type]"]', $form).val(ui.item.entity_type_id);
  67. $('input[name="attributes[data-entity-uuid]"]', $form).val(ui.item.entity_uuid);
  68. $('input[name="attributes[data-entity-substitution]"]', $form).val(ui.item.substitution_id);
  69. }
  70. event.target.value = ui.item.path;
  71. return false;
  72. }
  73. /**
  74. * Override jQuery UI _renderItem function to output HTML by default.
  75. *
  76. * @param {object} ul
  77. * The <ul> element that the newly created <li> element must be appended to.
  78. * @param {object} item
  79. * The list item to append.
  80. *
  81. * @return {object}
  82. * jQuery collection of the ul element.
  83. */
  84. function renderItem(ul, item) {
  85. var $line = $('<li>').addClass('linkit-result-line');
  86. var $wrapper = $('<div>').addClass('linkit-result-line-wrapper');
  87. $wrapper.append($('<span>').html(item.label).addClass('linkit-result-line--title'));
  88. if (item.hasOwnProperty('description')) {
  89. $wrapper.append($('<span>').html(item.description).addClass('linkit-result-line--description'));
  90. }
  91. return $line.append($wrapper).appendTo(ul);
  92. }
  93. /**
  94. * Override jQuery UI _renderMenu function to handle groups.
  95. *
  96. * @param {object} ul
  97. * An empty <ul> element to use as the widget's menu.
  98. * @param {array} items
  99. * An Array of items that match the user typed term.
  100. */
  101. function renderMenu(ul, items) {
  102. var self = this.element.autocomplete('instance');
  103. var grouped_items = _.groupBy(items, function (item) {
  104. return item.hasOwnProperty('group') ? item.group : '';
  105. });
  106. $.each(grouped_items, function (group, items) {
  107. if (group.length) {
  108. ul.append('<li class="linkit-result-line--group ui-menu-divider">' + group + '</li>');
  109. }
  110. $.each(items, function (index, item) {
  111. self._renderItemData(ul, item);
  112. });
  113. });
  114. }
  115. function focusHandler() {
  116. return false;
  117. }
  118. function searchHandler(event) {
  119. var options = autocomplete.options;
  120. return !options.isComposing;
  121. }
  122. /**
  123. * Attaches the autocomplete behavior to all required fields.
  124. *
  125. * @type {Drupal~behavior}
  126. *
  127. * @prop {Drupal~behaviorAttach} attach
  128. * Attaches the autocomplete behaviors.
  129. * @prop {Drupal~behaviorDetach} detach
  130. * Detaches the autocomplete behaviors.
  131. */
  132. Drupal.behaviors.linkit_autocomplete = {
  133. attach: function (context) {
  134. // Act on textfields with the "form-linkit-autocomplete" class.
  135. var $autocomplete = $(context).find('input.form-linkit-autocomplete').once('linkit-autocomplete');
  136. if ($autocomplete.length) {
  137. $.widget('custom.autocomplete', $.ui.autocomplete, {
  138. _create: function () {
  139. this._super();
  140. this.widget().menu('option', 'items', '> :not(.linkit-result-line--group)');
  141. },
  142. _renderMenu: autocomplete.options.renderMenu,
  143. _renderItem: autocomplete.options.renderItem
  144. });
  145. // Use jQuery UI Autocomplete on the textfield.
  146. $autocomplete.autocomplete(autocomplete.options);
  147. $autocomplete.autocomplete('widget').addClass('linkit-ui-autocomplete');
  148. $autocomplete.click(function () {
  149. $autocomplete.autocomplete('search', $autocomplete.val());
  150. });
  151. $autocomplete.on('compositionstart.autocomplete', function () {
  152. autocomplete.options.isComposing = true;
  153. });
  154. $autocomplete.on('compositionend.autocomplete', function () {
  155. autocomplete.options.isComposing = false;
  156. });
  157. }
  158. },
  159. detach: function (context, settings, trigger) {
  160. if (trigger === 'unload') {
  161. $(context).find('input.form-linkit-autocomplete')
  162. .removeOnce('linkit-autocomplete')
  163. .autocomplete('destroy');
  164. }
  165. }
  166. };
  167. /**
  168. * Autocomplete object implementation.
  169. */
  170. autocomplete = {
  171. cache: {},
  172. options: {
  173. source: sourceData,
  174. focus: focusHandler,
  175. search: searchHandler,
  176. select: selectHandler,
  177. renderItem: renderItem,
  178. renderMenu: renderMenu,
  179. minLength: 1,
  180. isComposing: false
  181. },
  182. ajax: {
  183. dataType: 'json'
  184. }
  185. };
  186. })(jQuery, Drupal, _);