Posted By

Huskie on 04/17/12


Tagged

select jquery load lists ui Large loading ComboBox autocomplete very slow


Versions (?)

Who likes this?

1 person have marked this snippet as a favorite

BrockSamsom


Speed up jQuery UI Autocomplete Combobox with very large select lists


 / Published in: jQuery
 

URL: http://darrenhuskie.com/

With the current combobox implementation, the full list is emptied and re-rendered every time you expand the dropdown. Also you are stuck with setting the minLength to 0, because it has to do an empty search to get the full list.

Here is my own implementation extending the autocomplete widget. It renders the full list just once, and reuses it whenever the dropdown button is clicked. This also removes the dependence of the option minLength = 0. It also works with arrays, and ajax as list source. Also if you have multiple large list, the widget initialization is added to a queue so it can run in the background, and not freeze the browser.

  1. (function ($) {
  2. $.widget('ui.combobox', $.ui.autocomplete,
  3. {
  4. options: {
  5. minLength: 2,
  6. ajaxGetAll: { get: 'all' }
  7. },
  8.  
  9. _create: function () {
  10. if (this.element.is('SELECT')) {
  11. this._selectInit();
  12. return;
  13. }
  14.  
  15. $.ui.autocomplete.prototype._create.call(this);
  16. var input = this.element;
  17. input.addClass('ui-widget ui-widget-content ui-corner-left');
  18.  
  19. this.button = $('<button type="button">&nbsp;</button>')
  20. .attr('tabIndex', -1)
  21. .attr('title', 'Show All Items')
  22. .insertAfter(input)
  23. .button({
  24. icons: { primary: 'ui-icon-triangle-1-s' },
  25. text: false
  26. })
  27. .removeClass('ui-corner-all')
  28. .addClass('ui-corner-right ui-button-icon')
  29. .click(function (event) {
  30. if (input.combobox('widget').is(':visible')) {
  31. input.combobox('close');
  32. return;
  33. }
  34. var data = input.data('combobox');
  35. clearTimeout(data.closing);
  36. if (!input.isFullMenu) {
  37. data._swapMenu();
  38. input.isFullMenu = true;
  39. }
  40. input.combobox('widget').css('display', 'block')
  41. .position($.extend({ of: input },
  42. data.options.position
  43. ));
  44. input.focus();
  45. data._trigger('open');
  46. });
  47.  
  48. $(document).queue(function () {
  49. var data = input.data('combobox');
  50. if ($.isArray(data.options.source)) {
  51. $.ui.combobox.prototype._renderFullMenu.call(data, data.options.source);
  52. } else if (typeof data.options.source === 'string') {
  53. $.getJSON(data.options.source, data.options.ajaxGetAll, function (source) {
  54. $.ui.combobox.prototype._renderFullMenu.call(data, source);
  55. });
  56. } else {
  57. $.ui.combobox.prototype._renderFullMenu.call(data, data.source());
  58. }
  59. });
  60. },
  61.  
  62. _renderFullMenu: function (source) {
  63. var self = this,
  64. input = this.element,
  65. ul = input.data('combobox').menu.element,
  66. lis = [];
  67. source = this._normalize(source);
  68. input.data('combobox').menuAll = input.data('combobox').menu.element.clone(true).appendTo('body');
  69. for (var i = 0; i < source.length; i++) {
  70. lis[i] = '<li class="ui-menu-item" role="menuitem"><a class="ui-corner-all" tabindex="-1">' + source[i].label + '</a></li>';
  71. }
  72. ul.append(lis.join(''));
  73. this._resizeMenu();
  74. setTimeout(function () {
  75. self._setupMenuItem.call(self, ul.children('li'), source);
  76. }, 0);
  77. input.isFullMenu = true;
  78. },
  79.  
  80. _setupMenuItem: function (items, source) {
  81. var self = this,
  82. itemsChunk = items.splice(0, 500),
  83. sourceChunk = source.splice(0, 500);
  84. for (var i = 0; i < itemsChunk.length; i++) {
  85. $(itemsChunk[i])
  86. .data('item.autocomplete', sourceChunk[i])
  87. .mouseenter(function (event) {
  88. self.menu.activate(event, $(this));
  89. })
  90. .mouseleave(function () {
  91. self.menu.deactivate();
  92. });
  93. }
  94. if (items.length > 0) {
  95. setTimeout(function () {
  96. self._setupMenuItem.call(self, items, source);
  97. }, 0);
  98. } else {
  99. $(document).dequeue();
  100. }
  101. },
  102.  
  103. _renderItem: function (ul, item) {
  104. var label = item.label.replace(new RegExp(
  105. '(?![^&;]+;)(?!<[^<>]*)(' + $.ui.autocomplete.escapeRegex(this.term) +
  106. ')(?![^<>]*>)(?![^&;]+;)', 'gi'), '<strong>$1</strong>');
  107. return $('<li></li>')
  108. .data('item.autocomplete', item)
  109. .append('<a>' + label + '</a>')
  110. .appendTo(ul);
  111. },
  112.  
  113. destroy: function () {
  114. if (this.element.is('SELECT')) {
  115. this.input.remove();
  116. this.element.removeData().show();
  117. return;
  118. }
  119. $.ui.autocomplete.prototype.destroy.call(this);
  120. this.element.removeClass('ui-widget ui-widget-content ui-corner-left');
  121. this.button.remove();
  122. },
  123.  
  124. search: function (value, event) {
  125. var input = this.element;
  126. if (input.isFullMenu) {
  127. this._swapMenu();
  128. input.isFullMenu = false;
  129. }
  130. $.ui.autocomplete.prototype.search.call(this, value, event);
  131. },
  132.  
  133. _change: function (event) {
  134. abc = this;
  135. if (!this.selectedItem) {
  136. var matcher = new RegExp('^' + $.ui.autocomplete.escapeRegex(this.element.val()) + '$', 'i'),
  137. match = $.grep(this.options.source, function (value) {
  138. return matcher.test(value.label);
  139. });
  140. if (match.length) {
  141. match[0].option.selected = true;
  142. } else {
  143. this.element.val('');
  144. if (this.options.selectElement) {
  145. this.options.selectElement.val('');
  146. }
  147. }
  148. }
  149. $.ui.autocomplete.prototype._change.call(this, event);
  150. },
  151.  
  152. _swapMenu: function () {
  153. var input = this.element,
  154. data = input.data('combobox'),
  155. tmp = data.menuAll;
  156. data.menuAll = data.menu.element.hide();
  157. data.menu.element = tmp;
  158. },
  159.  
  160. _selectInit: function () {
  161. var select = this.element.hide(),
  162. selected = select.children(':selected'),
  163. value = selected.val() ? selected.text() : '';
  164. this.options.source = select.children('option[value!=""]').map(function () {
  165. return { label: $.trim(this.text), option: this };
  166. }).toArray();
  167. var userSelectCallback = this.options.select;
  168. var userSelectedCallback = this.options.selected;
  169. this.options.select = function (event, ui) {
  170. ui.item.option.selected = true;
  171. if (userSelectCallback) userSelectCallback(event, ui);
  172. if (userSelectedCallback) userSelectedCallback(event, ui);
  173. };
  174. this.options.selectElement = select;
  175. this.input = $('<input>').insertAfter(select)
  176. .val(value).combobox(this.options);
  177. }
  178. }
  179. );
  180. })(jQuery);

Report this snippet  

Comments

RSS Icon Subscribe to comments
Posted By: sameersinghaswal on September 19, 2012

Thanks a lot, for the solution. How to implement search, matching with start of string. I mean when I type "ap" the filtered options should be "Apple", "Application","Ape" and the options like "Gap","Map" or "RedApple" should not come in filtered results.

Posted By: eldho_10 on January 7, 2013

How can i change the data source of the combo box ? now when i change the list it is not reflecting and shows only initially loaded data

You need to login to post a comment.