Return to Snippet

Revision: 56826
at April 17, 2012 19:56 by Huskie


Initial Code
(function ($) {
	$.widget('ui.combobox', $.ui.autocomplete,
		{
			options: {
        		minLength: 2,
        		ajaxGetAll: { get: 'all' }
			},

        	_create: function () {
        		if (this.element.is('SELECT')) {
        			this._selectInit();
        			return;
        		}

        		$.ui.autocomplete.prototype._create.call(this);
        		var input = this.element;
        		input.addClass('ui-widget ui-widget-content ui-corner-left');

        		this.button = $('<button type="button">&nbsp;</button>')
            .attr('tabIndex', -1)
            .attr('title', 'Show All Items')
            .insertAfter(input)
            .button({
            	icons: { primary: 'ui-icon-triangle-1-s' },
            	text: false
            })
            .removeClass('ui-corner-all')
            .addClass('ui-corner-right ui-button-icon')
            .click(function (event) {
					if (input.combobox('widget').is(':visible')) {
            		input.combobox('close');
            		return;
            	}
            	var data = input.data('combobox');
            	clearTimeout(data.closing);
            	if (!input.isFullMenu) {
            		data._swapMenu();
            		input.isFullMenu = true;
            	}
            	input.combobox('widget').css('display', 'block')
                .position($.extend({ of: input },
                    data.options.position
                    ));
            	input.focus();
            	data._trigger('open');
            });

        		$(document).queue(function () {
        			var data = input.data('combobox');
        			if ($.isArray(data.options.source)) {
        				$.ui.combobox.prototype._renderFullMenu.call(data, data.options.source);
        			} else if (typeof data.options.source === 'string') {
        				$.getJSON(data.options.source, data.options.ajaxGetAll, function (source) {
        					$.ui.combobox.prototype._renderFullMenu.call(data, source);
        				});
        			} else {
        				$.ui.combobox.prototype._renderFullMenu.call(data, data.source());
        			}
        		});
        	},

        	_renderFullMenu: function (source) {
        		var self = this,
                input = this.element,
                ul = input.data('combobox').menu.element,
                lis = [];
        		source = this._normalize(source);
        		input.data('combobox').menuAll = input.data('combobox').menu.element.clone(true).appendTo('body');
        		for (var i = 0; i < source.length; i++) {
        			lis[i] = '<li class="ui-menu-item" role="menuitem"><a class="ui-corner-all" tabindex="-1">' + source[i].label + '</a></li>';
        		}
        		ul.append(lis.join(''));
        		this._resizeMenu();
        		setTimeout(function () {
        			self._setupMenuItem.call(self, ul.children('li'), source);
        		}, 0);
        		input.isFullMenu = true;
        	},

        	_setupMenuItem: function (items, source) {
        		var self = this,
                itemsChunk = items.splice(0, 500),
                sourceChunk = source.splice(0, 500);
        		for (var i = 0; i < itemsChunk.length; i++) {
        			$(itemsChunk[i])
                .data('item.autocomplete', sourceChunk[i])
                .mouseenter(function (event) {
                	self.menu.activate(event, $(this));
                })
                .mouseleave(function () {
                	self.menu.deactivate();
                });
        		}
        		if (items.length > 0) {
        			setTimeout(function () {
        				self._setupMenuItem.call(self, items, source);
        			}, 0);
        		} else {
        			$(document).dequeue();
        		}
        	},

        	_renderItem: function (ul, item) {
        		var label = item.label.replace(new RegExp(
                '(?![^&;]+;)(?!<[^<>]*)(' + $.ui.autocomplete.escapeRegex(this.term) +
                ')(?![^<>]*>)(?![^&;]+;)', 'gi'), '<strong>$1</strong>');
        		return $('<li></li>')
                .data('item.autocomplete', item)
                .append('<a>' + label + '</a>')
                .appendTo(ul);
        	},

        	destroy: function () {
        		if (this.element.is('SELECT')) {
        			this.input.remove();
        			this.element.removeData().show();
        			return;
        		}
        		$.ui.autocomplete.prototype.destroy.call(this);
        		this.element.removeClass('ui-widget ui-widget-content ui-corner-left');
        		this.button.remove();
        	},

        	search: function (value, event) {
        		var input = this.element;
        		if (input.isFullMenu) {
        			this._swapMenu();
        			input.isFullMenu = false;
        		}
        		$.ui.autocomplete.prototype.search.call(this, value, event);
        	},

        	_change: function (event) {
        		abc = this;
        		if (!this.selectedItem) {
        			var matcher = new RegExp('^' + $.ui.autocomplete.escapeRegex(this.element.val()) + '$', 'i'),
                    match = $.grep(this.options.source, function (value) {
                    	return matcher.test(value.label);
                    });
        			if (match.length) {
        				match[0].option.selected = true;
        			} else {
        				this.element.val('');
        				if (this.options.selectElement) {
        					this.options.selectElement.val('');
        				}
        			}
        		}
        		$.ui.autocomplete.prototype._change.call(this, event);
        	},

        	_swapMenu: function () {
        		var input = this.element,
                data = input.data('combobox'),
                tmp = data.menuAll;
        		data.menuAll = data.menu.element.hide();
        		data.menu.element = tmp;
        	},

        	_selectInit: function () {
        		var select = this.element.hide(),
            selected = select.children(':selected'),
            value = selected.val() ? selected.text() : '';
        		this.options.source = select.children('option[value!=""]').map(function () {
        			return { label: $.trim(this.text), option: this };
        		}).toArray();
        		var userSelectCallback = this.options.select;
        		var userSelectedCallback = this.options.selected;
        		this.options.select = function (event, ui) {
        			ui.item.option.selected = true;
        			if (userSelectCallback) userSelectCallback(event, ui);
        			if (userSelectedCallback) userSelectedCallback(event, ui);
        		};
        		this.options.selectElement = select;
        		this.input = $('<input>').insertAfter(select)
                .val(value).combobox(this.options);
        	}
        }
	);
})(jQuery);

Initial URL
http://darrenhuskie.com/

Initial Description
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.

Initial Title
Speed up jQuery UI Autocomplete Combobox with very large select lists

Initial Tags
jquery, load

Initial Language
jQuery