jQuery-UI multicomplete widget (based on autocomplete)

Posted: July 11th, 2010 | Author: | Filed under: Javascript, jQuery, Web Development | Tags: , , , , , | 5 Comments »

After writing my $.fn.autocomplete function to allow using jQuery’s UI autocomplete with multiple selections, I’ve rewritten it as a widget that extends the autocomplete widget. Basically, it works the exact same as autocomplete – other than allowing to select multiple values separated with a comma.

A few things were improved/changed in that version:

  • All options are settable, including all the callbacks that I’m modifying. your callbacks will be called first, and returning false from them will have effect.
  • All methods/getters/setters are available via multicomplete() (e.g. $(..).multicomplete(‘option’, ‘source’, ['foo', 'bar'])). When using multicomplete, setting/getting them via .autocomplete() will no longer work.
  • Events are prefixed with multicomplete instead of autocomplete (e.g. $(..).bind(‘multicompletesearch’, function(){…})). Using “autocomplete” as the prefix won’t work.
  • When you pass an array as the source, items that were already selected won’t show up in the autocomplete list.
  • When you pass a URL as the source, a new parameter named “selected” is passed in the AJAX request so your server-side code can know which items are already selected and not return them.

It should be noted that most of the handling is still done with jQuery-UI’s autocomplete, my multicomplete code just extends that widget and changes a few things. Everything should work as expected, and I don’t foresee any issues when new versions of jQuery UI are out – unless they make radical changes.

So here’s the demo (only works on the single post page):

Source code of demo:

$('#suggest').multicomplete({
	source: ["c++", "java", "php", "coldfusion", "javascript", "asp", "ruby", "python", "c", "scala", "groovy", "haskell", "perl"]
});

Source code of the multicomplete widget:

(function($ ,$a, $p){ // getting $p as a parameter doesn't require me to "var $p=..." and saves a two bytes ;-)  ("var " versus ",x" in argument list [when minifier is shrinking variables])
	$p=$a.prototype;
	$.widget('ui.multicomplete', $a, {
		// Search for everything after the last "," instead of the whole input contents
		_search: function(value){
			$p._search.call(this, value.match(/s*([^,]*)s*$/)[1]);
		},
		// Overwrite _trigger to make custom handling for events while still allowing user callbacks
		// Setting my own callbacks on this.options or binding using .bind() doesn't allow me to properly handle user callbacks, as this must be called AFTER the user callbacks are executed (which isn't possible by bind()ing when this.options[] is set)
		_trigger: function(type, event, data) {
			// call "real" triggers
			var ret = $p._trigger.apply(this, arguments);
			// When its select event, and user callback didn't return FALSE, do my handling and return false
			if (type == 'select' && ret !== false) {
				// When a selection is made, replace everything after the last "," with the selection instead of replacing everything
				var val=this.element.val();
				this.element.val(val.replace(/[^,]+$/,(val.indexOf(',') != -1 ?' ':'')+data.item.value + ', '));
				ret = false;
			}
			// Force false when its the focus event - parent should never set the value on focus
			return (type == 'focus' ? false : ret);
		},
		_create:function(){
			var self=this;
			// When menu item is selected and TAB is pressed focus should remain on current element to allow adding more values
			this.element.keydown(function(e){
				self.menu.active && e.keyCode == $.ui.keyCode.TAB && e.preventDefault();
			});
			$p._create.call(this);
		},
		_initSource: function() {
			// Change the way arrays are handled by making autocomplete think the user sent his own source callback instead of an array
			// The way autocomplete is written doesn't allow me to do it in a prettier way :(
			if ( $.isArray(this.options.source) ) {
				var array = this.options.source, self = this;
				this.options.source = function( request, response ) {
					response( self.filter(array, request) ); // Use our filter() and pass the entire request object so the filter can tell what's currently selected
				};
			}

			// call autocomplete._initSource to create this.source function according to user input
			$p._initSource.call(this);

			// Save a copy of current source() function, than new source() sets request.selected and delegate to original source
			var _source = this.source;
			this.source = function(request, response) {
				request.selected = this.element.val().split(/s*,s*/);
				request.selected.pop(); // don't include the term the user is currently writing as selected
				_source(request, response);
			};
			// TODO: instead of overwritting this.source, I can overwrite _search which is easier, but than I'll have to repeat jQuery-UI's code that might change
		},

		// Like $.ui.autocomplete.filter, but excludes items that are already selected
		filter: function(array, request) {
			return $.grep($a.filter(array, request.term),function(value){return $.inArray(value, request.selected) == -1;});
		}
	});
})(jQuery, jQuery.ui.autocomplete);

