if (!Suggest) {
	var Suggest = {};
}

Suggest.City = {
	ALL :"|BEIJING,北京首都,PEK,BJ|SHANGHAIHONGQIAO,上海虹桥,SHA,SHHQ|GUANGZHOU,广州,CAN,GZ|SHENZHEN,深圳,SZX,SZ|CHENGDU,成都,CTU,CD|TIANJIN,天津,TSN,TJ|CHONGQING,重庆,CKG,CQ|WUHAN,武汉,WUH,WH|ZHUHAI,珠海,ZUH,ZH|CHANGSHA,长沙,CSX,CS|HANGZHOU,杭州,HGH,HZ|QINGDAO,青岛,TAO,QD|XIAMEN,厦门,XMN,XM|NANCHANG,南昌,KHN,NC|GANZHOU,赣州,KOW,GZ|JIUJIANG,九江,JIU,JJ|JINGDEZHEN,景德镇,JDZ,JDZ|JINGGANGSHAN,井冈山,JGS,JGS|FUZHOU,福州,FOC,FZ|JINJIANG,晋江/泉州,JJN,JJ|WUYISHAN,武夷山,WUS,WYS|LIANCHENG,连城,LCX,LC|NINGBO,宁波,NGB,NB|WENZHOU,温州,WNZ,WZ|ZHANGJIAJIE,张家界/大庸,DYG,DY|CHANGDE,常德,CGD,CD|HUAIHUA,怀化/芷江,HJJ,HH|YIWU,义乌,YIW,YW|BEIJINGNANYUANG,北京南苑,NAY,BJNY|SHANGHAIPVDONG,上海浦东,PVG,SHPD|ZHOUSHAN,舟山,HSN,ZS|HUANGYAN,黄岩,HYN,HY|QUZHOU,衢州,JUZ,QZ|NANJING,南京,NKG,NJ|WUXI,无锡,WUX,WX|CHANGZHOU,常州,CZX,CZ|LIANYUNGANG,连云港,LYG,LYG|YANCHENG,盐城,YNZ,YC|XUZHOU,徐州,XUZ,XZ|NANTONG,南通,NTG,NT|JINAN,济南,TNA,JN|YANTAI,烟台,YNT,YT|WEIFANG,潍坊,WEF,WF|WEIHAI,威海,WEH,WH|LINYI,临沂,LYI,LY|DONGYING,东营,DOY,DY|JINING,济宁,JNG,JN|WANZHOU,万州,WXN,WZ|MEIZHOU,梅州,MXZ,MZ|SHANTOU,汕头,SWA,ST|ZHANJIANG, 湛江,ZHA,ZJ|ZHENGZHOU,郑州,CGO,ZZ|NANYANG,南阳,NNY,NY|LUOYANG,洛阳,LYA,LY|PANZHIHUA,攀枝花,PZI,PZH|MIANYANG,绵阳,MIG,MY|NANCHONG,南充,NAO,NC|DAXIAN,达县,DAX,DX|YIBIN,宜宾,YBP,YB|LUZHOU,泸州,LZO,LZ|JIUZHAIGOU,九寨沟,JZH,JZG|XICHANG,西昌,XIN,XC|GUANGYUAN,广元,GYS,GY|SANYA,三亚,SYX,SY|HAIKOU,海口,HAK,HK|KUNMING,昆明,KMG,KM|LIJIANG,丽江,LJG,LJ|DALI,大理,DLU,DL|JINGHONG,景洪/ 西双版纳,JHG,JH|ZHONGDIAN,中甸/迪庆香格里拉,DIG,ZD|MANGSHI,芒市,LUM,MS|PUER,普洱,SYM,PR|BAOSHAN,保山,BSD,BS|LINCANG,临沧,LNJ,LC|ZHAOTONG,昭通,ZAT,ZT|YUANMOU, 元谋,YUA,YM|WENSHAN,文山,WNH,WS|GUIYANG,贵阳,KWE,GY|ANSHUN,安顺,AVA,AS|TONGREN,铜仁,TEN,TR|XINGYI,兴义,ACX,XI|LIPING,黎平,HZH,LP|ZUNYI,遵义,ZYI,ZY|LANZHOU,兰州,LHW,LZ|DUNHUANG,敦煌,DNH,DH|JIAYUGUANG,嘉峪关,JGN,JYG|QINGYANG,庆阳,IQN,QY|HEFEI,合肥,HFE,HF|HUANGSHAN,黄山,TXN,HS|ANQING,安庆,AQG,AQ|XIAN,西安,XIY,XA|YANAN,延安,ENY,YA|YULIN,榆林,UYN,YL|ANKANG,安康,AKA,AK|HANZHONG,汉中,HZG,HZ|TAIYUAN,太原,TYN,TY|CHANGZHI,长治,CIH,CZ|YUNCHENG,运城,YCU,YC|DATONG, 大同,DAT,DT|SHIJIAZHUANG,石家庄,SJW,SJZ|QIHUANGDAO,秦皇岛,SHP,QHD|HANDAN,邯郸,HDG,HD|SHEYANG,沈阳,SHE,SY|DALIAN,大连,DLC,DL|DANDONG,丹东,DDG,DD|JINZHOU,锦州,JNZ,JZ|CHANGCHUN,长春,CGQ,CC|JILIN,吉林,JIL,JL|YANJI,延吉,YNJ,YJ|HAERBIN,哈尔滨,HRB,HEB|QIQIHAER,齐齐哈尔,NDG,QQHE|MUDANJIANG,牡丹江,MDG,MDJ|HEIHE,黑河,HEK,HH|JIAMUSI,佳木斯,JMU,JMS|HUHEHAOTE,呼和浩特,HET,HHHT|XILINHAOTE,锡林浩特,XIL,XLHT|WULANHAOTE,乌兰浩特,HLH,WLHT|BAOTOU,包头,BAV,BT|WUHAI,乌海,WUA,WH|HAILAER,海拉尔,HLD,HLE|CHIFENG,赤峰,CIF,CF|TONGLIAO,通辽,TGO,TL|EERDUOSI,鄂尔多斯,DSN,EEDS|XINING,西宁,XNN,XN|GEERMU,格尔木,GOQ,GEM|YINCHUAN,银川,INC,YC|LASA,拉萨,LXA,LS|LINZHI,林芝,LZY,LZ|CHANGDU,昌都,BPX,CD|WULUMUQI,乌鲁木齐,URC,WLMQ|KASHI,喀什,KHG,KS|YINING,伊宁,YIN,YN|KUERLE, 库尔勒,KRL,KEL|AKESU,阿克苏,AKU,AKS|HETIAN,和田,HTN,HT|ALETAI,阿勒泰,AAT,ALT|QIEMO, 且末,IQM,QM|KUCHE,库车,KCA,KC|TACHENG,塔城,TCG,TC|FUYUN,富蕴,FYN,FY|KELAMAYI,克拉马依,KRY,KLMY|HAMI,哈密,HMI,HM|GUILIN,桂林,KWL,GL|NANNING,南宁,NNG,NN|LIUZHOU,柳州,LZH,LZ|BEIHAI,北海,BHY,BH|WUZHOU,梧州,WUZ,WZ|BAISE,百色,AEB,BS|XiangFan,襄樊,XFN,XF|YICHANG,宜昌,YIH,YH|xishuangbanna,西双版纳,JHG,xsbn|ENSHI,恩施,ENH,ES|CHANGBAISHAN,长白山,NBS,cbs|DAQING,大庆,DQA,dq|YICHUN,伊春,LDS,yc|ERLIANHAOTE,二连浩特,ERL,elht|JIXI,鸡西,JXA,jx|BEIJINGCHAOYANG,北京朝阳机场,CHG,bjcy|BOLE,博乐机场,BPL,bl|TANGSHAN,唐山三女河机场,TVS,tsjc|FUYANG,阜阳,FUG,fy",
	DEFAULT :"|BEIJING,北京首都,PEK,BJ|SHANGHAIHONGQIAO,上海虹桥,SHA,SHHQ|GUANGZHOU,广州,CAN,GZ|SHENZHEN,深圳,SZX,SZ|CHENGDU,成都,CTU,CD|TIANJIN,天津,TSN,TJ|CHONGQING,重庆,CKG,CQ|WUHAN,武汉,WUH,WH|ZHUHAI,珠海,ZUH,ZH|CHANGSHA,长沙,CSX,CS"
};

