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不会充满改行)
*/

 github:https://github.com/thomas1986/suggest嘿嘿

posted on 2012-05-03 16:24  LcKey  阅读(670)  评论(0编辑  收藏  举报