站长论坛

标题: Google Suggest自动下拉完成功能 [打印本页]

作者: superadmin    时间: 2009-5-9 12:53
标题: Google Suggest自动下拉完成功能
JS代码文件共9K多,如果打乱格式应该可以压到6K左右吧。
本程序共三个计时器:输入框onblur后列表隐藏计时、用户按键计时、数据发送后等待结果计时。
— 因为没有任何参考,也没做任何调查,因此所设定的默认时间都是凭个人感觉,如:发送请求后延迟时间隐藏列表项,是在个人感觉保证列表没有闪动的前提下服务器能够返回数据的差不多时间 :)。

演示地址:http://www.hansir.cn/tem/sample/suggest_test.html

调用:
后台数据以 [['关键字符','估计数量'], ['关键字符','估计数量'], ...] 格式输出。
页面onload后调用(因为有body.appendChild方法)
var mySuggest = new hansir.TextSuggest();
mySuggest.add_suggest(inp, url, method, defer, defer2);
除了前两项必填,后三项都是可选的
   inp :输入框ID名。
   url :ajax请求的后台服务器页面地址。
method :发送方试 get或post,默认post。
defer :按键计时,即用户输入字符多久后请求服务器,默认不计时,用户输入字符后立即发送。
defer2 :服务器返回结果计时,即服务器发送请求后多长时间没有返回数据,列表自动隐藏,默认200ms。
测试说明:
1、数据库存放的是临时数据。
2、可以输入“中华人民共和国”,“中”,”蓝色“ 测试。
3、可以输入一些其他的自定义数据,提交存到表里,然后就可以用刚刚提交的数据测试。
4、我的服务器网速慢,可能有个别卡的现象。
5、迟延:用户输入字符多久后请求服务器,照顾输入快的用户 :)
   — 如果服务器速度够快可以考虑迟延,如果慢就无所谓了,反正是一个请求完成才会进行下一个 :)

已知缺陷:
1、下拉提示框位置问题:因为是以BODY为参考,所以位置会随body大小改变(下拉列表显示时拖动窗口大小可看效果)。
   — 解决方法:真正用时可以根据input的父元素定位(这样还可以省些资源,即不毕每次显示下拉框都计算位置)。
2、用五笔输入法在FF下输入完成后上下方向键不好使要切换一下输入法才可以,拼音则正常。
   — 解决方法:google也有同样问题,所以。。。 :)。

suggest.php

<?php

header('Content-Type:text/html;charset=utf-8');

require('../../admin/include/db_conf.php');

if($_POST['add']){

    $keyword = trim($_POST['keyword']);

    if(empty($keyword)){

        header("LOCATION: suggest.html");

        exit;

    }

    $db = db_connect();

    $db->query("set names 'gb2312'");

    $sql = "select*from suggest where keyword = '$keyword'";

    if($db->query($sql)->num_rows>0){

        header("LOCATION: suggest.html");

        exit;

    }

    $num = rand(15, 2008);

    $sql = "insert into suggest values(NULL, '$keyword', $num)";

    $db->query($sql);

    $db = NULL;

    header("LOCATION: suggest.html");

    exit;

}

$keyword = $_POST['keyword'];

if(empty($keyword)){

    echo 'null';

    exit;

}

$db = db_connect();

$db->query("set names 'utf8'");

$sql = "select *from suggest where keyword REGEXP '^$keyword'  order by id desc limit 0, 15";

$result = $db->query($sql);

if($result->num_rows<1){

    echo 'null';

    exit;

}

$arr = array();

while($rows=$result->fetch_object()){

    $keyword = $rows->keyword;

    $arr[]="['$keyword', '$rows->num ".iconv('gb2312','utf-8','结果')."']";

}

$arr = '['.implode(',',$arr).']';

echo $arr;

?>


复制代码


suggest表: CREATE TABLE `suggest` (

  `id` int(10) unsigned NOT NULL auto_increment,

  `keyword` varchar(50) NOT NULL,

  `num` int(10) unsigned NOT NULL,

  PRIMARY KEY  (`id`)

) ENGINE=MyISAM  DEFAULT CHARSET=gb2312 AUTO_INCREMENT=59 ;


复制代码
JS代码: /*

    ****************************

    *** http://www.hansir.cn ***

    ****************************

*/

String.prototype.ltrim = function(){

    return this.replace(/^s*(.+?)$/,'$1');

}

//这里引用prototype的五个方法

function $(){return document.getElementById(arguments[0]);}

Object.extend = function(destination, source){

    for(property in source) destination[property] = source[property];

    return destination;

}

function $A(iterable) {

    var results = [];

    for (var i = 0; i < iterable.length; i++)results.push(iterable);

    return results;

}

Function.prototype.bindAsEventListener = function(object) {

    var __method = this;

    return function(event) {

        return __method.call(object, event || window.event);

    }

}

Function.prototype.bind = function() {

    var __method = this, args = $A(arguments), object = args.shift();

    return function() {

        return __method.apply(object, args.concat($A(arguments)));

    }

}

var hansir = {

    url: 'http://www.hansir.cn'

}

hansir.AjAx = function(){this.initialize.apply(this, arguments);}

hansir.AjAx.prototype = {

    initialize: function(complete, method, url){

        this.complete = complete;

        this.method      = method || 'post';

        this.url      = url;

        if (this.method == 'get') this.url += (this.url.match(/?/) ? '&' : '?');

    },

    xmlHttp: function(){

        var xmlHttp;

        if(window.XMLHttpRequest) xmlHttp = new XMLHttpRequest();

        else if(window.ActiveXObject)

            try{

                xmlHttp = new ActiveXObject('Msxml2.XMLHTTP');

            }catch(errr){

                xmlHttp = new ActiveXObject('Microsoft.XMLHTTP');

            }

        return xmlHttp;

  },

  request: function(parameters){

    var xmlHttp = this.xmlHttp();

    var send_val = null;

    this.method=='get' ? this.url += parameters : send_val = parameters;

    xmlHttp.open(this.method, this.url, true);

    xmlHttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded; charset=utf-8');

    xmlHttp.onreadystatechange = this.ready_handler.bind(this, xmlHttp);

    xmlHttp.send(send_val);

  },

  ready_handler: function(xmlHttp){

    if(xmlHttp.readyState == 4){

        if(this.success(xmlHttp)){

            this.complete.load_data(xmlHttp);

        }

    }

  },

  success: function(xmlHttp){return xmlHttp.status == 0 || xmlHttp.status >= 200 && xmlHttp.status < 300}

}

hansir.TextSuggest = function(){this.initialize.apply(this, arguments);}