/*-- KeyCodes -----------------------------------------*/
Suggest.Key = {
	TAB :9,
	RETURN :13,
	ESC :27,
	UP :38,
	DOWN :40
};

Suggest.ResultCode = {
	UNFOUND :-1,
	FOUND :0,
	UNEXEC :1
};

Suggest.Color = {
	INPUTTIPS :"#888",
	DEFAULT :"#000"
};

Suggest.Area = {
	HEIGHT :"315px",
	WIDTH :"230px"
};

Suggest.MsgTemplate = {
	UNFOUND :"对不起，未找到：$input$",
	FOUND :"$input$，按拼音排序",
	TIP :"输入中文/拼音，可↑↓选择。",

	INITMSG :"中文/拼音"
};

/*-- Utils --------------------------------------------*/
Suggest.copyProperties = function(dest, src) {
	for ( var property in src) {
		dest[property] = src[property];
	}
	return dest;
};

/*-- Suggest.Local ------------------------------------*/
Suggest.Local = function() {
	this.initialize.apply(this, arguments);
};

Suggest.Local.prototype = {
	// options
	interval :500,
	// 最多显示条目数
	dispMax :12,
	dispAllKey :false,
	classMouseOver :'over',
	classSelect :'select',

	initialize : function(input, inputValue, suggestArea) {
		this.resultCode = Suggest.ResultCode.UNEXEC;

		this.input = this._getElement(input);
		this.inputValue = this._getElement(inputValue);
		this.suggestArea = this._getElement(suggestArea);
		this.candidateAreaClassName = "candidateArea";
		this.messageAreaClassName = "messageArea";
		
		this.candidateCityList = [];

		if (arguments[3])
			this.setOptions(arguments[3]);

		if (this.defaultCity) {
			this.setCityParams(this.defaultCity);
		} else {
			this.initCityParams();
		}

		// 绑定focus事件
		this._addEvent(this.input, 'focus', this._bind(this.bindInput));

		this._addEvent(this.input, 'click', this._bind(this.showCandidateCitys));

		// 绑定Keyup事件
		this._addEvent(this.input, 'keyup', this._bindEvent(this.keyboardInput));

		// 绑定blur事件
		this._addEvent(this.input, 'blur', this._bind(this.inputBlur));

		var keyevent = 'keydown';
		if (window.opera || (navigator.userAgent.indexOf('Gecko') >= 0 && navigator.userAgent.indexOf('KHTML') == -1)) {
			keyevent = 'keypress';
		}

		this._addEvent(this.input, keyevent, this._bindEvent(this.keyEvent));

		// init
		this.clearSuggestArea();
	},

	bindInput : function() {
		if (this.input.value == Suggest.MsgTemplate.INITMSG) {
			this.input.value = "";
			this.showCandidateCitys();
		}
	},

	complateCitySelect : function() {
		if (this.input.value == null || this.input.value == "") {
			this.initCityParams();
		} else {
			this.setCityParams(this.candidateCityList[this.activePosition]);
		}
	},

	setCityParams : function(params) {
		this.input.value = params.name;
		this.inputValue.value = params.code;
		this.input.style.color = Suggest.Color.DEFAULT;
	},

	initCityParams : function() {
		this.input.value = Suggest.MsgTemplate.INITMSG;
		this.inputValue.value = "";
		this.input.style.color = Suggest.Color.INPUTTIPS;
	},

	inputBlur : function(event) {
		this.changeUnactive();
		this.complateCitySelect();
		/**
		 * 用户在进行鼠标单击选择操作时，会先出发input的blur事件 blur绑定的函数有可能在click事件触发之前清理候选区
		 * 因此，这里要延迟执行清理函数
		 */
		this.id = setTimeout(this._bind(this.clearSuggestArea), 500);
	},

	/**
	 * 响应有效的键盘输入 忽略ESC,RETURN,TAB,UP,DOWN
	 * 
	 * @param event
	 */
	keyboardInput : function(event) {
		if (!this.isCtrlKey(event.keyCode)) {
			this.showCandidateCitys();
		}
	},

	/**
	 * 显示候选的城市
	 * 
	 */
	showCandidateCitys : function() {
		// 检索备选城市
		var candidateCitys = this.searchCity();
		// 创建消息区
		var message = this.getMessage();
		if (this.resultCode == Suggest.ResultCode.FOUND
				|| this.resultCode == Suggest.ResultCode.UNEXEC) {
			this.clearSuggestArea();

			// 保存检索到城市对象数组，用来索引给目标控件赋值
			this.saveCandidateCitys(candidateCitys);
			// 创建候选区
			var candidateArea = this.createCandidateArea(candidateCitys);
			this.setActivePosition();
			this.changeActive(this.activePosition);
			// 创建消息区
			var messageArea = this.createMessageArea(message);
			// 创建显示面板
			this.addToPanel(messageArea, candidateArea);
			this.showPanel();
		} else {
			this.updateMessageArea(message);
		}

	},

	setActivePosition : function() {
		var activePos = this.activePosition;
		var candidateLen = this.suggestList.length;
		if (activePos == null) {
			this.activePosition = 0;
		} else if (activePos >= candidateLen) {
			this.activePosition = candidateLen - 1;
		} else {
			// do nothing
		}
	},

	/**
	 * 检索
	 * 
	 * @return Array
	 */
	searchCity : function() {
		var candidateList = {};
		var text = this.input.value;
		if (text == '' || text == null) {
			candidateList = this.getDefaultCityList();
			this.resultCode = Suggest.ResultCode.UNEXEC;
		} else {
			candidateList = this._searchCity(text);
			if (candidateList.length > 0) {
				// 搜索结果按拼音排序
				candidateList = this.citySort(candidateList);
				this.resultCode = Suggest.ResultCode.FOUND;
			} else {
				this.resultCode = Suggest.ResultCode.UNFOUND;
			}
		}

		candidateList = this._arrayCopy(candidateList, 0, this.dispMax);

		return candidateList;
	},

	/**
	 * 检索
	 * 
	 * @param text
	 * @return Array
	 */
	_searchCity : function(text) {
		var searchResult = [];

		var foundCitys = this.cityMatch(text);

		if (foundCitys != null) {
			searchResult = this.cityConversionStringToObject(foundCitys);
		}

		return searchResult;
	},

	/**
	 * 匹配城市
	 * 
	 * @param text
	 * @return Array
	 */
	cityMatch : function(text) {
		var result = [];
		var candidateStr = Suggest.City.ALL;
		// 输入过滤
		text = text.replace(/([\(\)\\\[\]\.\+\?\*\|\^\$])/gi, "\\$1").replace(
				/,|\|/gi, "");
		// 构造表达式
		var cityReg = new RegExp("\\|([^,]*?,)?([^,]*?,)?([^,]*?,)?" + text
				+ "[^\\|]*", "gi");

		result = candidateStr.match(cityReg);

		return result;
	},

	/**
	 * 返回默认的备选城市
	 * 
	 * @return Array
	 */
	getDefaultCityList : function() {
		var result = [];
		result = this.cityConversionStringToObject(Suggest.City.DEFAULT);

		return result;
	},

	/**
	 * 备选城市排序
	 * 
	 * @param cityList
	 * @return Array
	 */
	citySort : function(cityList) {
		if (cityList.constructor == Array) {
			cityList = cityList.sort( function(l, r) {
				return l.pinyin > r.pinyin
			});
		}

		return cityList;
	},

	/**
	 * 把String型的查询结果转换为对象
	 * 
	 * @param cityList
	 * @return Array
	 */
	cityConversionStringToObject : function(cityList) {
		var result = [];
		var isString = (cityList.constructor == String);

		if (isString) {
			// 过输入为String型，则转换成数组
			cityList = this.parseCityStrToArray(cityList);
		}

		for ( var i = 0; i < cityList.length; i++) {
			var aCity = cityList[i].split(",");

			if (aCity == "" || aCity == null) {
				continue;
			}

			var oCity = new Object();

			oCity.name = aCity[1];

			/**
			 * 在检索的时候，正则写的还是有点问题，得到的结果总是多一个“|” 在这里replace一下 见这里：cityMatch :
			 * function(text)
			 */
			oCity.pinyin = aCity[0].replace("|", "");
			oCity.code = aCity[2];
			oCity.samplePinyin = aCity[3];

			result.push(oCity);
		}

		return result;
	},

	/**
	 * 将城市字符串解析成数组
	 * 
	 * @param cityStr
	 * @return Array
	 */
	parseCityStrToArray : function(cityStr) {
		var cityList = new Array();
		var citys = cityStr.split('|');
		for ( var i = 0; i < citys.length; i++) {
			if (citys[i] == "" || citys[i] == null) {
				continue;
			}

			cityList.push(citys[i]);
		}

		return cityList;
	},

	getMessage : function() {
		var message = "";
		var pos = this.resultCode;
		switch (pos) {
		case Suggest.ResultCode.UNFOUND:
			message = Suggest.MsgTemplate.UNFOUND;
			break;
		case Suggest.ResultCode.FOUND:
			message = Suggest.MsgTemplate.FOUND;
			break;

		default:
			message = Suggest.MsgTemplate.TIP;
			break;
		}
		// 将用户当前输入内容填充到返回消息
		message = message.replace("$input$", this.input.value);

		return message;
	},

	/**
	 * 检查是否为控制键
	 * 
	 * @param keyCode
	 * @return boolean
	 */
	isCtrlKey : function(keyCode) {
		for ( var key in Suggest.Key) {
			if (eval("Suggest.Key." + key) == keyCode) {
				return true;
			}
		}

		return false;
	},

	showPanel : function() {
		this.areaDisplayPosition = this.calcAreaDisplayPosition(this.input);
		this.suggestArea.style.height = Suggest.Area.HEIGHT;
		this.suggestArea.style.width = Suggest.Area.WIDTH;
		this.suggestArea.style.zIndex = "120";
		this.suggestArea.style.top = this.areaDisplayPosition.top;
		this.suggestArea.style.left = this.areaDisplayPosition.left;
		this.suggestArea.style.visibility = "visible";
		this.suggestArea.style.position = "absolute";
		this.suggestArea.style.display = "";
	},

	clearSuggestArea : function() {
		if (this.timerId)
			clearTimeout(this.timerId);
		this.timerId = null;

		this.suggestArea.innerHTML = '';
		this.suggestArea.style.zIndex = "-120";
		this.suggestArea.style.top = "-2000px";
		this.suggestArea.style.left = "-2000px";
		this.suggestArea.style.visibility = "hidden";
		this.suggestArea.style.display = "none";
	},

	/**
	 * 创建一个备选city项
	 * 
	 * @param cityObj
	 * @return Object
	 */
	createCityElement : function(cityObj) {
		var cityItemAElement = document.createElement("a");
//		cityItemAElement.href = "javascript:void(0)";
		cityItemAElement.cityCode = cityObj.code;

		var cityPinyinSpanElement = document.createElement("span");
		var cityPinyinTextNode = document.createTextNode(cityObj.pinyin);
		cityPinyinSpanElement.appendChild(cityPinyinTextNode);

		var cityNameTextNode = document.createTextNode(cityObj.name);

		cityItemAElement.appendChild(cityPinyinSpanElement);
		cityItemAElement.appendChild(cityNameTextNode);

		return cityItemAElement;
	},

	/**
	 * 创建候选区
	 * 
	 * @param candidateCitys
	 * @return Object
	 */
	createCandidateArea : function(candidateCitys) {
		this.suggestList = [];
		this.inputValueBackup = this.input.value;
		var citysContainer = document.createElement("div");
		citysContainer.className = this.candidateAreaClassName;

		for ( var i = 0; i < candidateCitys.length; i++) {
			var city = this.createCityElement(candidateCitys[i]);

			this._addEvent(city, 'click', this._bindEvent(this.listClick, i));
			this._addEvent(city, 'mouseover', this._bindEvent(
					this.listMouseOver, i));
			this._addEvent(city, 'mouseout', this._bindEvent(this.listMouseOut,
					i));

			citysContainer.appendChild(city);
			this.suggestList.push(city);
		}

		return citysContainer;
	},

	/**
	 * 创建消息区
	 * 
	 * @param messageText
	 * @return Object
	 */
	createMessageArea : function(messageText) {
		var messageContainer = document.createElement("div");
		messageContainer.className = this.messageAreaClassName;
		messageContainer.appendChild(document.createTextNode(messageText));

		return messageContainer;
	},

	/**
	 * 更新消息区文本
	 * 
	 * @param messageText
	 */
	updateMessageArea : function(messageText) {
		this.suggestArea.firstChild.innerHTML = messageText;
	},

	/**
	 * 保存候选的对象数组 做为候选元素位置和值的映射
	 * 
	 * @param candidateCitys
	 */
	saveCandidateCitys : function(candidateCitys) {
		this.candidateCityList = candidateCitys;
	},

	/**
	 * 将消息区和候选区添加到显示面板
	 * 
	 * @param messageArea
	 * @param candidateArea
	 */
	addToPanel : function(messageArea, candidateArea) {
		var panel = this.suggestArea;
		panel.appendChild(messageArea);
		panel.appendChild(candidateArea);
	},

	getInputText : function() {
		return this.input.value;
	},

	setOptions : function(options) {
		Suggest.copyProperties(this, options);
	},

	setInputText : function(text) {
		this.input.value = text;
	},

	setInputValue : function(text) {
		this.inputValue.value = text;
	},
	
	/**
	 * 计算候选区显示位置
	 * 
	 * @param element
	 * @return Object
	 */
	calcAreaDisplayPosition : function(element) {
		var displayPosition = {top:'0px', left:'0px'};
		var elementHeight = element.offsetHeight;
		var elementPosition = this.getElementPosition(element);
		
		displayPosition.top = elementHeight 
							+ elementPosition.y 
							+ "px";
		displayPosition.left = elementPosition.x + "px";
		
		return displayPosition;
	},
	
	/**
	 * 获取元素位置
	 * 
	 * @param element
	 * @return Object
	 */
	getElementPosition : function(element) {
		var ua = navigator.userAgent.toLowerCase();
		var isOpera = (ua.indexOf('opera') != -1);
		var isIE = (ua.indexOf('msie') != -1 && !isOpera); // not opera spoof

		var el = element;//document.getElementById(elementId);

		if (el.parentNode === null || el.style.display == 'none') {
			return false;
		}

		var parent = null;
		var pos = [];
		var box;

		if (el.getBoundingClientRect) // IE
		{
			box = el.getBoundingClientRect();
			var scrollTop = Math.max(document.documentElement.scrollTop,
					document.body.scrollTop);
			var scrollLeft = Math.max(document.documentElement.scrollLeft,
					document.body.scrollLeft);

			return {
				x :box.left + scrollLeft,
				y :box.top + scrollTop
			};
		} else if (document.getBoxObjectFor) // gecko
		{
			box = document.getBoxObjectFor(el);

			var borderLeft = (el.style.borderLeftWidth) ? parseInt(el.style.borderLeftWidth)
					: 0;
			var borderTop = (el.style.borderTopWidth) ? parseInt(el.style.borderTopWidth)
					: 0;

			pos = [ box.x - borderLeft, box.y - borderTop ];
		} else // safari & opera
		{
			pos = [ el.offsetLeft, el.offsetTop ];
			parent = el.offsetParent;
			if (parent != el) {
				while (parent) {
					pos[0] += parent.offsetLeft;
					pos[1] += parent.offsetTop;
					parent = parent.offsetParent;
				}
			}
			if (ua.indexOf('opera') != -1
					|| (ua.indexOf('safari') != -1 && el.style.position == 'absolute')) {
				pos[0] -= document.body.offsetLeft;
				pos[1] -= document.body.offsetTop;
			}
		}

		if (el.parentNode) {
			parent = el.parentNode;
		} else {
			parent = null;
		}

		while (parent && parent.tagName != 'BODY' && parent.tagName != 'HTML') { // account
																					// for
																					// any
																					// scrolled
																					// ancestors
			pos[0] -= parent.scrollLeft;
			pos[1] -= parent.scrollTop;

			if (parent.parentNode) {
				parent = parent.parentNode;
			} else {
				parent = null;
			}
		}
		
		return {
			x :pos[0],
			y :pos[1]
		};
	},

	// key event
	keyEvent : function(event) {

		if (!this.isCtrlKey(event.keyCode)) {
			return;
		}

		if (this.dispAllKey && event.ctrlKey && this.getInputText() == ''
				&& !this.suggestList && event.keyCode == Suggest.Key.DOWN) {
			// dispAll
			this._stopEvent(event);
			this.keyEventDispAll();
		} else if (event.keyCode == Suggest.Key.UP
				|| event.keyCode == Suggest.Key.DOWN) {
			// key move
			if (this.suggestList && this.suggestList.length != 0) {
				this._stopEvent(event);
				this.keyEventMove(event.keyCode);
			}
		} else if (event.keyCode == Suggest.Key.RETURN) {
			// fix
			if (this.suggestList && this.suggestList.length != 0) {
				this._stopEvent(event);
				this.keyEventReturn();
			}
		} else if (event.keyCode == Suggest.Key.ESC) {
			// cancel
			if (this.suggestList && this.suggestList.length != 0) {
				this._stopEvent(event);
				this.keyEventEsc();
			}
		} else {
			this.keyEventOther(event);
		}
	},

	keyEventDispAll : function() {

		// init
		this.clearSuggestArea();

		this.oldText = this.getInputText();

		this.suggestIndexList = [];
		for ( var i = 0, length = this.candidateList.length; i < length; i++) {
			this.suggestIndexList.push(i);
		}

		this.createSuggestArea(this.candidateList);
	},

	keyEventMove : function(keyCode) {

		this.changeUnactive();

		if (keyCode == Suggest.Key.UP) {
			// up
			if (this.activePosition == null) {
				this.activePosition = this.suggestList.length - 1;
			} else {
				this.activePosition--;
				if (this.activePosition < 0) {
					this.activePosition = this.suggestList.length - 1;
				}
			}
		} else {
			// down
			if (this.activePosition == null) {
				this.activePosition = 0;
			} else {
				this.activePosition++;
			}

			if (this.activePosition >= this.suggestList.length) {
				this.activePosition = 0;
			}
		}

		this.changeActive(this.activePosition);
	},

	keyEventReturn : function() {
		this.setCityParams(this.candidateCityList[this.activePosition]);
		this.clearSuggestArea();
		this.moveEnd();
	},

	keyEventEsc : function() {

		this.clearSuggestArea();
		this.input.value = this.inputValueBackup;
		this.oldText = this.getInputText();

		if (window.opera)
			setTimeout(this._bind(this.moveEnd), 5);
	},

	keyEventOther : function(event) {
	},

	/**
	 * 设置指定位置的候选元素为选中状态
	 * 
	 * @param index
	 */
	changeActive : function(index) {
		this.setStyleActive(this.suggestList[index]);
	},

	/**
	 * 为当前选中的候选元素解除选定
	 */
	changeUnactive : function() {
		if (this.suggestList != null && this.suggestList.length > 0
				&& this.activePosition != null) {
			this.setStyleUnactive(this.suggestList[this.activePosition]);
		}
	},

	listClick : function(event, index) {
		this.changeUnactive();
		this.activePosition = index;
		this.changeActive(index);
		this.complateCitySelect();
		this.moveEnd();
	},

	listMouseOver : function(event, index) {
		this.setStyleMouseOver(this.suggestList[index]);
	},

	listMouseOut : function(event, index) {

		if (!this.suggestList)
			return;

		var element = this.suggestList[index];

		if (index == this.activePosition) {
			this.setStyleActive(element);
		} else {
			this.setStyleUnactive(element);
		}
	},

	setStyleActive : function(element) {
		element.className = this.classSelect;
	},

	setStyleUnactive : function(element) {
		element.className = '';
	},

	setStyleMouseOver : function(element) {
		element.className = this.classMouseOver;
	},

	moveEnd : function() {

		if (this.input.createTextRange) {
			this.input.focus(); // Opera
			var range = this.input.createTextRange();
			range.move('character', this.input.value.length);
			range.select();
		} else if (this.input.setSelectionRange) {
			this.input.setSelectionRange(this.input.value.length,
					this.input.value.length);
		}
	},

	// Util methods
	_arrayCopy : function(src, start, len) {
		var dest = [];
		var srcLength = src.length;
		// 如果目标数组大于源数组本身，则只返回源数组
		if (len - start > srcLength) {
			len = srcLength - start;
		}

		for ( var i = start; i < len; i++) {
			dest.push(src[i]);
		}

		return dest;
	},

	_getElement : function(element) {
		return (typeof element == 'string') ? document.getElementById(element)
				: element;
	},

	_addEvent :(window.addEventListener ? function(element, type, func) {
		element.addEventListener(type, func, false);
	} : function(element, type, func) {
		element.attachEvent('on' + type, func);
	}),

	_stopEvent : function(event) {
		if (event.preventDefault) {
			event.preventDefault();
			event.stopPropagation();
		} else {
			event.returnValue = false;
			event.cancelBubble = true;
		}
	},

	_getEventElement : function(event) {
		return event.target || event.srcElement;
	},

	_bind : function(func) {
		var self = this;
		var args = Array.prototype.slice.call(arguments, 1);
		return function() {
			func.apply(self, args);
		};
	},

	_bindEvent : function(func) {
		var self = this;
		var args = Array.prototype.slice.call(arguments, 1);
		return function(event) {
			event = event || window.event;
			func.apply(self, [ event ].concat(args));
		};
	}

};
