自动补全搜索实现

目前大多数搜索框都已实现自动补全功能,自己也私底下实现了一个简易版本,

在此总结过程中的一些要点:

  1,侦听文本框的value值改变,注意在Ie8及其之前版本的onpropertychange和Ie9的oninput事件与

  W3C下的oninput事件的异同;

  2,ajax请求数据;

  3,自动补全框的定位;

  4,上下键导航以及鼠标导航

 

在此附上源码:

  

        .auto-ul{
            list-style: none;
            padding: 0;
            margin: 0;
        }
        .auto-ul li{
            margin: 0;
            padding:5px;
        }
        .auto-ul a{
            text-decoration: none;
            color: black;
        }
        .w{
            background: #cecece;
            cursor: default;
        }
        input{
            border: 1px solid #c0c0c0;
        }
<div class="w250 h200 bgf0 auto tc pt15">
        <div>
            <form>
                <label for="t">username:&nbsp;</label>
                <input type="text" id="t" autocomplete="false" class="p5 w150 lh22">
            </form>
        </div>
 </div>
        items.txt文件    
<ul>
    <li>
        <a href="#" class="db">java</a>
    </li>
    <li>
        <a href="#" class="db">javaWeb</a>
    </li>
    <li>
        <a href="#" class="db">javaScript</a>
    </li>
    <li>
        <a href="#" class="db">javaScript & CSS</a>
    </li>
</ul>
     function $(m){
            return document.getElementById(m)
        }
        function createBox(){
            var div = document.createElement('div'),w;
            w = $('t').offsetWidth;
            div.id = 'box';
            div.style.cssText = 'position:absolute;width:'+(w-2)+'px;border:1px solid #cecece;display:none;';
            div.innerHTML = '<ul class="auto-ul" id="autobox-ul"></ul>'
            return div;
        }
        function showBox(d,boxLocation){
            d.style.display = '';
            d.style.left = boxLocation.left + 'px';
            d.style.top = boxLocation.top + 'px';
        }
        function hideBox(d){
            d.style.display = 'none';
            d.getElementsByTagName('ul')[0].innerHTML = '';
        }
        //创建xhr
        function createXHR(){
            if('XMLHttpRequest' in window){
                createXHR = function(){
                    return new XMLHttpRequest();
                }
            }else{
                var i= 0,len, fns = [function(){return new ActiveXObject('Microsoft.XMLHTTP')},function(){return new ActiveXObject('Msxml2.XMLHTTP')},
                    function(){return new ActiveXObject('Msxml2.XMLHTTP.3.0')},function(){return new ActiveXObject('Msxml2.XMLHTTP.6.0')}];

                for(len = fns.length;i<len;i++){
                    try{
                        fns[i]();
                        createXHR = fns[i];
                        break;
                    }catch (e){
                    }
                }
            }
            return createXHR();
        }
        // ajax实现
        function ajaxInit(ajaxData){
            var xhr = createXHR(),get_data,isLoaded = false,
                    map = {
                        'html': 'text',
                        'arraybuffer': 'arraybuffer',
                        'blob': 'blob',
                        'document': 'document',
                        'json': 'text'
                    };
            ajaxData.onBefore = ajaxData.onBefore || function(){};
            ajaxData.onSuccess = ajaxData.onSuccess || function(){};
            ajaxData.onFailure = ajaxData.onFailure || function(){};
            ajaxData.onComplete = ajaxData.onComplete || function(){};
            ajaxData.timeout = ajaxData.timeout || 5000;
            ajaxData.type = ajaxData.type || 'post';
            /**
             * 'text':返回类型为字符串,这是默认值。
                'arraybuffer':返回类型为ArrayBuffer。
                'blob':返回类型为Blob。
                'document':返回类型为Document,用于xml。
                'json':返回类型为JSON object,支持JSON的浏览器(Firefox>9,chrome>30),
                就会自动对返回数据调用JSON.parse() 方法。也就是说,你从xhr.response属性
                (注意,不是xhr.responseText属性)得到的不是文本,而是一个JSON对象。
             */
            if(xhr.responseType)
                   xhr.responseType = ajaxData.responseType in map ? map[ajaxData.responseType] : 'text';

            ajaxData.onBefore();
            xhr.open(ajaxData.type,ajaxData.url,true);
            xhr.onreadystatechange = function(){
                if(xhr.readyState == 4 && !isLoaded){
                    // 判断响应成功的几点:
                    // 1,如果是访问本地文件,请求成功但不会获得响应码
                    // 2,IE(通过ActiveXObject创建的xhr对象)会将204设置为1223
                    // 3, opera会将204设置为0
                    if(!xhr.status && location.protocol == 'file:'
                            || xhr.status == 1223
                            || xhr.status == 0 || xhr.status >= 200 && xhr.status < 300
                            || xhr.status == 304){
                        if(ajaxData.responseType.toLowerCase() == 'json'){
                            get_data = 'JSON' in window ? JSON.parse(xhr.responseText) :
                                    new Function('return ' + xhr.responseText + ";");
                        }else if(ajaxData.responseType.toLowerCase() == 'html'){
                            get_data = xhr.responseText;
                        }else if(xhr.responseType.toLowerCase() == 'document'){
                            get_data = xhr.responseXML;
                        }else{
                            get_data = xhr.response;
                        }
                        ajaxData.onSuccess(get_data);
                    }else{
                        ajaxData.onFailure();
                    }
                    ajaxData.onComplete();
                    xhr = null;
                }
            }

            setTimeout(function(){
                isLoaded = true;
            },ajaxData.timeout);
            if(ajaxData.type.toLowerCase() == 'get'){
                xhr.setRequestHeader('X-Request-With','XMLHttpRequest');
                xhr.send(null);
            }else if(ajaxData.type.toLowerCase() == 'post' && ajaxData.data){
                xhr.setRequestHeader('X-Request-With','XMLHttpRequest');
                xhr.setRequestHeader('content-type','application/x-www-form-urlencoded');
                xhr.send(ajaxData.data);
            }

            return xhr;
        }
        function addEvent(el,type,fn){
            if(window.addEventListener){
                el.addEventListener(type,fn,false)
            }else{
                el.attachEvent('on'+type,function(){
                    var e = window.event;
                    e.preventDefault = function(){
                        e.returnValue = false;
                    };
                    e.stopPropagation = function(){
                        e.cancelBubble = true;
                    }
                    fn.call(el,e);
                })
            }
        }
        //给文本框绑定事件
        function bindEvent(t,fn){
            var input = t;
            //对输入框绑定事件
            if(input.addEventListener){
                input.addEventListener('input',fn,false);
            }else{
                input.attachEvent('onpropertychange',function(){
                    var e = window.event;
                    if(e.propertyName == 'value'){
                         fn();
                    }
                });
            }

            if(window.VBArray && window.addEventListener && !window.WebSocket){
                input.addEventListener('keyup',function(e){
                    var code = e.keycode || e.charcode;
                    if(code==8 || code==46){
                        fn();
                    }
                },false) ;
                input.oncut=function(){fn()};
            }

            //对按键事件侦听
            addEvent(input,'keydown',function(e){
                var l,ol = -1,nl;
                if(e.keyCode == 40 || e.keyCode == 38){
                    e.preventDefault();
                    l = $('autobox-ul').getElementsByTagName('li');
                    for(var i=0,len=l.length;i<len;i++){
                        if(l[i].className == 'w'){
                            ol = i;  //保存当前选定的选项
                        }
                        l[i].className = '';
                    }
                    if(e.keyCode == 40 || e.charCode == 40){ //下箭头
                        ol++;
                        if(ol <= len-1){

                            l[ol].className = 'w';
                            nl = l[ol];
                        }else{
                            l[0].className = 'w';
                            nl = l[0];
                        }
                    }else if(e.keyCode == 38 || e.charCode ==38){ //上箭头
                        ol--;
                        if(ol >= 0){
                            l[ol].className = 'w';
                            nl = l[ol];
                        }else{
                            l[l.length-1].className = 'w';
                            nl = l[l.length-1];
                        }
                    }
                    this.value = nl.getElementsByTagName('a')[0].innerHTML;
                    nl.className = 'w';
                }
            });
            addEvent($('box'),'mousemove',function(e){
                var t = e.target || e.srcElement,
                        l = $('autobox-ul').getElementsByTagName('li');
                e.preventDefault();
                for(var i= 0,len=l.length;i<len;i++){
                    l[i].className = '';
                }
                if(t.tagName.toLowerCase() == 'a'){
                    t.parentNode.className = 'w';
                    input.value = t.innerHTML;
                }

            })
            //若输入框失去焦点,则隐藏补全框
            addEvent(input,'blur',function(){
                hideBox($('box'))
            })
        }
        (function(){
            var t = $('t'),div,boxLocation,ul;
            div = createBox();
            boxLocation = {
                left: t.getBoundingClientRect().left + parseInt(document.documentElement.scrollLeft || document.body.scrollLeft || 0)
                    - parseInt(document.documentElement.clientLeft || document.body.clientLeft || 0),
                top: t.getBoundingClientRect().top + parseInt(document.documentElement.scrollTop || document.body.scrollTop || 0)
                        - parseInt(document.documentElement.clientTop || document.body.clientTop || 0) +
                        parseInt(t.offsetHeight)
            };
            document.body.appendChild(div);
            ul = $('autobox-ul');
            bindEvent(t,function(){
                var value = t.value;
                ajaxInit({
                    type: 'get',
                    timeout: 3000,
                    url: './items.txt',
                    responseType: 'html',
                    onSuccess: function(data){
                        var d = document.createElement('div');
                        d.innerHTML = data;
                        d = d.getElementsByTagName('ul')[0];
                        ul.innerHTML = d.innerHTML;
                        showBox(div,boxLocation)
                    }
                })
            })
        })()

  经测试,IE8及其之前版本有bug,主要是因为onpropertychange的原因导致无法直接给文本框赋值。待修改。

posted @ 2014-12-10 18:25  royalrover  阅读(918)  评论(1编辑  收藏  举报