您的位置:首页 > 其它

自动补全多标签输入, 适合新闻标签/商品标签

2015-05-12 20:22 148 查看
2015年5月12日 20:16:48 星期二

效果:



原理:

1. 建立"单字"索引(倒排索引): 将汉字拆分成单个字, 作为redis hash 的一个键, 将所有包含该字的id作为hash的值

2. 每次输入一个字, 就去redis里将该字的所有id取出来, 输入第二个字的时候, 取出第二个字的所有id与第一个字的id求交集

  求得同时包含这两个字的所有id, 再进一步获取id对应的信息并返回

另外:

1. 文中还建立了两个全词索引(redis hash), 一个是 {"完整汉字词语" : "id", .....} 另一个是 {"id" : "完整汉字词语" ,.......} 方便后续程序使用, 与这个自动补全关系不大

2. 小细节, 每次点选词语的时候, js自动补上"," 而且ajax请求数据的时候, 只把最后一个","后边的用户输入词语发送给服务端

话不多说, 上代码:

js+css

<script type="text/javascript">
var ac_domain = 'http://'+document.domain+'/';
initAutoComplete();
function initAutoComplete()
{
var ac_input = document.getElementById('auto_complete_input');
if (!ac_input) {return false;}
var ac_rebuildindex = document.getElementById('rebuild_autocomplete_index');

if (document.attachEvent) {
ac_input.attachEvent('oninput', input_autocomplete); //输入时进行自动补全
// ac_input.attachEvent('onblur', hide_autocomplete); //鼠标点击其它地方隐藏div, 屏蔽 因为onblur 和 onclick 冲突
ac_input.attachEvent('onfocus', input_autocomplete); //输入框重新获取焦点时显示自动补全结果
ac_input.attachEvent('onkeydown', input_autocomplete_keydown); //处理按键信息
ac_rebuildindex.attachEvent('onclick', rebuild_autocomplete_index);
} else {
ac_input.addEventListener('input', input_autocomplete); //输入时进行自动补全
// ac_input.addEventListener('blur', hide_autocomplete); //鼠标点击其它地方隐藏div, 屏蔽 因为onblur 和 onclick 冲突
ac_input.addEventListener('focus', input_autocomplete); //输入框重新获取焦点时显示自动补全结果
ac_input.addEventListener('keydown', input_autocomplete_keydown); //处理按键信息
ac_rebuildindex.addEventListener('click', rebuild_autocomplete_index);
}

$(document).bind("click",function(e){
var target  = $(e.target);//表示当前对象,切记,如果没有e这个参数,即表示整个BODY对象
if(target.closest(".form-input-autocomplete,#auto_complete_input").length == 0){
hide_autocomplete();
}
})
}

function input_autocomplete()
{
var ac_input = document.getElementById('auto_complete_input');
var userinput = ac_input.value;
if (!userinput) {return false;}
var arr_new_word = userinput.split(/,\s*/);
var new_word = arr_new_word.pop();

var url = ac_domain+'getAutoComplete?word='+new_word;
$.get(url,
function(data) {
if (!data) {return false;};
var objGame = eval('('+data+')');
html = '<ul>';
for (var i in objGame) {
html += '<li class="auto_complete_li" data-id="'+i+'" data-name="'+objGame[i]+'">'+objGame[i]+'</li>';
}
html += '</ul>';
var ac_div = $("#auto_complete_div");
ac_div.show();
ac_div.html(html);
click_autocomplete_li(); //注册点击事件
}
);
}

function hide_autocomplete()
{
var ac_div = document.getElementById('auto_complete_div');
ac_div.style.display = 'none';
}

function click_autocomplete_li()
{
var arr_ac_li = getElementsByClassName('auto_complete_li', 'ul');
for (var i = 0; i < arr_ac_li.length; i++) {
arr_ac_li[i].addEventListener('click', function () {
//获取列表li上的数据, 并组装
var data_id = this.getAttribute('data-id');
var data_name = this.getAttribute('data-name');
var data_info = data_id+'|'+data_name;

///////向输入框写数据
var already_input = document.getElementById('auto_complete_input');

//去掉用户的输入, 换做用户点击的li里的name
var arr_already_input = already_input.value.split(/,\s*/);
arr_already_input.pop();
arr_already_input.push(data_name);

//去重
var hash_already_input = {};
for (var i in arr_already_input) {
hash_already_input[arr_already_input[i]] = arr_already_input[i];
}
arr_already_input = [];
for (key in hash_already_input) {
arr_already_input.push(key);
}

//回写入表单
var str_already_input = arr_already_input.join(", ");
document.getElementById('auto_complete_input').value = str_already_input+", ";

//如果只有一条匹配, 点击后就消失, 多条的话点击后不消失, 可以多次点选
if (arr_ac_li.length == 1) {
hide_autocomplete();
};
})
};
}

