Skip to content
Extraits de code Groupes Projets
jquery.autocomplete-custom.js 19,8 ko
Newer Older
  • Learn to ignore specific revisions
  • Raphael's avatar
    Raphael a validé
    /*
     * Autocomplete - jQuery plugin 1.1pre
     *
     * Copyright (c) 2007 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, Jörn Zaefferer
     *
     * Dual licensed under the MIT and GPL licenses:
     *   http://www.opensource.org/licenses/mit-license.php
     *   http://www.gnu.org/licenses/gpl.html
     *
     * Revision: $Id: jquery.autocomplete.js 5785 2008-07-12 10:37:33Z joern.zaefferer $
    
    Raphael's avatar
    Raphael a validé
     * Modified by Diaspora
    
    Raphael's avatar
    Raphael a validé
     */
    
    ;(function($) {
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
    $.fn.extend({
    	autocomplete: function(urlOrData, options) {
    		var isUrl = typeof urlOrData == "string";
    		options = $.extend({}, $.Autocompleter.defaults, {
    			url: isUrl ? urlOrData : null,
    			data: isUrl ? null : urlOrData,
    			delay: isUrl ? $.Autocompleter.defaults.delay : 10,
    			max: options && !options.scroll ? 10 : 150
    		}, options);
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
    		// if highlight is set to false, replace it with a do-nothing function
    		options.highlight = options.highlight || function(value) { return value; };
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
    		// if the formatMatch option is not specified, then use formatItem for backwards compatibility
    		options.formatMatch = options.formatMatch || options.formatItem;
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
    		return this.each(function() {
    			new $.Autocompleter(this, options);
    		});
    	},
    	result: function(handler) {
    		return this.bind("result", handler);
    	},
    	search: function(handler) {
    		return this.trigger("search", [handler]);
    	},
    	flushCache: function() {
    		return this.trigger("flushCache");
    	},
    	setOptions: function(options){
    		return this.trigger("setOptions", [options]);
    	},
    	unautocomplete: function() {
    		return this.trigger("unautocomplete");
    	}
    });
    
    $.Autocompleter = function(input, options) {
    
    	var KEY = {
    		UP: 38,
    		DOWN: 40,
    		DEL: 46,
    		TAB: 9,
    		RETURN: 13,
    		ESC: 27,
    		COMMA: 188,
    		PAGEUP: 33,
    		PAGEDOWN: 34,
    		BACKSPACE: 8
    	};
    
    	// Create $ object for input element
    	var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass);
    
    	var timeout;
    	var previousValue = "";
    	var cache = $.Autocompleter.Cache(options);
    	var hasFocus = 0;
    	var lastKeyPressCode;
    	var config = {
    		mouseDownOnSelect: false
    	};
    	var select = $.Autocompleter.Select(options, input, selectCurrent, config);
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
    	var blockSubmit;
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
    	// prevent form submit in opera when selecting with return key
    	$.browser.opera && $(input.form).bind("submit.autocomplete", function() {
    		if (blockSubmit) {
    			blockSubmit = false;
    			return false;
    		}
    	});
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
    	// only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all
    	$input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) {
    		// track last key pressed
    		lastKeyPressCode = event.keyCode;
    		switch(event.keyCode) {
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
    			case KEY.UP:
    				event.preventDefault();
    				if ( select.visible() ) {
    					select.prev();
    				} else {
    					onChange(0, true);
    				}
    				break;
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
    			case KEY.DOWN:
    				event.preventDefault();
    				if ( select.visible() ) {
    					select.next();
    				} else {
    					onChange(0, true);
    				}
    				break;
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
    			case KEY.PAGEUP:
    				event.preventDefault();
    				if ( select.visible() ) {
    					select.pageUp();
    				} else {
    					onChange(0, true);
    				}
    				break;
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
    			case KEY.PAGEDOWN:
    				event.preventDefault();
    				if ( select.visible() ) {
    					select.pageDown();
    				} else {
    					onChange(0, true);
    				}
    				break;
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
    			// matches also semicolon
    			case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA:
    			case KEY.TAB:
    			case KEY.RETURN:
    				if( selectCurrent() ) {
    					// stop default to prevent a form submit, Opera needs special handling
    					event.preventDefault();
    					blockSubmit = true;
    					return false;
    				}
    				break;
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
    			case KEY.ESC:
    				select.hide();
    				break;
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
    			default:
    
            options.onLetterTyped(event, $input);
    
    Raphael's avatar
    Raphael a validé
    				clearTimeout(timeout);
    				timeout = setTimeout(onChange, options.delay);
    				break;
    		}
    	}).focus(function(){
    		// track whether the field has focus, we shouldn't process any
    		// results if the field no longer has focus
    		hasFocus++;
    	}).blur(function() {
    		hasFocus = 0;
    		if (!config.mouseDownOnSelect) {
    			hideResults();
    		}
    	}).click(function() {
    		// show select when clicking in a focused field
    		if ( hasFocus++ > 1 && !select.visible() ) {
    			onChange(0, true);
    		}
    	}).bind("search", function() {
    		// TODO why not just specifying both arguments?
    		var fn = (arguments.length > 1) ? arguments[1] : null;
    		function findValueCallback(q, data) {
    			var result;
    			if( data && data.length ) {
    				for (var i=0; i < data.length; i++) {
    					if( data[i].result.toLowerCase() == q.toLowerCase() ) {
    						result = data[i];
    						break;
    					}
    				}
    			}
    			if( typeof fn == "function" ) fn(result);
    			else $input.trigger("result", result && [result.data, result.value]);
    		}
    		$.each(trimWords($input.val()), function(i, value) {
    			request(value, findValueCallback, findValueCallback);
    		});
    	}).bind("flushCache", function() {
    		cache.flush();
    	}).bind("setOptions", function() {
    		$.extend(options, arguments[1]);
    		// if we've updated the data, repopulate
    		if ( "data" in arguments[1] )
    			cache.populate();
    	}).bind("unautocomplete", function() {
    		select.unbind();
    		$input.unbind();
    		$(input.form).unbind(".autocomplete");
    	});
    
    Raphael's avatar
    Raphael a validé
    	function selectCurrent() {
    		var selected = select.selected();
    		if( !selected )
    			return false;
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
    		var v = selected.result;
    		previousValue = v;
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
    		if ( options.multiple ) {
    			var words = trimWords($input.val());
    			if ( words.length > 1 ) {
    				v = words.slice(0, words.length - 1).join( options.multipleSeparator ) + options.multipleSeparator + v;
    			}
    			v += options.multipleSeparator;
    		}
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
    		hideResultsNow();
    
        options.onSelect($input, selected.data, selected.value);
    
    Raphael's avatar
    Raphael a validé
    		return true;
    	}
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
    	function onChange(crap, skipPrevCheck) {
    		if( lastKeyPressCode == KEY.DEL ) {
    			select.hide();
    			return;
    		}
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
    		var currentValue = $input.val();
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
    		if ( !skipPrevCheck && currentValue == previousValue )
    			return;
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
    		previousValue = currentValue;
    
    Raphael's avatar
    Raphael a validé
    
    
    		currentValue = options.searchTermFromValue(currentValue, $input[0].selectionStart);
    
    Raphael's avatar
    Raphael a validé
    		if ( currentValue.length >= options.minChars) {
    			$input.addClass(options.loadingClass);
    			if (!options.matchCase)
    				currentValue = currentValue.toLowerCase();
    			request(currentValue, receiveData, hideResultsNow);
    		} else {
    			stopLoading();
    			select.hide();
    		}
    	};
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
    	function trimWords(value) {
    		if ( !value ) {
    			return [""];
    		}
    		var words = value.split( options.multipleSeparator );
    		var result = [];
    		$.each(words, function(i, value) {
    			if ( $.trim(value) )
    				result[i] = $.trim(value);
    		});
    		return result;
    	}
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
    	// fills in the input box w/the first match (assumed to be the best match)
    	// q: the term entered
    	// sValue: the first matching result
    	function autoFill(q, sValue){
    		// autofill in the complete box w/the first match as long as the user hasn't entered in more data
    		// if the last user key pressed was backspace, don't autofill
    
    		if( options.autoFill && (options.lastWord($input.val(), null, options.multiple).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) {
    
    Raphael's avatar
    Raphael a validé
    			// fill in the value (keep the case the user has typed)
    
    			$input.val($input.val() + sValue.substring(options.lastWord(previousValue, null, options.multiple).length));
    
    Raphael's avatar
    Raphael a validé
    			// select the portion of the value not typed by the user (so the next character will erase)
    			$.Autocompleter.Selection(input, previousValue.length, previousValue.length + sValue.length);
    		}
    	};
    
    	function hideResults() {
    		clearTimeout(timeout);
    		timeout = setTimeout(hideResultsNow, 200);
    	};
    
    	function hideResultsNow() {
    		var wasVisible = select.visible();
    		select.hide();
    		clearTimeout(timeout);
    		stopLoading();
    		if (options.mustMatch) {
    			// call search and run callback
    			$input.search(
    				function (result){
    					// if no value found, clear the input box
    					if( !result ) {
    						if (options.multiple) {
    							var words = trimWords($input.val()).slice(0, -1);
    							$input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") );
    						}
    						else
    							$input.val( "" );
    					}
    				}
    			);
    		}
    		if (wasVisible)
    			// position cursor at end of input field
    			$.Autocompleter.Selection(input, input.value.length, input.value.length);
    	};
    
    	function receiveData(q, data) {
    		if ( data && data.length && hasFocus ) {
    			stopLoading();
    			select.display(data, q);
    			autoFill(q, data[0].value);
    			select.show();
    		} else {
    			hideResultsNow();
    		}
    	};
    
    	function request(term, success, failure) {
    		if (!options.matchCase)
    			term = term.toLowerCase();
    		var data = cache.load(term);
    		// recieve the cached data
    		if (data && data.length) {
    			success(term, data);
    		// if an AJAX url has been supplied, try loading the data now
    		} else if( (typeof options.url == "string") && (options.url.length > 0) ){
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
    			var extraParams = {
    				timestamp: +new Date()
    			};
    			$.each(options.extraParams, function(key, param) {
    				extraParams[key] = typeof param == "function" ? param() : param;
    			});
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
    			$.ajax({
    				// try to leverage ajaxQueue plugin to abort previous requests
    				mode: "abort",
    				// limit abortion to this input
    				port: "autocomplete" + input.name,
    				dataType: options.dataType,
    				url: options.url,
    				data: $.extend({
    
    					q: options.lastWord(term, null, options.multiple),
    
    Raphael's avatar
    Raphael a validé
    					limit: options.max
    				}, extraParams),
    				success: function(data) {
    					var parsed = options.parse && options.parse(data) || parse(data);
    					cache.add(term, parsed);
    					success(term, parsed);
    				}
    			});
    		} else {
    			// if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match
    			select.emptyList();
    			failure(term);
    		}
    	};
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
    	function parse(data) {
    		var parsed = [];
    		var rows = data.split("\n");
    		for (var i=0; i < rows.length; i++) {
    			var row = $.trim(rows[i]);
    			if (row) {
    				row = row.split("|");
    				parsed[parsed.length] = {
    					data: row,
    					value: row[0],
    					result: options.formatResult && options.formatResult(row, row[0]) || row[0]
    				};
    			}
    		}
    		return parsed;
    	};
    
    	function stopLoading() {
    		$input.removeClass(options.loadingClass);
    	};
    
    };
    
    $.Autocompleter.defaults = {
    
      onLetterTyped : function(event){},
      lastWord : function(value, crap, multiple) {
    		if ( !multiple )
    			return value;
    		var words = trimWords(value);
    		return words[words.length - 1];
    	},
    
    Raphael's avatar
    Raphael a validé
    	inputClass: "ac_input",
    	resultsClass: "ac_results",
    	loadingClass: "ac_loading",
    
      onSelect: function(input, data, formatted){
        input.val(formatted);
      },
    
    Raphael's avatar
    Raphael a validé
    	minChars: 1,
    	delay: 400,
    	matchCase: false,
    	matchSubset: true,
    	matchContains: false,
    	cacheLength: 10,
    	max: 100,
    	mustMatch: false,
    	extraParams: {},
    	selectFirst: true,
    	formatItem: function(row) { return row[0]; },
    
    Raphael's avatar
    Raphael a validé
      selectionChanged : function(newItem) {},
    
    Raphael's avatar
    Raphael a validé
    	formatMatch: null,
    	autoFill: false,
    	width: 0,
    	multiple: false,
    	multipleSeparator: ", ",
    	highlight: function(value, term) {
    		return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>");
    	},
        scroll: true,
        scrollHeight: 180
    };
    
    $.Autocompleter.defaults.searchTermFromValue = $.Autocompleter.defaults.lastWord;
    
    Raphael's avatar
    Raphael a validé
    
    $.Autocompleter.Cache = function(options) {
    
    	var data = {};
    	var length = 0;
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
    	function matchSubset(s, sub) {
    
    Raphael's avatar
    Raphael a validé
    		if (!options.matchCase)
    
    Raphael's avatar
    Raphael a validé
    			s = s.toLowerCase();
    		var i = s.indexOf(sub);
    		if (options.matchContains == "word"){
    			i = s.toLowerCase().search("\\b" + sub.toLowerCase());
    		}
    		if (i == -1) return false;
    		return i == 0 || options.matchContains;
    	};
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
    	function add(q, value) {
    		if (length > options.cacheLength){
    			flush();
    		}
    
    Raphael's avatar
    Raphael a validé
    		if (!data[q]){
    
    Raphael's avatar
    Raphael a validé
    			length++;
    		}
    		data[q] = value;
    	}
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
    	function populate(){
    		if( !options.data ) return false;
    		// track the matches
    		var stMatchSets = {},
    			nullData = 0;
    
    		// no url was specified, we need to adjust the cache length to make sure it fits the local data store
    		if( !options.url ) options.cacheLength = 1;
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
    		// track all options for minChars = 0
    		stMatchSets[""] = [];
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
    		// loop through the array and create a lookup structure
    		for ( var i = 0, ol = options.data.length; i < ol; i++ ) {
    			var rawValue = options.data[i];
    			// if rawValue is a string, make an array otherwise just reference the array
    			rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue;
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
    			var value = options.formatMatch(rawValue, i+1, options.data.length);
    			if ( value === false )
    				continue;
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
    			var firstChar = value.charAt(0).toLowerCase();
    			// if no lookup array for this character exists, look it up now
    
    Raphael's avatar
    Raphael a validé
    			if( !stMatchSets[firstChar] )
    
    Raphael's avatar
    Raphael a validé
    				stMatchSets[firstChar] = [];
    
    			// if the match is a string
    			var row = {
    				value: value,
    				data: rawValue,
    				result: options.formatResult && options.formatResult(rawValue) || value
    			};
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
    			// push the current match into the set list
    			stMatchSets[firstChar].push(row);
    
    			// keep track of minChars zero items
    			if ( nullData++ < options.max ) {
    				stMatchSets[""].push(row);
    			}
    		};
    
    		// add the data items to the cache
    		$.each(stMatchSets, function(i, value) {
    			// increase the cache size
    			options.cacheLength++;
    			// add to the cache
    			add(i, value);
    		});
    	}
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
    	// populate any existing data
    	setTimeout(populate, 25);
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
    	function flush(){
    		data = {};
    		length = 0;
    	}
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
    	return {
    		flush: flush,
    		add: add,
    		populate: populate,
    		load: function(q) {
    			if (!options.cacheLength || !length)
    				return null;
    
    Raphael's avatar
    Raphael a validé
    			/*
    
    Raphael's avatar
    Raphael a validé
    			 * if dealing w/local data and matchContains than we must make sure
    			 * to loop through all the data collections looking for matches
    			 */
    			if( !options.url && options.matchContains ){
    				// track all matches
    				var csub = [];
    				// loop through all the data grids for matches
    				for( var k in data ){
    					// don't search through the stMatchSets[""] (minChars: 0) cache
    					// this prevents duplicates
    					if( k.length > 0 ){
    						var c = data[k];
    						$.each(c, function(i, x) {
    							// if we've got a match, add it to the array
    							if (matchSubset(x.value, q)) {
    								csub.push(x);
    							}
    						});
    					}
    
    Raphael's avatar
    Raphael a validé
    				}
    
    Raphael's avatar
    Raphael a validé
    				return csub;
    
    Raphael's avatar
    Raphael a validé
    			} else
    
    Raphael's avatar
    Raphael a validé
    			// if the exact item exists, use it
    			if (data[q]){
    				return data[q];
    			} else
    			if (options.matchSubset) {
    				for (var i = q.length - 1; i >= options.minChars; i--) {
    					var c = data[q.substr(0, i)];
    					if (c) {
    						var csub = [];
    						$.each(c, function(i, x) {
    							if (matchSubset(x.value, q)) {
    								csub[csub.length] = x;
    							}
    						});
    						return csub;
    					}
    				}
    			}
    			return null;
    		}
    	};
    };
    
    $.Autocompleter.Select = function (options, input, select, config) {
    	var CLASSES = {
    		ACTIVE: "ac_over"
    	};
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
    	var listItems,
    		active = -1,
    		data,
    		term = "",
    		needsInit = true,
    		element,
    		list;
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
    	// Create results
    	function init() {
    		if (!needsInit)
    			return;
    		element = $("<div/>")
    		.hide()
    		.addClass(options.resultsClass)
    		.css("position", "absolute")
    		.appendTo(document.body);
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
    		list = $("<ul/>").appendTo(element).mouseover( function(event) {
    			if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') {
    	            active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event));
    
    Raphael's avatar
    Raphael a validé
    			    $(target(event)).addClass(CLASSES.ACTIVE);
    
    Raphael's avatar
    Raphael a validé
    	        }
    		}).click(function(event) {
    			$(target(event)).addClass(CLASSES.ACTIVE);
    			select();
    			// TODO provide option to avoid setting focus again after selection? useful for cleanup-on-focus
    			input.focus();
    			return false;
    		}).mousedown(function() {
    			config.mouseDownOnSelect = true;
    		}).mouseup(function() {
    			config.mouseDownOnSelect = false;
    		});
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
    		if( options.width > 0 )
    			element.css("width", options.width);
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
    		needsInit = false;
    
    Raphael's avatar
    Raphael a validé
    	}
    
    
    Raphael's avatar
    Raphael a validé
    	function target(event) {
    		var element = event.target;
    		while(element && element.tagName != "LI")
    			element = element.parentNode;
    		// more fun with IE, sometimes event.target is empty, just ignore it then
    		if(!element)
    			return [];
    		return element;
    	}
    
    	function moveSelect(step) {
    
    Raphael's avatar
    Raphael a validé
    	  listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE);
    
    Raphael's avatar
    Raphael a validé
    		movePosition(step);
            var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE);
            if(options.scroll) {
                var offset = 0;
                listItems.slice(0, active).each(function() {
    				offset += this.offsetHeight;
    			});
                if((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) {
                    list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight());
                } else if(offset < list.scrollTop()) {
                    list.scrollTop(offset);
                }
            }
    
    Raphael's avatar
    Raphael a validé
        options.selectionChanged(activeItem);
    
    Raphael's avatar
    Raphael a validé
    	};
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
    	function movePosition(step) {
    		active += step;
    		if (active < 0) {
    			active = listItems.size() - 1;
    		} else if (active >= listItems.size()) {
    			active = 0;
    		}
    	}
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
    	function limitNumberOfItems(available) {
    		return options.max && options.max < available
    			? options.max
    			: available;
    	}
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
    	function fillList() {
    		list.empty();
    		var max = limitNumberOfItems(data.length);
    		for (var i=0; i < max; i++) {
    			if (!data[i])
    				continue;
    			var formatted = options.formatItem(data[i].data, i+1, max, data[i].value, term);
    			if ( formatted === false )
    				continue;
    			var li = $("<li/>").html( options.highlight(formatted, term) ).addClass(i%2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0];
    			$.data(li, "ac_data", data[i]);
    		}
    		listItems = list.find("li");
    		if ( options.selectFirst ) {
    			listItems.slice(0, 1).addClass(CLASSES.ACTIVE);
    			active = 0;
    		}
    		// apply bgiframe if available
    		if ( $.fn.bgiframe )
    			list.bgiframe();
    	}
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
    	return {
    		display: function(d, q) {
    			init();
    			data = d;
    			term = q;
    			fillList();
    		},
    		next: function() {
    			moveSelect(1);
    		},
    		prev: function() {
    			moveSelect(-1);
    		},
    		pageUp: function() {
    			if (active != 0 && active - 8 < 0) {
    				moveSelect( -active );
    			} else {
    				moveSelect(-8);
    			}
    		},
    		pageDown: function() {
    			if (active != listItems.size() - 1 && active + 8 > listItems.size()) {
    				moveSelect( listItems.size() - 1 - active );
    			} else {
    				moveSelect(8);
    			}
    		},
    		hide: function() {
    			element && element.hide();
    			listItems && listItems.removeClass(CLASSES.ACTIVE);
    			active = -1;
    		},
    		visible : function() {
    			return element && element.is(":visible");
    		},
    		current: function() {
    			return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]);
    		},
    		show: function() {
    			var offset = $(input).offset();
    			element.css({
    				width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(),
    				top: offset.top + input.offsetHeight,
    				left: offset.left
    			}).show();
                if(options.scroll) {
                    list.scrollTop(0);
                    list.css({
    					maxHeight: options.scrollHeight,
    					overflow: 'auto'
    				});
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
                    if($.browser.msie && typeof document.body.style.maxHeight === "undefined") {
    					var listHeight = 0;
    					listItems.each(function() {
    						listHeight += this.offsetHeight;
    					});
    					var scrollbarsVisible = listHeight > options.scrollHeight;
                        list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight );
    					if (!scrollbarsVisible) {
    						// IE doesn't recalculate width when scrollbar disappears
    						listItems.width( list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")) );
    					}
                    }
    
    Raphael's avatar
    Raphael a validé
    
    
    Raphael's avatar
    Raphael a validé
                }
    		},
    		selected: function() {
    			var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);
    			return selected && selected.length && $.data(selected[0], "ac_data");
    		},
    		emptyList: function (){
    			list && list.empty();
    		},
    		unbind: function() {
    			element && element.remove();
    		}
    	};
    };
    
    $.Autocompleter.Selection = function(field, start, end) {
    	if( field.createTextRange ){
    		var selRange = field.createTextRange();
    		selRange.collapse(true);
    		selRange.moveStart("character", start);
    		selRange.moveEnd("character", end);
    		selRange.select();
    	} else if( field.setSelectionRange ){
    		field.setSelectionRange(start, end);
    	} else {
    		if( field.selectionStart ){
    			field.selectionStart = start;
    			field.selectionEnd = end;
    		}
    	}
    	field.focus();
    };
    
    
    Raphael's avatar
    Raphael a validé
    })(jQuery);