Minified:
(function($,f,g){g=f.prototype;$.widget(‘ui.multicomplete’,f,{_search:function(a){g._search.call(this,a.match(/s*([^,]*)s*$/)[1])},_trigger:function(a,b,c){var d=g._trigger.apply(this,arguments);if(a==’select’&&d!==false){var e=this.element.val();this.element.val(e.replace(/[^,]+$/,(e.indexOf(‘,’)!=-1?’ ‘:”)+c.item.value+’, ‘));d=false}return(a==’focus’?false:d)},_create:function(){var a=this;this.element.keydown(function(e){a.menu.active&&e.keyCode==$.ui.keyCode.TAB&&e.preventDefault()});g._create.call(this)}, _initSource:function(){if($.isArray(this.options.source)){var c=this.options.source,self=this;this.options.source=function(a,b){b(self.filter(c,a))}}g._initSource.call(this);var d=this.source;this.source=function(a,b){a.selected=this.element.val().split(/s*,s*/);a.selected.pop();d(a,b)}},filter:function(b,c){return $.grep(f.filter(b,c.term),function(a){return $.inArray(a,c.selected)==-1})}})})(jQuery,jQuery.ui.autocomplete);

Download: Full code with comments (3141 bytes) or Minified code (932 bytes)

Released under the MIT and GPL licenses.

P.S. I would like to thank ajpiano from #jquery (at freenode) for suggesting me to write this as a widget. I’ve learned a few things on jQuery/jQuery-UI in the process and I’ll definitely start writing some of my code as widgets, I really like the way they works.

Read the rest of this entry »


jQuery-UI autocomplete with multiple selections

Posted: July 10th, 2010 | Author: | Filed under: Javascript, jQuery, Web Development | Tags: , , , | 2 Comments »

For one of our projects we needed to have an auto-complete input field with multiple selections, separated with a comma.

I found quite a few plugins that can do that, but preferred to use the autocomplete feature that’s built-in to jQuery UI. To get it working, all that’s needed is modifying some events/callbacks (select, focus, source and keydown).

I’ve wrapped everything as $.fn.multiselect, that modifies what’s needed and lets the built-in autocomplete handle the rest.

You can pass it the same options as you would pass to autocomplete(), other than “select” and “focus” which are overwritten by multicomplete. It should be noted that you have to work with autocomplete() if you want to use their methods/getters/setter (e.g. $(..).autocomplete( “option”, “delay”, 500 ) or $(..).autocomplete(“close”);)

Update: this was rewritten as a widget. That version works too, but has some limitations. you should probably use the widget version instead.

You can see it in action here (try typing “a”):
(The demo only works when viewing the single-post page)

Source code of demo:

$('#suggest').multicomplete({
	source: ["c++", "java", "php", "coldfusion", "javascript", "asp", "ruby", "python", "c", "scala", "groovy", "haskell", "perl"]
});

Source code for $.fn.multicomplete:

(function($){
	$.fn.multicomplete = function(opt) {
		var $t = $(this);
		// When menu item is selected and TAB is pressed, focus should remain on current element to allow adding more values
		$t.bind('keydown', function(e) {
			if ($t.data('autocomplete').menu.active && e.keyCode == $.ui.keyCode.TAB) {
				e.preventDefault();
			}
		});

		// Call autocomplete() with our modified select/focus callbacks
		$t.autocomplete($.extend(opt,{
			// When a selection is made, replace everything after the last "," with the selection instead of replacing everything
			select: function(event,ui) {
				this.value = this.value.replace(/[^,]+$/,(this.value.indexOf(',') != -1 ?' ':'')+ui.item.value + ', ');
				return false;
			},
			// Disable replacing value on focus
			focus: function(){return false;}
		}));

		// Get the "source" callback that jQuery-UI prepared
		var $source = $t.data('autocomplete').source;

		// Modify the source callback to change request.term to everything after the last ",", than delegate to $source
		$t.autocomplete('option', 'source', function(request, response) {
			request.term = request.term.match(/s*([^,]*)s*$/)[1]; // get everything after the last "," and trim it
			$source(request, response);
		});
	};
})(jQuery);

Minified:
(function($){$.fn.multicomplete=function(c){var d=$(this);d.bind(‘keydown’,function(e){if(d.data(‘autocomplete’).menu.active&&e.keyCode==$.ui.keyCode.TAB){e.preventDefault()}}); d.autocomplete($.extend(c,{select:function(a,b){this.value=this.value.replace(/[^,]+$/,(this.value.indexOf(‘,’)!=-1?’ ‘:”)+b.item.value+’, ‘);return false},focus:function(){return false}}));var f=d.data(‘autocomplete’).source;d.autocomplete(‘option’,'source’,function(a,b){a.term=a.term.match(/s*([^,]*)s*$/)[1];f(a,b)})}})(jQuery);

Download: Full code with comments (1263 bytes) or Minified code (512 bytes)

Released under the MIT and GPL licenses.
Read the rest of this entry »


Internet Explorer “Unspecified Error” with jQuery and getBoundingClientRect

Posted: June 25th, 2010 | Author: | Filed under: Web Development | Tags: , , , , , , | No Comments »

I’ve been getting “Unspecified Error” errors from Internet Explorer with some jQuery code. I traced the cause to jQuery’s “getBoundingClientRect” which comes from a call to $(…).offset({top: X, left: T}).

After looking around in some forums/blogs, it seems the error is coming from some handling of internet explorer with offsetParent in some specific cases. to be honest, I don’t really understand what happens there…

But I did manage to fix it by simply calling $(…).css({top: X, left: Y}) instead of $(…).offset({top: X, left:Y}), which does pretty much the same in my case.

Just thought someone out there might find it useful…


Internet Explorer “Unspecified Error” with jQuery and getBoundingClientRect

Posted: May 8th, 2010 | Author: | Filed under: Web Development | Tags: , , , , , , | 1 Comment »

I’ve been getting “Unspecified Error” errors from Internet Explorer with some jQuery code. I traced the cause to jQuery’s “getBoundingClientRect” which comes from a call to $(…).offset({top: X, left: T}).

After looking around in some forums/blogs, it seems the error is coming from some handling of internet explorer with offsetParent in some specific cases. to be honest, I don’t really understand what happens there…

But I did manage to fix it by simply calling $(…).css({top: X, left: Y}) instead of $(…).offset({top: X, left:Y}), which does pretty much the same in my case.

Just thought someone out there might find it useful…


jQuery shake() function

Posted: December 29th, 2009 | Author: | Filed under: Web Development | Tags: , | No Comments »

I needed a quick function for jQuery to shake an absolute or static positioned (can be easily modified to support other positions too, but it doesn’t at the moment) element up&down that isn’t depended on jQuery UI (I’m using to shake simplemodal‘s container).

This is what I came up with that:

(function($){
	$.fn.shake=function(opt){
		opt=$.extend({times: 8,delay: 150,pixels: 20},opt||{});
		$(this).each(function(){
			var orig=parseInt($(this).css('top'));
			for (var i=0; i

Its usage looks like that:

$('#simplemodal-container').shake({times:8, delay:150, pixels:20});

All the parameters are optional, you could simply call $(..).shake() to use the defaults values or overwrite just some of the them.

times determines how many times to shake the element, delay is how long each animate() call should take, and pixels is the number of pixels to move up/down every time