var Autocompleter = new Class({	Implements: [Options, Events],	options: {		minLength: 1,		markQuery: true,		width: 'inherit',		maxChoices: 10,		injectChoice: null,		customChoices: null,		emptyChoices: null,		visibleChoices: true,		className: 'autocompleter-choices',		zIndex: 42,		delay: 400,		observerOptions: {},		fxOptions: {},		autoSubmit: false,		overflow: false,		overflowMargin: 25,		selectFirst: false,		filter: null,		filterCase: false,		filterSubset: false,		forceSelect: false,		selectMode: true,		choicesMatch: null,		multiple: true,		separator: ', ',		separatorSplit: /\s*[,;]\s*/,		autoTrim: false,		allowDupes: false,		inpVals: 0,		fIdCity: '',				cache: true,		relative: false	},	initialize: function(element, options){		this.element = $(element);		this.setOptions(options);		this.build();		this.observer = new Observer(this.element, this.prefetch.bind(this), $merge({			'delay': this.options.delay		}, this.options.observerOptions));				this.queryValue = null;		if(this.options.filter) 			this.filter = this.options.filter.bind(this);		var mode = this.options.selectMode;		this.typeAhead = (mode == 'type-ahead');		this.selectMode = (mode === true) ? 'selection' : mode;		this.cached = [];	},	build: function(){		if($(this.options.customChoices))			this.choices = this.options.customChoices;		else{			this.choices = new Element('ul', {				'class': this.options.className,				'styles': {					'zIndex': this.options.zIndex				}			}).inject(document.body);						this.relative = false;			if(this.options.relative){				this.choices.inject(this.element, 'after');				this.relative = this.element.getOffsetParent();			}			this.fix = new OverlayFix(this.choices);		}				if(!this.options.separator.test(this.options.separatorSplit))			this.options.separatorSplit = this.options.separator;		this.fx = (!this.options.fxOptions) ? null : new Fx.Tween(this.choices, $merge({			'property': 'opacity',			'link': 'cancel',			'duration': 200		}, this.options.fxOptions)).addEvent('onStart', Chain.prototype.clearChain).set(0);		this.element.setProperty('autocomplete', 'off').addEvent((Browser.Engine.trident || Browser.Engine.webkit) ? 'keydown' : 'keypress', this.onCommand.bind(this)).addEvent('click', this.onCommand.bind(this, [false])).addEvent('focus', this.toggleFocus.create({bind: this, arguments: true, delay: 100})).addEvent('blur', this.toggleFocus.create({bind: this, arguments: false, delay: 100}));	},	destroy: function(){		if(this.fix) 			this.fix.destroy();		this.choices = this.selected = this.choices.destroy();	},	toggleFocus: function(state){		this.focussed = state;		if(!state) 			this.hideChoices(true);		this.fireEvent((state) ? 'onFocus' : 'onBlur', [this.element]);	},	onCommand: function(e){		if(!e && this.focussed) 			return this.prefetch();		if(e && e.key && !e.shift){			switch (e.key){				case 'enter':					if(this.element.value != this.opted) 						return true;					if(this.selected && this.visible){						this.choiceSelect(this.selected);						return !!(this.options.autoSubmit);					}				break;				case 'up': case 'down':					if(!this.prefetch() && this.queryValue !== null){						var up = (e.key == 'up');						this.choiceOver((this.selected || this.choices)[							(this.selected) ? ((up) ? 'getPrevious' : 'getNext') : ((up) ? 'getLast' : 'getFirst')						](this.options.choicesMatch), true);					}					return false;				break;				case 'esc': case 'tab':					this.hideChoices(true);				break;			}		}		return true;	},	setSelection: function(finish){		var input = this.selected.inputValue, value = input['cityName'];		var start = this.queryValue.length, end = input['cityName'].length;		if(input['cityName'].substr(0, start).toLowerCase() != this.queryValue.toLowerCase()) 			start = 0;		if((this.options.multiple == false && this.options.inpVals == 1) || this.options.multiple){			var split = this.options.separatorSplit;			value = this.element.value;			start += this.queryIndex;			end += this.queryIndex;						var old = value.substr(this.queryIndex).split(split, 1)[0];		}				if(this.options.multiple == false && this.options.inpVals == 1)			value = value.substr(0, this.queryIndex) + input['cityName'];		else{			if(this.options.multiple)				value = value.substr(0, this.queryIndex) + input['cityName'] + value.substr(this.queryIndex + old.length);		}					if((this.options.multiple == false && this.options.inpVals == 1) || this.options.multiple){			if(finish){				var tokens = value.split(this.options.separatorSplit).filter(function(entry){					return this.test(entry);				}, /[^\s,]+/);				if(!this.options.allowDupes) 					tokens = [].combine(tokens);				var sep = this.options.separator;				value = tokens.join(sep) + sep;				end = value.length;			}		}		if(this.options.multiple == false && this.options.inpVals == 1){			if(finish){				if(value.test(sep) == false)					this.options.inpVals = 0;			}		}		if(this.options.multiple){			if(finish){				if(value.test(sep) == true)					this.options.inpVals = 1;				else					this.options.inpVals = 0;			}		}		this.observer.setValue(value);		this.opted = value;		if(finish || this.selectMode == 'pick') 			start = end;		if(this.options.inpVals == 1)			this.options.multiple = false;		else			this.options.multiple = true;		this.element.selectRange(start, end)		$(this.options.fIdCity).set('value', input['idCity']);		this.fireEvent('onSelection', [this.element, this.selected, value, input['cityName']]);	},	showChoices: function(){		var match = this.options.choicesMatch, first = this.choices.getFirst(match);		this.selected = this.selectedValue = null;		if(this.fix){			var pos = this.element.getCoordinates(this.relative), width = this.options.width || 'auto';			this.choices.setStyles({				'left': pos.left,				'top': pos.bottom,				'width': (width === true || width == 'inherit') ? pos.width-2 : width			});		}		if(!first) 			return;		if(!this.visible){			this.visible = true;			this.choices.setStyle('display', '');			if(this.fx) 				this.fx.start(1);			this.fireEvent('onShow', [this.element, this.choices]);		}		if(this.options.selectFirst || this.typeAhead || first.inputValue == this.queryValue) 			this.choiceOver(first, this.typeAhead);		var items = this.choices.getChildren(match), max = this.options.maxChoices;		var styles = {'overflowY': 'hidden', 'height': ''};		this.overflown = false;		if(items.length > max){			var item = items[max - 1];			styles.overflowY = 'scroll';			styles.height = item.getCoordinates(this.choices).bottom;			this.overflown = true;		};		this.choices.setStyles(styles);		this.fix.show();		if(this.options.visibleChoices){			var scroll = document.getScroll(),			size = document.getSize(),			coords = this.choices.getCoordinates();						if(coords.right > scroll.x + size.x) 				scroll.x = coords.right - size.x;			if(coords.bottom > scroll.y + size.y) 				scroll.y = coords.bottom - size.y;			window.scrollTo(Math.min(scroll.x, coords.left), Math.min(scroll.y, coords.top));		}	},	hideChoices: function(clear){		if(clear){			var value = this.element.value;			if(this.options.forceSelect) 				value = this.opted;			if(this.options.autoTrim)				value = value.split(this.options.separatorSplit).filter($arguments(0)).join(this.options.separator);			this.observer.setValue(value);		}		if(!this.visible) 			return;		this.visible = false;		if(this.selected) 			this.selected.removeClass('autocompleter-selected');		this.observer.clear();		var hide = function(){			this.choices.setStyle('display', 'none');			this.fix.hide();		}.bind(this);		if(this.fx) 			this.fx.start(0).chain(hide);		else 			hide();		this.fireEvent('onHide', [this.element, this.choices]);	},	prefetch: function(){		var value = this.element.value, query = value;		var split = this.options.separatorSplit;		var values = value.split(split);				if(this.options.multiple){			var index = this.element.getSelectedRange().start;			var toIndex = value.substr(0, index).split(split);			var last = toIndex.length - 1;			index -= toIndex[last].length;			query = values[last];		}				if(query.length < this.options.minLength)			this.hideChoices();		else{			if(query === this.queryValue || (this.visible && query == this.selectedValue)){				if(this.visible) 					return false;				this.showChoices();			}			else{				this.queryValue = query;				this.queryIndex = index;				if(!this.fetchCached())					this.query();			}		}		return true;	},	fetchCached: function(){		return false;		if(!this.options.cache || !this.cached || !this.cached.length || this.cached.length >= this.options.maxChoices || this.queryValue) return false;			this.update(this.filter(this.cached));		return true;	},	update: function(tokens){		this.choices.empty();		this.cached = tokens;		var type = tokens && $type(tokens);		if(!type || (type == 'array' && !tokens.length) || (type == 'hash' && !tokens.getLength()))			(this.options.emptyChoices || this.hideChoices).call(this);		else{			if(this.options.maxChoices < tokens.length && !this.options.overflow) 				tokens.length = this.options.maxChoices;			tokens.each(this.options.injectChoice || function(token){				var choice = new Element('li', {'html': this.markQueryValue(token)});				choice.inputValue = token;				this.addChoiceEvents(choice).inject(this.choices);			}, this);			this.showChoices();		}	},	choiceOver: function(choice, selection){		if(!choice || choice == this.selected) 			return;		if(this.selected) 			this.selected.removeClass('autocompleter-selected');		this.selected = choice.addClass('autocompleter-selected');		this.fireEvent('onSelect', [this.element, this.selected, selection]);		if(!this.selectMode) 			this.opted = this.element.value;		if(!selection) 			return;		this.selectedValue = this.selected.inputValue;		if(this.overflown){			var coords = this.selected.getCoordinates(this.choices), margin = this.options.overflowMargin, top = this.choices.scrollTop, height = this.choices.offsetHeight, bottom = top + height;			if(coords.top - margin < top && top) 				this.choices.scrollTop = Math.max(coords.top - margin, 0);			else if(coords.bottom + margin > bottom) 				this.choices.scrollTop = Math.min(coords.bottom - height + margin, bottom);		}		if(this.selectMode) 			this.setSelection();	},	choiceSelect: function(choice){		if(choice) 			this.choiceOver(choice);		this.setSelection(true);		this.queryValue = false;		this.hideChoices();	},	filter: function(){		return (tokens || this.tokens).filter(function(token){			return this.test(token);		}, new RegExp(((this.options.filterSubset) ? '' : '^') + this.queryValue.escapeRegExp(), (this.options.filterCase) ? '' : 'i'));	},	markQueryValue: function(str){		return (!this.options.markQuery || !this.queryValue) ? str['cityName'] : str['cityDispName'].replace(new RegExp('(' + ((this.options.filterSubset) ? '' : '^') + this.queryValue.escapeRegExp() + ')', (this.options.filterCase) ? '' : 'i'), '<span class="autocompleter-queried">$1</span>');	},	addChoiceEvents: function(el){		return el.addEvents({			'mouseover': this.choiceOver.bind(this, [el]),			'click': this.choiceSelect.bind(this, [el])		});	}});var OverlayFix = new Class({	initialize: function(el){		if(Browser.Engine.trident){			this.element = $(el);			this.relative = this.element.getOffsetParent();			this.fix = new Element('iframe', {				'frameborder': '0',				'scrolling': 'no',				'src': 'javascript:false;',				'styles': {					'position': 'absolute',					'border': 'none',					'display': 'none',					'filter': 'progid:DXImageTransform.Microsoft.Alpha(opacity=0)'				}			}).inject(this.element, 'after');		}	},	show: function(){		if(this.fix){			var coords = this.element.getCoordinates(this.relative);			delete coords.right;			delete coords.bottom;			this.fix.setStyles($extend(coords, {				'display': '',				'zIndex': (this.element.getStyle('zIndex') || 1) - 1			}));		}		return this;	},	hide: function(){		if(this.fix) 			this.fix.setStyle('display', 'none');		return this;	},	destroy: function(){		if(this.fix) 			this.fix = this.fix.destroy();	}});Element.implement({	getSelectedRange: function(){		if(!Browser.Engine.trident) 			return {start: this.selectionStart, end: this.selectionEnd};		var pos = {start: 0, end: 0};		var range = this.getDocument().selection.createRange();				if(!range || range.parentElement() != this) 			return pos;		var dup = range.duplicate();		if(this.type == 'text'){			pos.start = 0 - dup.moveStart('character', -100000);			pos.end = pos.start + range.text.length;		} 		else{			var value = this.value;			var offset = value.length - value.match(/[\n\r]*$/)[0].length;			dup.moveToElementText(this);			dup.setEndPoint('StartToEnd', range);			pos.end = offset - dup.text.length;			dup.setEndPoint('StartToStart', range);			pos.start = offset - dup.text.length;		}		return pos;	},	selectRange: function(start, end){		if(Browser.Engine.trident){			var diff = this.value.substr(start, end - start).replace(/\r/g, '').length;			start = this.value.substr(0, start).replace(/\r/g, '').length;			var range = this.createTextRange();			range.collapse(true);			range.moveEnd('character', start + diff);			range.moveStart('character', start);			range.select();		}		else{			this.focus();			this.setSelectionRange(start, end);		}		return this;	}});Autocompleter.Base = Autocompleter;Autocompleter.Request = new Class({	Extends: Autocompleter,	options:{		postData: {},		ajaxOptions: {},		postVar: 'cityTip'	},	query: function(){		var data = $unlink(this.options.postData) || {};		data[this.options.postVar] = this.queryValue;		var indicator = $(this.options.indicator);		if(indicator) indicator.setStyle('display', '');		var cls = this.options.indicatorClass;		if(cls) this.element.addClass(cls);		this.fireEvent('onRequest', [this.element, this.request, data, this.queryValue]);		this.request.send({'data': data});	},	queryResponse: function(){		var indicator = $(this.options.indicator);		if(indicator) indicator.setStyle('display', 'none');		var cls = this.options.indicatorClass;		if(cls) this.element.removeClass(cls);		return this.fireEvent('onComplete', [this.element, this.request]);	}});Autocompleter.Request.JSON = new Class({	Extends: Autocompleter.Request,	initialize: function(el, url, options){		this.parent(el, options);		this.request = new Request.JSON($merge({			'url': url,			'link': 'cancel'		}, this.options.ajaxOptions)).addEvent('onComplete', this.queryResponse.bind(this));	},	queryResponse: function(response){		this.parent();		this.update(response);	}});