实现一个类似于百度搜索推荐列表功能的通用的jquery插件,根据用户输入变化动态的去服务器上拉取推荐结果并插入到搜索推荐框中;要实现的功能如下:
1.监听输入框文本变化事件
2.获取输入的关键字通过ajax去服务器拉去推荐结果然后插入到搜索框下面
3.用户移动鼠标和按上下方向键可以动态选中推荐项,按回车或点击可以执行相应的动作
分析:
原则:我们是要做一个通用的插件,write once, run anywhere, 所以数据和ui还有操作的接口必须设计好,在保持通用的前提下,可以更多的定制。
基本布局结构:
<
div id="search"
>
<
input
/>
<
ul
> 推荐列表</
ul
>
<
div
>
先来预览一下爱国学网都使用该插件的效果图,让我们先睹为快:
抽象:
哪些事情插件做?当然是和具体业务无关通用的部分,这里主要包括编辑框输入变化事件监听和从服务器拉去推荐数据,而用户关心的是将推荐列表数据如何去展示(ui)以及用户点击或者按回车之后操作。这是典型的mvc模式。
需要用户做的事:
1.用户提供一个处理后台拉去的数据的回掉(handler(data)),用户可以在这里解析数据插入自己的布局到ul中。
2.提供一个处理回车或者点击了推荐列表中的某一项的回掉(enter(item))
但还会有两个问题:
1.那键盘的上下键谁来处理?如果插件处理的话,插件必须得知道推荐列表的布局结构,但每个业务的布局都会不一样,甚至一个推荐列表中还会出现分类的推荐结果,框架不知道光标该在推荐列表中那些元素上移动。那如果用户来做的话,也不合理,因为无论什么业务,搜索推荐列表框都得响应上下键来实现动态选择某一项。
2.如百度一样,按上下键盘选中某一项时搜索框中的文字会变成选中的推荐项,但我们的推荐列表不项百度那样只有一个关键词那么简单,正如上图所见,一个推荐项左边是内容,右边是该内容的出处/作者等信息,移动光标后搜索输入框中的内容显然是不能直接取选中项的text的,这样会包含一些其它的内容,如作者/出处。如果框架来处理上下键事件,它又怎么知道该取推荐项的那部分文本呢。
3.用户点击搜索框后但未输入任何内容时能够提供默认推荐项。如下图:
对于问题1,一种方法是可以约定按上下键时焦点只会在ul的直接子节点移动,这样的话可以满足大多数业务,但实际业务中会出现对于有些直接子节点,用户不会希望它能获得焦点,举个栗子,如果推荐列表中有多个分类 ,而每个分类之间用一条分割线隔开,而这个分割线本身是不被期望能获取焦点的,但它可能就正好是ul的直接子节点。那怎么办呢,回想一下,你是在handler中将数据插入到推荐列表中的,那再插入时你可以指定哪些项是可被选择的,那些想是不可以被选择的,那具体怎么办呢?我们可以在插入时给期望能被选中的项给个标记,用什么做标记呢?对,就是类名!这样插件就可以通过类选择器知道哪些是可以被选中的,哪些是不能被选中的了,看到这里,对于问题2,聪明的你是不是也已经想到了解决方案呢,对,依旧是用类名做标记! 这样插件也就知道选中某项后该在输入框中显示那些元素的文本了。
对于问题3,最好是能支持在页面中静态的初始化比如:
<
div id="search"
>
<
input
/>
<
ul
>
<li>first recomend</li>
<li>second recomend</li>
</
ul
>
<
div
>
$.AutoComplete(selector, page, hoverStyle, handler,options) ;
selector: 搜索布局容器选择器
page:请求数据的页面地址
hoverStyle:按上下键选中项的样式(类名)
handler(container,data)后台拉去数据后的回掉,container是推荐框容器
optinons:可选参数
{
selectable:">", //可被选中的选择器,默认是直接子节点
textsel: ">" ,//编辑框中要显示的文字的选择器
enter:function(t){} //按回车(或点击时的回调用)
}
一个使用的栗子:
//单击或回车事件处理,回车时如果用户没有用上下键选中某一项的话 e为 null
function
onenter(e){
if
(e) open(e.attr(
"data-url"
),
"_blank"
);
//若
else
{
var
wd=$.trim($(
"input"
).val());
if
(!wd)
return
;
if
(location.href.lastIndexOf(
"search.php"
)!=-1)
open(
"search.php?wd="
+wd,
"_self"
)
else
open(
"search.php?wd="
+wd,
"_blank"
);
}
}
$.AutoComplete(
"#search"
,
"./search.php?wd="
,
"hov"
,
function
(p,d){
//解析数据,作为示例直接插入一条
s=
'<li data-url="author.php?id=474"><div>1</div><div class="s-left">杜甫</div><div class="s-right">人物/唐代</div></li>'
;
p.html(s)
},{
textsel:
".s-left"
,
enter:onenter
});
下面是源代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
|
/**
* Created by duwen on 2015/9/2.
*/
+
function
() {
$.AutoComplete =
function
(selector, page, hs, handler, options) {
var
du = $.extend({
selectable:
'>'
, textsel:
'>'
, enter:
function
() {
}
}, options)
var
search = $(selector);
var
$input = $(
"input"
, search);
var
$list = $(
"ul"
, search);
var
$listItems = $(
"ul "
+ du.selectable, search);
var
onlist =
false
;
//判断
var
len = -1;
var
recommend = $list.html();
//初始化
var
upordown =
false
;
$input.focus(
function
() {
if
($(
this
).val().length == 0) {
$list.html(recommend);
updateUl();
}
$list.fadeIn(400);
})
function
updateUl() {
$listItems = $(
"ul "
+ du.selectable, search);
$listItems.hover(
function
() {
$(
this
).addClass(hs);
},
function
() {
$(
this
).removeClass(hs);
});
}
$list.on(
"click"
,
">"
,
function
() {
var
valr = $(
this
).text();
$list.hide()
du.enter($(
this
));
})
search.hover(
function
() {
onlist =
true
;
},
function
() {
onlist =
false
;
})
$input.blur(
function
() {
if
(!onlist) $list.hide()
});
$input.on(
"propertychange input"
,
function
(e) {
if
(upordown)
return
;
var
wd = $.trim($(
this
).val());
if
(!wd) {
$list.html(recommend);
return
updateUl();
}
len = -1;
$.get(page + wd,
function
(d) {
handler($list, d);
updateUl();
})
})
//上下键处理
function
up(isUp) {
upordown =
true
;
$listItems.eq(len % $listItems.length).removeClass(hs);
if
(len == -1 && isUp)
len = $listItems.length - 1;
else
{
len = isUp ? --len : ++len;
}
$input.val($listItems.eq(len % $listItems.length).addClass(hs).find(du.textsel).text().replace(/《|》/g,
''
));
//for chrome
event.preventDefault && event.preventDefault();
}
$(document).keydown(
function
(e) {
if
($list.css(
"display"
) !=
'block'
) {
len = -1;
return
;
}
var
length = $listItems.length;
upordown =
false
;
switch
(e.keyCode) {
case
40:
up(
false
);
break
;
case
38:
up(
true
);
break
;
case
13:
{
//enter
var
t = $(
"."
+ hs, $list);
if
(t.length == 0)
du.enter()
else
{
du.enter(t)
}
}
break
;
}
});
}
}()
|