function rebuild_autocomplete_index()
{
var nodeParent = this.parentNode;
var url = ac_domain+'buildTeamAutoComplete';
$.get(url,
function(data) {
nodeParent.innerHTML = '已经重建索引,再点击输入框试试';
}
);
}

function input_autocomplete_keydown(e)
{
switch(e.keyCode){
case '8':
//按下删除键
input_autocomplete();
break;
};
}

function getElementsByClassName(className, tagName)
{
var t = typeof(document.getElementsByClassName);

if (t == 'function') {
return document.getElementsByClassName(className);
}

var tags, matchedTags;
if (tagName) {
tags = document.getElementsByTagName(tagName);
} else {
tags = document.getElementsByTagName('*');
}

matchedTags = [];
for (var i = 0; i < tags.length; i++) {
if (tags[i].className.indexOf(className) != -1) {
matchedTags.push(tags[i]);
}
}

return matchedTags;
}
</script>

<style type="text/css">
/*自动补全*/
.form-input-autocomplete {padding: 0px; width: 200px; border: 1px solid #aaa; display: none; z-index: 99; position: absolute; background-color: #fff; filter: alpha(opacity=100); opacity: 1;}
.form-input-autocomplete ul {margin: 0px;padding: 0px; text-align: left; list-style: none; font:15px;}
.form-input-autocomplete ul li{margin: 3px;}
.form-input-autocomplete ul li:hover{background-color: #eee; cursor:hand;}
</style>


html (只用在原有的表单上加一个id, 并在下方添加一个隐藏的div, id/class如下)

<input type="text" id="auto_complete_input" autocomplete="off" placeholder="新闻标签" value=""> <a href="javascript:;" id="rebuild_autocomplete_index">没找到? 点这里试试</a>
<div class="form-input-autocomplete" id="auto_complete_div"></div>


php (php+redis:hash)

//创建索引
public function dobuildTeamAutoComplete()
{
$oneWordIndex = 'autocomplete_zi'; //单字索引
$allWordIndexByName = 'autocomplete_ci_name'; //全词索引, 键为全名(火箭:team_1_2)
$allWordIndexById = 'autocomplete_ci_id'; //全词索引, 键名为id(team_1_2:火箭)

$type = 'team_';

//删除之前所有的旧值
$this->redis->del($oneWordIndex);

//重新构建
$team = $this->redis->hgetall('LeagueTeamsNames');

$oneWordInfo = array();
$allWordInfoName = array();
foreach ($team as $lid => $jsonTeam) {
if ($lid != 8 && $lid != 12) {
$arrTeam = json_decode($jsonTeam, true);
foreach ($arrTeam as $name => $tid) {
$value = $type.$tid.'_'.$lid;
$allWordInfo[$name] = $value;
$length = mb_strlen($name, 'UTF-8');
for ($i=0; $i < $length; $i++) {
$strOne = mb_substr($name, $i, 1, 'UTF-8');
$oneWordInfo[$strOne][$value] = $value; //懒得去重了
}
}
}
}
$oneWordInfoRedis = array();
foreach ($oneWordInfo as $word => $ids) {
$a = array_values($ids);
$oneWordInfoRedis[$word] = json_encode($a);
}

//索引写入redis
$this->redis->hmset($oneWordIndex, $oneWordInfoRedis);
$this->redis->hmset($allWordIndexByName, $allWordInfo);
$this->redis->hmset($allWordIndexById, array_flip($allWordInfo));

exit('over');
}

//自动补全请求处理函数
public function dogetAutoComplete()
{
$oneWordIndex = 'autocomplete_zi'; //单字索引
$allWordIndexById = 'autocomplete_ci_id'; //全词索引, 键名为id(team_1_2:火箭)

$word = common::request('word', 'G');

if (empty($word)) {
exit('0');
}
$intWordLength = mb_strlen($word, 'UTF-8');

$arrId = array();
if (1 == $intWordLength) {
$jsonId = $this->redisSlave->hget($oneWordIndex, $word);
$arrId = json_decode($jsonId, true);
} else {
for ($i=0; $i < $intWordLength; $i++) {
$strOne = mb_substr($word, $i, 1, 'UTF-8');
$jsonIdTmp = $this->redisSlave->hget($oneWordIndex, $strOne);
$arrIdTmp = json_decode($jsonIdTmp, true);

$arrIdTmp = $arrIdTmp ? $arrIdTmp : array();
$arrId = empty($arrId) ? $arrIdTmp : $arrId;
$b = array_intersect($arrId, $arrIdTmp);
$arrId = empty($b) ? $arrId : $b; // 新输入的词如果跟之前的没有交集, 就维持原来的交集不变
}
}
$arrId = empty($arrId) ? array() : $arrId;

//获取球队信息
$arrInfo = array();
foreach ($arrId as $v) {
$arrInfo[$v] = $this->redisSlave->hget($allWordIndexById, $v);
}

$jsonInfo = json_encode($arrInfo);
exit($jsonInfo);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: