View Code
function Suggest(textInput, config) { if (!(this instanceof Suggest)) { return new Suggest(textInput, config); } var now = function() { return (new Date).getTime(); }; var EMPTY = "", SPACE = " ", HIDDEN = "hidden", NULL = null, UNDEFINED, WIN = window, DOC = document, BODY = document.body, ARRAY_VALUE = "[object Array]", EMPTY_FUNCTION = function() { }, PREFIX_CLS = EMPTY, //全局前缀,方便自定义 CLS_CONTAINER = PREFIX_CLS + "sug-list-container", CLS_CONTENT = PREFIX_CLS + "sug-list-content", CLS_ITEM_SELECTED = PREFIX_CLS + "sug-item-selected", CLS_ITEM_KEY = PREFIX_CLS + "sug-item-key", CLS_ITEM_RESULT = PREFIX_CLS + "sug-item-result", DEFAULT_VALUE = "", //input 默认值 isIE = /\w/.test('\u0130'), isIE6 = isIE && !window.XMLHttpRequest, isIE9 = document.documentMode && document.documentMode === 9, toString = Object.prototype.toString; var timeout = false, //script 请求是否过时 timeoutID = null, lockIE = NaN, reqScript = '', //jsonp请求标签 latestReqScriptTime = '', scriptDataIsOut = false, //非ie6-8判断是否数据过期 isLocalDataSource = false, //根据config判断是否是本地数据源 selectedIndex = -1, //选中项索引 selectedItem = null, //当前选中项 taobaoURL = "http://suggest.taobao.com/sug?callback=callback&q=", youaURL = "http://youa.baidu.com/suggest/se/s?cmd=suggest&type=kwd&max_count=10&callback=callback&keyword=", baiduURL = "http://suggestion.baidu.com/su?p=3&cb=window.bdsug.sug", //在键盘操作up,down时,忽略鼠标的mousemove,mousedown操作 mouseMoveFlag = true, //config全部小写 defaultConfig = { queryname: 'wd', charset: 'gbk', callbackname: 'callback', callbackfn: 'bdsug.sug', resultkey: 's', containerwidth: 0, offsettop: -1, usecache: true, //string || json object datasource: "http://suggestion.baidu.com/su?p=3&cb=window.bdsug.sug", autofocus: false, inputdelay: inputDelay, updowndelay: updownDelay, /*是否使用外部css文件,动态添加 *使用外部定制样式 * 外部定制css<style>文件 * 定制的className * 可在_init以后调用this._setCss来修改样式 */ usestylefile: false, styleid: PREFIX_CLS + "style", prefixclass: PREFIX_CLS, //定制前缀 //事件 //替换 buildhtml: NULL, formatdata: NULL, //插入 onbeforeconstructor: NULL, onbeforeinit: NULL, oninit: NULL, onbeforerequest: NULL, onrequestload: NULL, oncomplete: NULL, onbeforeselect: NULL, onbeforeshow: NULL, onhide: NULL }, CFG_RESULT_KEY = "resultkey", CFG_INPUT_DELAY = "inputdelay", inputDelay = 3000, CFG_UP_DOWN_DELAY = "updowndelay", updownDelay = 160, CFG_STYLE_ID = "styleid", callee = arguments.callee, ctrlLockFlag = false; //ie下使用ctrl+y/z时,不在propertychange中请求数据 //Static callee._focusInstance = UNDEFINED; //Constructor function Sug(textInput, config) { if (!(this instanceof Sug)) { return new Sug(); } this._regisEvent("onbeforeconstructor", config); this._initThis.call(this); this._preInit.call(this); this._initConfig.call(this, config); this.textInput = typeof textInput == 'string' ? DOC.getElementById(textInput) : textInput; this._regisEvent("onbeforeinit"); this._initialize.call(this); this._regisEvent("oninit"); } Sug.prototype = { constructor: Sug, guid: now(), //prototype对象共用实例 //注册外部事件 _regisEvent: function(evType, config) { config = config || this.config; if (config&&config[evType]) { config[evType].call(this); } }, _initThis: function() { //数据 this._PreHandleData = NULL; this._HandledData = NULL; //this._HtmlContent = NULL; //劲量确保当前查询处理数据的查询关键字实时准确,最好由服务端返回,好确保缓存key:value准确,避免延时产生交叉 this._CurrentQueryWord = EMPTY; this._QueryData = []; this._QueryData._QueryIndex = -1; this._CacheData = {}; /* 数据来源类型;默认0 * 不使用&运算 * a,使用cache;b,使用url;c,使用传入data; * 0:[cache+url],1:[cache+data],2:[url],3:[data] */ this._DataType = 0; //操作sug结果List //List是否有数据 this._HasData = false; }, //初始化前的任务 _preInit: function() { //缓存外部config可能修改的 this.__buildHTML__ = this.buildHTML; this.__formatData__ = this._formatData; }, //初始化config _initConfig: function(_config) { this.config = mix(deepCopy({}, defaultConfig), _config); //datasource var that = this, config = that.config, _dataSource = config.datasource, _useCache = config.usecache; //初始化稀释执行相关函数 this._updownDiute = NULL; //updown this._updownDiute = this.__updownDiute__(config[CFG_UP_DOWN_DELAY] || updownDelay); //该闭包是否泄漏?验证>0? this._reqDiute = NULL; //request data this._reqDiute = this.__reqDiute__(config[CFG_INPUT_DELAY] || inputDelay); //className this._initClassName(config["prefixclass"]); //如果不使用外部css文件或标签则初始化样式 if (!config["usestylefile"]) { this._initStyle(); } //数据源 if (typeof _dataSource === 'string') { that._DataType = _useCache ? 0 : 2; isLocalDataSource = false; _dataSource += (_dataSource.indexOf('?') === -1) ? '?' : '&'; config.dataSource = _dataSource + config.callbackname + '=' + config.callbackfn; //初始化回调函数 this._initCallback(config.callbackfn); } else { that._PreHandleData = _dataSource; that._DataType = _useCache ? 1 : 3; //本地传入数据源 isLocalDataSource = true; } //bulidHTML if (!config.buildhtml) { that.buildHTML = that.__buildHTML__; } else { that.buildHTML = function(data) { var html = config.buildhtml(data); //保存键值cache if (that.config.usecache) that._CacheData[that._CurrentQueryWord] = html; return html; }; } //formatData 空函数相等判断? if (!config.formatdata) { that._formatData = that.__formatData__; } else { that._formatData = config.formatdata; } }, _initClassName: function(prefix_cls) { var styleId = this.config[CFG_STYLE_ID]; if (styleId == PREFIX_CLS + "style") {//判断是否用户自定义id styleId = prefix_cls + "style"; } PREFIX_CLS = prefix_cls; CLS_CONTAINER = PREFIX_CLS + "sug-list-container"; CLS_CONTENT = PREFIX_CLS + "sug-list-content"; CLS_ITEM_SELECTED = PREFIX_CLS + "sug-item-selected"; CLS_ITEM_KEY = PREFIX_CLS + "sug-item-key"; CLS_ITEM_RESULT = PREFIX_CLS + "sug-item-result"; }, _initStyle: function() { var styleId = this.config[CFG_STYLE_ID]; if (DOC.getElementById(styleId) && !PREFIX_CLS) return; //已存在并样式前缀相同时不重复初始化该样式 var s = {}; s["." + CLS_CONTAINER] = "border: 1px solid #ccc;background: #fff;z-index: 9999;overflow: hidden;"; s["." + CLS_CONTENT] = "overflow: hidden;"; s["." + CLS_CONTENT + SPACE + "ul"] = "margin:0;padding:0;"; s["." + CLS_CONTENT + SPACE + "li"] = "cursor: default;color: #333;font-size: 14px;line-height: 20px;clear: both;float: left;width: 100%;overflow: hidden;text-decoration: none;padding: 2px 0 3px 2px;margin: 0;"; s["." + CLS_CONTAINER + SPACE + "li" + SPACE + "span." + CLS_ITEM_KEY] = "float:left;"; s["." + CLS_CONTAINER + SPACE + "li" + SPACE + "span." + CLS_ITEM_RESULT] = "float:right"; s["." + CLS_CONTAINER + SPACE + "." + CLS_ITEM_SELECTED] = "background:#ddd;"; this._setCss(s, styleId); }, _setCss: setCss, _initialize: function() { this._initTextInput(); this._initContainer(); if (isIE6) this._initShim(); //初始化 this._initSugList(); this._initEvent(); }, _initTextInput: function() { var textInput = this.textInput; textInput.setAttribute("autocomplete", "off"); if (this.config["autofocus"]) { textInput.focus(); } }, _initContainer: function() { var that = this, container = document.createElement('div'), content = document.createElement('div'); container.style.cssText = "position:absolute; visiblility:hidden;"; addClass(container, CLS_CONTAINER); addClass(content, CLS_CONTENT); container.appendChild(content); BODY.insertBefore(container, BODY.firstChild); that.container = container; that.content = content; }, _setContainerRegion: function() { var that = this; var inputCoords = getCoords(that.textInput); that.container.style.left = inputCoords.left + "px"; that.container.style.top = inputCoords.top + that.textInput.offsetHeight + that.config["offsettop"] + "px"; var configWidth = that.config["containerwidth"]; that.container.style.width = (configWidth || (that.textInput.offsetWidth - 2)) + "px"; }, _initShim: function() { var iframe = document.createElement('iframe'); iframe.src = "about:blank"; iframe.style.cssText = "position:absolute;visibility:hidden;border:none;"; this.container.shim = iframe; BODY.insertBefore(iframe, BODY.firstChild); }, _setShimRegion: function() { var that = this, container = that.container, shim = container.shim, style = container.style; if (shim) { shim.style.left = style.left; shim.style.top = style.top; shim.style.width = container.offsetWidth + "px"; //auto width时需要精确获取 shim.style.height = container.offsetHeight + "px"; } }, _initEvent: function() { var that = this; addEvent(window, 'resize', function() { that._setContainerRegion(); that._setShimRegion(); }); //input keyup //文本框内容改变触发 addEvent(that.textInput, 'input', function(e) { //使用当前this作为环境 that._inputValue(e); }); addEvent(that.textInput, 'propertychange', function(e) { that._inputValue(e); }); //ie //hide that.textInput.onblur = function() { that._hide(); callee._focusInstance = UNDEFINED; } //up down //上下按,选择内容触发 //addEvent(document, 'keypress', _keyUpDown);//chrome,safari,ie 问题 //keyup时chrome阻止光标移动到最前无效 //ie向下选择后,输入框中值改变触发事件,//opera 一开始不行,后来向下选择按钮有效。。。 addEvent(that.textInput, 'keydown', function(e) { that._keyUpDown(e); }); addEvent(that.textInput, 'keyup', function() { mouseMoveFlag = true; }); addEvent(that.textInput, 'focus', function(e) { //sugList._show.call(sugList); }); addEvent(that.textInput, 'click', function(e) { //click处hold会在ie中需要2次点击空处才能blur //holdFocus(e, that.textInput); //sugList._show.call(sugList); }); //点击选中下面选项 addEvent(that.content, 'mousedown', function(ev) { if (!mouseMoveFlag) { holdFocus(ev, that.textInput); return; } // 非左键和中键点击 if (eWhich(ev).which > 2) { //ie下右键后input仍然有焦点为activeElement但是光标不显示 holdFocus(ev, that.textInput); return; } ev = ev || window.event; var elem = ev.target || ev.srcElement; elem = matchElem(elem, that.content, function() { return this.tagName.toLowerCase() == 'li'; }); that.setSelectedIndexByValue(elem); that.selectItem.call(that, elem); holdFocus.call(that, ev, that.textInput); }); //"hover": //list item高亮 mouseover addEvent(that.content, 'mousemove', function(e) { if (!mouseMoveFlag) return; e = e || window.event; var elem = e.target || e.srcElement, li = matchElem(elem, that.content, function() { return this.tagName.toLowerCase() == 'li'; }); if (!li) return; //同一个li会触发2-3次,在remove时判断,是否需要remove that.selectItemByMouse(li); }) }, _inputValue: function(e) { e = e || window.event; var _keyCode = e.which || e.keyCode, that = this, val = that.textInput.value; callee._focusInstance = that; //? //that._CurrentQueryWord = val; //propertychange,输入框val可能会被赋初值,当val与lockie相同,如果无数据需要继续请求;使用ctrl+z/y时锁住不往下请求数据 if (val === lockIE && (that._HasData || ctrlLockFlag)) return; /*若需要不断更新list,选中listItem后根据value继续更新list则在此更新lockIE,不在select时同时更新 * 修复右键后左击显示2次list,确保lockIE为最新,更新list后也要保证lockIE值也是最新 */ //初始化时不检索,此时:lockIE无数据,val为空或为默认值(当lockIE有数据时,需要隐藏下面list,需要进入_reqDiute延时处理,防止交叉) if (!lockIE && (!val || val == DEFAULT_VALUE)) return; val && that._QueryData.push(val); lockIE = val; //请求数据 that._reqDiute(val); }, _keyUpDown: function(e) { e = e || window.event; var _keyCode = e.which || e.keyCode, that = this, targetEl = e.target || e.srcElement; if (targetEl == that.textInput) { //Enter,在ie_tester中ie6-7检测不到enter键,原生ie6-7可以检测到 if (_keyCode == 13 || _keyCode == 108) { return that.selectItem(that.getSelectedItemByIndex()); } //可以用event.altKey,event.ctrlKey,event.metaKey(上有微软的旗帜),event.shiftKey来判断对应的键是否被按下 if (e.ctrlKey) { var qData = that._QueryData, qDataIndex = qData._QueryIndex, maxIndex = qData.length - 1, minIndex = -1; //qData._QueryIndex qData._QueryIndex = qDataIndex = qDataIndex == -1 ? maxIndex : qDataIndex; if (_keyCode == 89) { //前进 if (qDataIndex < maxIndex) {//最大值问题。。。这里修改值时,会再次记录querydata... //?是否保证的了ctrlLockFlag瞬时为true,ff下不会触发oninput事件 ctrlLockFlag = true; targetEl.value = lockIE = ++qDataIndex <= maxIndex ? qData[++qData._QueryIndex] : qData[qData._QueryIndex]; setTimeout(function() {//改变值瞬时不为false; ctrlLockFlag = false; }, 0); //保证_CurrentQueryWord即时性,不在延时后修正? //that._CurrentQueryWord = lockIE; that._reqDiute(lockIE); } stopEvent(e); //阻止默认ctrl+y return; } else if (_keyCode == 90) { //后退 if (qDataIndex > minIndex) { ctrlLockFlag = true; targetEl.value = lockIE = --qDataIndex > -1 ? qData[--qData._QueryIndex] : EMPTY; setTimeout(function() { ctrlLockFlag = false; }, 0); //从无数据到有数据hasData //that._CurrentQueryWord = lockIE; that._reqDiute(lockIE); } stopEvent(e); return; } } if (_keyCode == 27) { that._hide(); } var upOrDown = 0; if (_keyCode === 38 || _keyCode === 104) { upOrDown--; } else if (_keyCode === 40 || _keyCode == 98) { upOrDown++; } //执行向上向下选择 if (upOrDown) { mouseMoveFlag = false; //失去焦点后回来按up,down继续显示sugList._show()并判断是否有数据_HasData //如果是从未显示状态到显示,则第一次按键不修改selectedIndex if (!that.getDisplayState()) { that._show.call(that); //如果显示不了,则是没数据,其他地方保证数据一致性 } else { that._selectItemByIndexIncrease(upOrDown); } //阻止光标在chrome下,按up时跳到最前 e.preventDefault && e.preventDefault(); //ie下阻止输入框不显示光标,但任然有焦点为激活对象时,按up,down时,body的scroll也跟着滚动 e.returnValue = false; return false; } } }, /* selectedIndex,selectedItem,selectedValue *keyboard:高亮显示某项(1), * updown:取消前面选中项(2),选中新项(3),给input赋当前项值(4) * enter:隐藏list,使用该选中项(5), 执行跳转等操作(6) *mouse: * mousemove:取消前面选中项(2),选中新项(3) * mousedown:选中鼠标停留项(3),给input赋当前项值(4),隐藏List,使用该选中项(5), 执行跳转等操作(6) */ //select item 最终 选中某项 //mouse,key选中后直接跳转操作,还是再按submit/enter进行进一步操作? selectItem: function(elem) { this._regisEvent("onbeforeselect"); if (elem != selectedItem) { this.removeSelectedItem(); this.setSelectedItem(elem); } this.updateInputBySelectedItem(elem); this._hide(); }, //up down延时 __updownDiute__: function(updownDelay) { var that = this; return diuteEvent(function(upDownNum) { selectedIndex += upDownNum; var elem = that.getSelectedItemByIndex(); that.removeSelectedItem(); that.setSelectedItem(elem); that.updateInputBySelectedItem(elem); }, updownDelay); }, _updownDiute: NULL, //up down 键盘,根据index的增量大小,来显示选中项,使用延迟时间 //稀释up down按键,避免过快操作 _selectItemByIndexIncrease: function(upDownNum) { this._updownDiute.call(this, upDownNum); }, _reqDiute: NULL, //请求数据,100毫秒间隔 __reqDiute__: function(inputDelay) { var that = this; return diuteEvent(function(val) { this._regisEvent("onbeforerequest"); //val为空或者默认定义的初始值时,不请求数据,不更新lockIE,延时处理可能出现请求未完成,已经初始化,后显示未完成的请求 if (!val) { that._initSugList(); return; } that._updataContent(val, that._DataType); }, inputDelay); }, //mouse hover,会触发多次,因为多个子节点到li? selectItemByMouse: function(elem) { if (elem != selectedItem) { this.removeSelectedItem(); this.setSelectedItem(elem); //set selectedIndex this.setSelectedIndexByValue(elem); } }, //set selectedIndex setSelectedIndexByValue: function(item) { return (selectedIndex = indexOfArray(item, this._get())); }, //根据selectIndex获取selectItem getSelectedItemByIndex: function() { var list = this._get(), count = list.length || 0; //调整selectedIndex位于0和count-1之间 (selectedIndex >= count) && (selectedIndex = 0); (selectedIndex < 0) && (selectedIndex = count - 1); return list[selectedIndex]; }, //2移除之前选中项 removeSelectedItem: function() { if (selectedItem) { do { removeClass(selectedItem, CLS_ITEM_SELECTED); } while (hasClass(selectedItem, CLS_ITEM_SELECTED)); selectedItem = null; } }, //3设置选中项 setSelectedItem: function(item) { if (item) { addClass(item, CLS_ITEM_SELECTED); selectedItem = item; } }, /*4更新input中文字内容 * elem:selectedItem */ updateInputBySelectedItem: function(elem) { //#2更新lockie值,=运算从右往左执行 if (elem) { var input = this.getSelectedItemValue(elem); input && (this.textInput.value = lockIE = input); //记录Ctrl+z/y this._QueryData.push(input); } }, //获取对应item选中项的值,默认为当前选中项的值 getSelectedItemValue: function(elem) { elem = elem || selectedItem; elem = matchElem(elem, this.content, function() { return this.tagName.toLowerCase() == 'li'; }); if (elem) { //鼠标点击或enter选中某项后,不需要更新sug数据 elem = matchChild(elem, function() { return this.tagName.toLowerCase() == 'span' && hasClass(this, CLS_ITEM_KEY); //(this.className.indexOf('sug-key') != -1); }, 0); if (elem) { return elem.innerText || elem.textContent || elem.innerHTML || ''; //elem.innerHTML; //?是否将''空值换成当前input中的值 } } }, //jsonp request _requestData: function(q) { var that = this, config = this.config; //ie6-8的script直接替换src请求新数据,其他浏览器不重建script则不请求新数据 if (!reqScript || !isIE || isIE9) { var _script = document.createElement('script'), _head = document.getElementsByTagName('head')[0]; _script.type = "text/javascript"; //async不能解决异步执行的问题,只能是异步加载 //如果 async 属性为 true,则脚本会相对于文档的其余部分异步执行,这样脚本会可以在页面继续解析的过程中来执行。 _script.async = true; if (reqScript) { _head.replaceChild(_script, reqScript); } else { _head.appendChild(_script); //ie6-8 load //ie不支持onerror, opera与ie支持readyState if (isIE) { _script.onreadystatechange = function(e) { e = e || window.event; //opera的script也存在readyState,但如果请求地址不存在,是不会进入onload回调的 //http://www.cnblogs.com/_franky/archive/2010/06/20/1761370.html#1875070 if (isIE && /loaded|complete/i.test(_script.readyState)) { //ie会执行2次 that._regisEvent("onrequestload"); } }; } } reqScript = _script; //仅仅对非ie6-8有效判断获取过来的数据是否过期?,ie会自动abort之前的请求,不在触发onload var t = now(); _script.setAttribute('data-time', t); latestReqScriptTime = t; //_script这个唯一的,用来替换的script //error url:op返回1个load;ff、chrome返回请求数量的error;safari返回1个error _script.onload = function() { that._regisEvent("onrequestload"); //判断返回的数据是否已经过期,true则过期 scriptDataIsOut = (_script.getAttribute('data-time') != latestReqScriptTime); }; _script.onerror = function() { }; /*加载script * http://www.cnblogs.com/rubylouvre/archive/2011/02/13/1953087.html * http://www.cnblogs.com/rubylouvre/archive/2011/03/01/1968397.html */ } /*过期: * 1,发送请求a前,产生最新请求b,取消a使用b; * 2,请求a返回数据,此时请求b,c..等已经发出,不使用a,等待最后一个请求返回数据; */ reqScript.charset = config.charset; //"utf-8"; baidu使用gbk编码 /*var t = new Date(); reqScript.setAttribute('data-time', t); latestReqScriptTime = t; */ //第1种过期? //script src设置,ie6-8已最后设置值为准,可赋值更换,chrome以先设置的值为准,不再更换... //最好先给script 设置 src属性 然后 再 appedChild 他 到 DOM树中,这样ie6中loaded与complete出现顺序不会乱 reqScript.src = config.datasource + "&" + config.queryname + "=" + encodeURIComponent(q) + "&t=" + now(); }, _initCallback: function(_callbackFn) { if (!_callbackFn) return; if (typeof _callbackFn === 'string') { var context = setNamespace(_callbackFn), lastName = _callbackFn.split('.').pop(); context[lastName] = this._jsonpCallback; } else { _callbackFn = this._jsonpCallback; } }, _jsonpCallback: function(data) { var that = callee._focusInstance; if (scriptDataIsOut || !data || !that) return; /* * 超时判断不靠谱,如果第一个请求未超时阶段发送第2个请求,并返回数据。。。 * 目前返回数据不会出现2个数据先后颠倒,并只返回一个有用数据 */ that._PreHandleData = data; that._handlePreData(data); }, //原始数据处理入口 _handlePreData: function(preHandleData) { this._HandledData = this._formatData(preHandleData); var outHTML = this.buildHTML(this._HandledData); //如果没数据,则在_set初始化中会隐藏suglist this._set(outHTML) && this._show(); }, /*数据处理 1,config数据源 || _requestData 2,config呈现方法 || buildHTML */ _formatData: function(data) { var key = this.config[CFG_RESULT_KEY]; data = key ? data[key] : data; if (!isLocalDataSource) { //return data["result"];//taobao return data; //baidu } //local datasource var len = data.length, ret = []; for (len; len--; ) { if (data[len].indexOf(this._CurrentQueryWord) != -1) {//?过滤规则 ret.push(data[len]); } } return ret; }, /*获取、请求数据入口 0,cache+url 1,cache+config.datasource 2,url 3,config.datasource */ _updataContent: function(val, this_DataType) { //确保是当前正在执行的_CurrentQueryWord作为进一步确认修正? this._CurrentQueryWord = val; switch (this_DataType) {//this._DataType case 0: var cacheValue = this._CacheData[val]; if (cacheValue) { this._set(cacheValue) && this._show(); } else { this._requestData(val); } break; case 1: var cacheValue = this._CacheData[val]; if (cacheValue) { this._set(cacheValue) && this._show(); } else { this._handlePreData(this._PreHandleData); } break; case 2: this._requestData(val); break; case 3: this._handlePreData(this._PreHandleData); break; default: break; } }, buildHTML: function(arr) { var html = ''; if (isArray(arr)) { var len = arr.length; //html += "<ul>"; for (var i = 0; i < len && i < 10; i++) { //html += '<li><a href="javascript:void(0)" >' + arr[i][0] + '<span style="float:right;">'+arr[i][1]+'</span></a></li>'; html += '<li>'; html += '<span class="' + CLS_ITEM_KEY + '">' + arr[i] + '</span>'; //arr[i][0] //html += '<span class="'+CLS_ITEM_RESULT+'">' + arr[i][1] + '</span>'; html += '</li>'; } //html += '</ul>'; } //保存键值cache if (this.config.usecache) this._CacheData[this._CurrentQueryWord] = html; return html; }, _initSugList: function() { //获取到新的sugList时初始化参数 selectedIndex = -1; selectedItem = null; this._HasData = false; this.content.innerHTML = ''; this._hide(); }, "_get": function() { return this.content.getElementsByTagName('li'); }, "_set": function(outHTML) {//_set()清空 this._initSugList.call(this); //确保不展示延时的数据,与输入框不匹配 if (outHTML && this.textInput.value == this._CurrentQueryWord) { //设置sugListWrap内容 this.content.innerHTML = "<ul>" + outHTML + "</ul>"; return this._HasData = true; } else { return false; //this._HasData = false; //that.content.innerHTML = ''; //_initSugList中以作清空 } }, "getDisplayState": function() {//注意是方法..getDisplayState() return this.container.style.visibility.toLowerCase() != HIDDEN; }, _show: function() { this._regisEvent("onbeforeshow"); //确认无数据情况下清空sugList if (this._HasData) { if (DOC.activeElement != this.textInput) return; var container = this.container, shim = this.container.shim; //显示前重新计算位置 this._setContainerRegion(); visible(container); if (shim) { this._setShimRegion(); visible(shim); } return true; } return false; //sugList._initSugList(); }, _hide: function() { var shim = this.container.shim; if (shim) invisible(shim); invisible(this.container); this._regisEvent("onhide"); } }; //设置短时间连续执行时,光标会停止跳动 //setInterval(function() { }, 30); //stop ie go back document.onkeydown = function(e) { e = e || window.event; var _keyCode = e.which || e.keyCode, targetEl = e.target || e.srcElement; if ((targetEl == document.body || targetEl == document.documentElement) && _keyCode == 8) { return false; } }; //根据string: 如a.b.c获得当前全局执行环境相应命名空间对象 //只返回到倒数第2个对象 function setNamespace(_namespace, context) { context = context || WIN; if (!_namespace) return context; //去掉window开头的部分 _namespace = _namespace.replace(/^window\.?/, ''); var arr = _namespace.split('.'), len = arr.length, i = 0, p; for (; i < len - 1; i++) { p = arr[i]; if (p in context) { context = context[p]; } else { context = context[p] = {}; //需要更新context[p]->context } } return context; } function visible(elem) { elem.style.visibility = EMPTY; } function invisible(elem) { elem.style.visibility = HIDDEN; } //对象拷贝 function deepCopy(result, source) { for (var key in source) { var copy = source[key]; if (result === copy) continue; if (isPlainObject(copy)) { result[key] = arguments.callee(result[key] || {}, copy); } else if (isArray(copy)) { result[key] = arguments.callee(result[key] || [], copy); } else { result[key] = copy; } } return result; } function isPlainObject(obj) { if (!obj || toString.call(obj) !== "[object Object]" || obj.nodeType || obj.setInterval) { return false; } if (obj.constructor && !hasOwnProperty.call(obj, "constructor") && !hasOwnProperty.call(obj.constructor.prototype, "isPrototypeOf")) { return false; } var key; for (key in obj) { } return key === undefined || hasOwnProperty.call(obj, key); } function isArray(arr) { return toString.call(arr) == ARRAY_VALUE; } //indexOf function indexOfArray(item, arr) { for (var i = 0, len = arr.length; i < len; ++i) { if (arr[i] === item) { return i; } } return -1; } //稀释事件,interval 执行密度 function diuteEvent(fn, interval, args) { if (typeof fn !== 'function' || typeof interval !== 'number' || interval < 0) { return fn; } var timeStamp = 0, timer = null; return function() { clearTimeout(timer); var _args = [].slice.call(arguments, 0).concat(args), self = this, now = +new Date; if (now - timeStamp >= interval) { timeStamp = now; return fn.apply(self, _args); //self } else { //消除误差 var delay = interval + timeStamp - now; timer = setTimeout(function() { fn.apply(self, _args); timeStamp = +new Date; }, delay > 15 ? delay : 15); } }; } //elem是否在wrap子孙节点内 function isElemInWrap(elem, wrap) { wrap = wrap || document.documentElement || document.body; while (elem) { if (elem == wrap) return true; elem = elem.parentNode; } } //匹配子元素elem的父元素中符合fn在wrap内的元素,返回第一个符合条件的elem function matchElem(elem, wrap, fn) { wrap = wrap || document.documentElement || document.body; while (elem !== wrap) { if (fn.call(elem, elem)) return elem; //不再往上检索document.docElement,没有tagName等属性(ie6没问题,有documentElement去掉<doc声明) ? body if (elem == document.documentElement) return; elem = elem.parentNode; } return; //return null? } //获取elem子元素符合fn条件的第eq(0-N)个childNode function matchChild(elem, fn, eq) { eq = eq || 0; var cNodes = elem.childNodes; len = cNodes.length, num = 0; for (var i = 0; i < len; i++) { if (fn.call(cNodes[i], cNodes[i])) { //符合条件的第num个 if (num == eq) { return cNodes[i]; } else { eq++; } } } return null; } //使输入框elem不失去焦点 function holdFocus(ev, elem) { ev = ev || window.event; if (ev.preventDefault) { //w3c ev.preventDefault(); } else { //ie elem.onbeforedeactivate = function() { ev.returnValue = false; elem.onbeforedeactivate = null; }; } } //获取鼠标按键值,左1中2右3 function eWhich(ev) { ev.which = (ev.button & 1 ? 1 : (ev.button & 2 ? 3 : (ev.button & 4 ? 2 : 0))); //ev.which = (ev.charCode === undefined) ? ev.keyCode : ev.charCode; return ev; } //bind event function addEvent(elem, evType, fn, useCapture) { if (document.addEventListener) { elem.addEventListener(evType, fn, false); } else if (document.attachEvent) { //elem.attachEvent('on' + evType, fn); //ie下,第一参数传入window.event这样不用一直取e||window.event; elem.attachEvent('on' + evType, function() { fn.call(elem, window.event); }) } else { elem['on' + evType] = fn; } return elem; } //unbind event function removeEvent(elem, evType, fn, useCapture) { if (document.removeEventListener) { elem.removeEventListener(evType, fn, useCapture); } else if (document.detachEvent) { elem.detachEvent('on' + evType, fn); } else { elem['on' + evType] = null; } return elem; } //阻止默认事件、冒泡 function stopEvent(e) { e = e || window.event; if (e.preventDefault) { e.preventDefault(); e.stopPropagation(); } else { e.returnValue = false; e.cancelBubble = true; } } //阻止冒泡 function stopPropagation(e) { e = e || window.event; if (e.stopPropagation) { e.stopPropagation() } else { e.cancelBubble = true; } } //阻止默认事件 function preventDefault(e) { e = e || window.event; if (e.preventDefault) { //chrome下可阻止按up键时,输入框中光标跳到最前 e.preventDefault(); } else { e.returnValue = false; } } //获取元素在页面上的位置 function getCoords(el) { var box = el.getBoundingClientRect(), doc = el.ownerDocument, body = doc.body, html = doc.documentElement, clientTop = html.clientTop || body.clientTop || 0, clientLeft = html.clientLeft || body.clientLeft || 0, top = box.top + (self.pageYOffset || html.scrollTop || body.scrollTop) - clientTop, left = box.left + (self.pageXOffset || html.scrollLeft || body.scrollLeft) - clientLeft; return { 'top': top, 'left': left }; } //可添加多个className; "aaa bbb ccc" 消除重复 function addClassNoRepeat(elem, value) { if (elem.nodeType === 1 && value && typeof value === 'string') { if (!elem.className) { elem.className = value; } else { //className解释效果时,只与style在html中出现的先后顺序为准,以最后一个出现的同名className解释效果,与className.sort()无关 //消除className重复内容 var classNames = (elem.className + " " + value).match(/\s+/g).sort(); //rspace=/\s+/g for (var len = classNames.length; --len; ) { if (classNames[len] == classNames[len - 1]) classNames.splice(len, 1); } elem.className = classNames.join(" "); } } } //多node多className添加(div,'aaa bbb ccc'),不去除重复 function addClass(elems, value) { if (value && typeof value === 'string') { var classNames = (value || '').split(/\s+/g); //elems为ElementNode时手动加进数组。。。?htmlCollection if (elems && elems.nodeType === 1) { elems = [elems]; } for (var i = 0, l = elems.length; i < l; i++) { var elem = elems[i]; if (elem.nodeType === 1) { if (!elem.className) { elem.className = value; } else { //className有重复无效果上影响,只判断消重新添加的className var className = " " + elem.className + " ", setClass = elem.className; for (var c = 0, cl = classNames.length; c < cl; c++) { if ((className.indexOf(" " + classNames[c]) + " ") < 0) { setClass += " " + classNames[c]; } } elem.className = rtrim(setClass); } } } } return elems; } //删除elems上的classNames,不去除重复的className,不传value则清空className function removeClass(elems, value) { if ((value && typeof value === 'string') || value === void 0) {//void 0==undefined var classNames = (value || "").split(/\s+/g); if (elems && elems.nodeType === 1) { elems = [elems]; } for (var i = 0, l = elems.length; i < l; i++) { var elem = elems[i]; if (elem.nodeType === 1 && elem.className) { if (value) { var className = (" " + elem.className + " ").replace(/[\n\t]/g, " "); for (var c = 0, cl = classNames.length; c < cl; c++) { className = className.replace(" " + classNames[c] + " ", " "); } elem.className = rtrim(className); } else { elem.className = ""; } } } } return elems; } function hasClass(elem, value) { var className = " " + value + " "; return ((" " + elem.className + " ").replace(/[\n\t]/g, " ").indexOf(className) > -1); } function rtrim(value) { return (value || '').replace(/^(\s|\u00A0)+|(\s|\u00A0)+$/g, ""); } /* 数组去重 * for第三个条件不管j--还是--j都是先返回j,然后计算,下一次才使用计算后的值 * 数组中超过数组索引的值,取当前最后一项索引值 * for中第2个条件,初始化时会先执行一次,然后每次循环判断时执行一次 */ function removeRepeatItem(items) { if (toString.call(items) === ARRAY_VALUE) { items.sort(); for (var len = items.length; --len; ) { if (items[len] == items[len - 1]) items.splice(len, 1); } } return items; } /** * 糅杂,为一个对象添加更多成员 * @param {Object} target 目标对象 * @param {Object} source 属性包 * @return {Object} 目标对象 */ function mix(target, source) { var args = [].slice.call(arguments), key, ride = typeof args[args.length - 1] == "boolean" ? args.pop() : true; target = target || {}; for (var i = 1; source = args[i++]; ) { for (key in source) { if (ride || !(key in target)) { target[key] = source[key]; } } } return target; } /*运行版本 创建style标签来添加样式,相同id时不创建新style * 信息保存到style id对应node上 * 传入页面上自添加的styleNodeId时,memory为空,没有影响 * @param Object hash:{selector:declaration....} => {"#bodyid":"width:200px;height:200px;"} * @param string styleNodeId,标签id */ function setCss(hash, styleNodeId) { var _styleNode = document.getElementById(styleNodeId), _sheet = _styleNode ? (_styleNode.sheet || _styleNode.styleSheet) : null, _createStyleSheet = (function() { function createStyleSheet(__sheet__) { var sheet = null; if (!__sheet__) { var element = document.createElement('style'); element.type = 'text/css'; element.id = styleNodeId; //id document.getElementsByTagName('head')[0].appendChild(element); sheet = element.sheet || element.styleSheet; } else { sheet = __sheet__; } if (typeof sheet.addRule === 'undefined') {//ff sheet.addRule = function(selectorText, cssText, index) { if (typeof index === 'undefined') index = this.cssRules.length; this.insertRule(selectorText + ' {' + cssText + '}', index); }; } return sheet; } return createStyleSheet; })(); if (_sheet && _sheet.memory) { for (var key in hash) { if (!_styleNode.memory.exists(key, hash[key])) { _styleNode.memory.set(key, hash[key]); _sheet.addRule(key, hash[key]); } } } else { if (_sheet) { _sheet = _createStyleSheet(_sheet); } else { _sheet = _createStyleSheet(); } _styleNode = _sheet.ownerNode || _sheet.owningElement; var memory = function() { var keys = [], values = [], size = 0; return { get: function(k) { var results = []; for (var i = 0, l = keys.length; i < l; i++) { if (keys[i] == k) { results.push(values[i]) } } return results; }, exists: function(k, v) { var vs = this.get(k); for (var i = 0, l = vs.length; i < l; i++) { if (vs[i] == v) return true; } return false; }, set: function(k, v) { keys.push(k); values.push(v); size++; }, length: function() { return size; } } } _styleNode.memory = memory(); for (var key in hash) { _styleNode.memory.set(key, hash[key]); _sheet.addRule(key, hash[key]); } } } return new Sug(textInput, config); } /*暂不解决 timeout jq 5241,多个请求顺序不统一,可否js中断一个请求,不再返回数据? 右键list后,输入框有焦点但不显示光标 汉字输入 宽度自适应,最大、最小宽度(不设置容器宽度ie7,se360,ie6中li会自动延生最右边若float:left则li不会充满改行) */