hansir.TextSuggest.prototype = {

    initialize: function(){},

    add_suggest: function(inp, url, method, defer, defer2){

        var inp = $(inp);

        inp.defer = defer || null;

        inp.defer2 = defer2 || 200;

        var sw = inp.offsetWidth, sh = inp.offsetHeight;

        inp.suggest_list = this.create_list(sw, sh);

        inp.suggest_list.par = inp;

        inp.xmlHttp = new hansir.AjAx(inp.suggest_list, method, url);

        Object.extend(inp, {

                requesting : false,

               last_result : true,

            previous_value : null,

                last_value : null,

                        kt : null,

                        rt : null,

            load_event: function(){

                if(this.addEventListener){

                    this.addEventListener('input', this.keyup_handler.bindAsEventListener(this),false);

                }else if(this.attachEvent){

                    this.attachEvent('onkeyup', this.keyup_handler.bindAsEventListener(this));

                }

            },

            keyup_handler:function(e){

                var intKey;

                window.event ? intKey = event.keyCode : intKey = e.which;

                if(intKey == 38 || intKey == 40 || intKey == 13 || intKey == 37) return;

                if(this.requesting) return;

                var val = this.value.ltrim();

                this.last_value = val;

                if(val == this.previous_value) return;

                if(val==''){

                    this.previous_value = '';

                    this.last_value='';

                    this.suggest_list.hidden();

                    this.suggest_list.clear_data();

                    return;

                }

                if(new RegExp('^'+this.last_result,'i').test(val)) return;

                this.last_result = true;

                this.previous_value = val;

                if(this.kt) clearTimeout(this.kt);

                this.defer?this.kt = setTimeout(this.send_request.bind(this), this.defer):this.send_request();

            },

            onblur: function(){

                setTimeout(this.suggest_list.hidden.bind(this.suggest_list), 100);

            },

            onkeydown: function(e){ // 上下、回车键事件

                if(!this.suggest_list.rows.length) return;

                var intKey;

                window.event ? intKey = event.keyCode : intKey = e.which;

                switch(intKey){

                    case 38:

                        if(this.suggest_list.style.visibility=='hidden'){

                            this.suggest_list.visible();

                            return;

                        }

                        var val = this.suggest_list.select_index(1);

                        val?this.value=val : this.value = this.last_value;

                        break;

                    case  40:

                        if(this.suggest_list.style.visibility=='hidden'){

                            this.suggest_list.visible();

                            return;

                        }

                        var val=this.suggest_list.select_index(0);

                        val?this.value=val : this.value = this.last_value;

                        break;

                    case 13:

                        if(this.suggest_list.cur_tr!=-1){

                            this.suggest_list.hidden();

                            break;

                        }

                    case 39:

                        this.suggest_list.hidden();

                        this.keyup_handler('o');

                }

            },

            send_request: function(){ // 请求数据

                this.requesting = true;

                var val = this.value;

                var parameters = 'keyword=' + val.ltrim();

                this.xmlHttp.request(parameters);

                this.start_hidden_time();

            },

            start_hidden_time: function(){

                if(this.rt) clearTimeout(this.rt);

                this.rt = setTimeout(this.list_hidden.bind(this), this.defer2);

            },

            list_hidden: function(){

                if(this.requesting) this.suggest_list.hidden();

            }

        });

        inp.load_event();

    },

    create_list: function(w, h){ //创建列表

        var table  = document.createElement('table');

        table.cellSpacing = 0;

        document.body.appendChild(table);

        table.className = 'tab_suggest';

        table.style.width = w + 'px';

        table.parh = h-1;

        

        Object.extend(table,{

            cur_tr: -1,

            set_pos: function(){ // 下垃框位置

                var x=0, y=0, inp = this.par;

                while(inp != null){x += inp.offsetLeft;y += inp.offsetTop;inp = inp.offsetParent;}

                inp = null;

                table.style.left = x + 'px';

                table.style.top = y+this.parh+ 'px';

            },

            add: function(str, num){

                var n=0;

                this.rows.length ? n=this.rows.length : n = 0;

                var tr = this.insertRow(n);

                var th = document.createElement('th');

                var td = document.createElement('td');

                th.innerHTML = str, td.innerHTML = num;

                tr.appendChild(th), tr.appendChild(td);

                tr.num = this.rows.length-1;

                tr.par = this;

                tr.onmouseover = function(){

                    var par = this.par;

                    if(par.cur_tr!=-1 && par.cur_tr!=this.num){

                        par.rows[par.cur_tr].className='';

                        this.className = 'cur';

                        par.cur_tr = this.num;

                    }else{

                        this.className = 'cur';

                        par.cur_tr = this.num;

                    }

                }

                tr.onclick = function(){

                    var par = this.par.par;

                    par.value = this.cells[0].innerHTML;

                }

                tr = null, th = null, td = null;

            },

            load_data: function(xmlHttp){ // 加载列表

                var inp = this.par;

                if(inp.previous_value != inp.value){

                    inp.requesting = false;

                    this.clear_data();

                    inp.keyup_handler('o');

                    return;

                }

                var arr = xmlHttp.responseText;

                if(arr.ltrim() == 'null'){

                    inp.last_result = inp.value;

                    inp.requesting = false;

                    inp.suggest_list.hidden();

                    this.clear_data();

                    return;

                }

                var cur_data = eval(arr);

                this.clear_data();

                for(var i=0; i<cur_data.length; i++)this.add(cur_data[0],cur_data[1]);

                this.cur_tr = -1;

                this.visible();

                inp.requesting = false;

            },

            clear_data: function(){while(this.rows.length)this.deleteRow(this.rows[0])}, // 清空列表

            select_index: function(n){ // 移动选项

                if(n){

                    if(this.cur_tr==0){

                        this.rows[0].className = '';

                        this.cur_tr = -1;

                        return false;

                    }else{

                        this.cur_tr==-1?this.cur_tr=this.rows.length : this.rows[this.cur_tr].className = '';

                        this.cur_tr = this.cur_tr-1;

                        this.rows[this.cur_tr].className = 'cur';

                        return this.rows[this.cur_tr].cells[0].innerHTML;

                    }

                }else{

                    if(this.cur_tr == (this.rows.length-1)){

                        this.rows[this.cur_tr].className= '';

                        this.cur_tr = -1;

                        return false;

                    }else{

                        if(this.cur_tr!=-1)this.rows[this.cur_tr].className = '';

                        this.cur_tr = this.cur_tr+1;

                        this.rows[this.cur_tr].className = 'cur';

                        return this.rows[this.cur_tr].cells[0].innerHTML;

                    }

                }

            },

            hidden: function(){this.style.visibility = 'hidden';}, // 隐

            visible: function(){this.set_pos(); this.style.visibility = 'visible';} // 显

        });

        return table;

    }

}


复制代码HTML代码: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head>

<meta http-equiv="Content-Type" content="text/html; charset=gb2312" />

<style>

    *{font: 12px '宋体'}

    .tab_suggest{border:1px solid #333; background:#fff; position:absolute; z-index:101; visibility: hidden;}

    .tab_suggest th, .tab_suggest td{font:12px '宋体'; font-weight:normal; height:17px; text-align:left; line-height:17px; padding:2px 3px; white-space:nowrap; cursor: default;}

    .tab_suggest td{color:#008000; text-align:right;}

    .tab_suggest tr.cur{background:#36c; color:#fff}

    .tab_suggest tr.cur td{color:#fff}

</style>

<title>无标题文档</title>

<script type="text/javascript" src="js/suggest.js"></script>

<script type="text/javascript">

window.onload=function(){

    var mySuggest = new hansir.TextSuggest();

    mySuggest.add_suggest('textSuggest', 'suggest.php', 'post');

    mySuggest.add_suggest('textSuggest2', 'suggest.php', 'post', 100);

    $('textSuggest').focus();

}

</script>

</head>

<body>

<br /><br />

<form action="suggest.php" method="post"><input type="hidden" name="add" value="add" />

没有迟延:<input type="text" id="textSuggest" name="keyword" style="width:300px;" autocomplete="off" /> <input type="submit" value="提 交" />

</form>

<br /><br /><br />

<form action="suggest.php" method="post"><input type="hidden" name="add" value="add" />

迟延100ms:<input type="text" id="textSuggest2" name="keyword" style="width:300px;" autocomplete="off" /> <input type="submit" value="提 交" />

</form>

</body>

</html>

复制代码


代码发布完毕
再看一下演示:http://www.hansir.cn/tem/sample/suggest_test.html

来源:http://bbs.phpchina.com/viewthread.php?tid=85197
作者: superadmin    时间: 2009-5-9 13:01
我发现一个问题是用get的时候。不太正常。那this.url累加了.我稍改了一下

这样也可以
...
    var url = this.url //用局部变量  
    this.method=='get' ? url += parameters : send_val = parameters;   
    xmlHttp.open(this.method, url, true);   
...
作者: superadmin    时间: 2009-5-9 13:01
谢谢。站长 。不过。我发现一个问题是用get的时候。不太正常。那this.url累加了.

我稍改了一下

request: function(parameters){
    var xmlHttp = this.xmlHttp();
    var send_val = null;
    var oldurl=this.url;   
    this.method=='get' ? this.url += parameters : send_val = parameters;   
    xmlHttp.open(this.method, this.url, true);   
    xmlHttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded; charset=utf-8');
    xmlHttp.onreadystatechange = this.ready_handler.bind(this, xmlHttp);
    xmlHttp.send(send_val);
    this.url=oldurl;
  },




欢迎光临 站长论坛 (http://www.tzlink.com/bbs/) Powered by Discuz! X3.2