
一、引出Ajax的自动完成
现在要实现一个员工信息查询的功能,即根据输入的名字检索员工的详细信息。这是一个简单的数据表查询,在 ASP.NET 中实现这样的功能是比较简单的.

从上面可以看出,这种员工信息查询功能还存在一些不足,比如用户可能记不全员工的名字,只记得前面几个字母是什么,这样用户只能根据记忆猜测,一遍遍地尝试。如果在用户输入的同时,输入框下方可以给出相应的提示,辅助用户输入,那么用户进行检索的速度和成功率就会大大提高.这就是 基于 Ajax 的自动完成功能.

二、自动完成功能的实现
实现这样的功能需要按以下的步骤进行。
· 服务器端提供 GetSearchItems 方法给客户端,用来返回满足条件的员工列表。
· 客户端的输入框需要增加 onkeydown 响应函数,以便即时获取满足条件的员工列表。
· 通过客户端的 JavaScript 动态列出待选结果的列表,同时还要提供键盘和鼠标的响应。
三、服务器端实现
本文采用AjaxPro.NET 作为Ajax 开发框架,首先为使用AjaxPro.NET 做一些准备工作。 添加对AjaxPro.dll 的引用,修改Web.config 配置文件,在system.web节点下加入如下配置:
1
<
httpHandlers
>
2
<!--
Register the ajax handler
-->
3
<
add
verb
="POST,GET"
path
="ajaxpro/*.ashx"
type
="AjaxPro.AjaxHandlerFactory, AjaxPro"
/>
4
</
httpHandlers
>

2

3

4

在页面后台代码( Default.aspx.cs )的 Page_Load 方法中增加下面的代码:
1
protected
void
Page_Load(
object
sender, EventArgs e)
2
{
3
AjaxPro.Utility.RegisterTypeForAjax(typeof(_Default));
4
}

2



3

4

下面定义提供给客户端调用的方法GetSearchItems(),参数query为模糊查询的关键字值:
1
[AjaxPro.AjaxMethod()]
2
public
ArrayList GetSearchItems(
string
query)
3
{
4
ArrayList items = new ArrayList();
5
StringBuilder queryString = new StringBuilder();
6
queryString.Append("select employeeid,lastname,firstname,title,titleofcourtesy from dbo.Employees");
7
queryString.Append(" where firstname like '%" + query + "%'");
8
9
DataSet ds = DataBase.Instance.ReturnDataSet(queryString.ToString());
10
for (int i = 0; i < ds.Tables[0].Rows.Count; i++)
11
{
12
items.Add(ds.Tables[0].Rows[i][2].ToString());
13
}
14
return items;
15
}
GetSearchItems
方法返回一个
ArrayList
对象,它将包含所有以用户输入字符串的员工名字。
2

3



4

5

6

7

8

9

10

11



12

13

14

15

四、客户端实现
相对于服务器端的方法而言,客户端的处理要复杂得多。首先来分析如何根据服务器端返回的 ArrayList 对象展示结果。 这里用到了 Web 编程中“层”( div )的概念,通过 JavaScript 和 DOM 创建一个新的层 div ,将 ArrayList 中的每一个条目都作为其子节点加入到 div 中,而每一个条目也被看作是一个 div ,其中具体的文本内容则是一个 span 对象。
除了显示待选的结果之外,下拉区域还要对键盘、鼠标事件做出响应;为了实时地显示待选结果,还需要定时更新待选结果的列表。这些功能都封装在 lookup.js中。下面是lookeu.js的定义:
1
//
下拉区背景色
2
var
DIV_BG_COLOR
=
"
#EEE
"
;
3
//
高亮显示条目颜色
4
var
DIV_HIGHLIGHT_COLOR
=
"
#C30
"
;
5
//
字体
6
var
DIV_FONT
=
"
Arial
"
;
7
//
下拉区内补丁大小
8
var
DIV_PADDING
=
"
2px
"
;
9
//
下拉区边框样式
10
var
DIV_BORDER
=
"
1px solid #CCC
"
;
11
12
13
//
文本输入框
14
var
queryField;
15
//
下拉区id
16
var
divName;
17
//
IFrame名称
18
var
ifName;
19
//
记录上次选择的值
20
var
lastVal
=
""
;
21
//
当前选择的值
22
var
val
=
""
;
23
//
显示结果的下拉区
24
var
globalDiv;
25
//
下拉区是否设置格式的标记
26
var
divFormatted
=
false
;
27
28
/**/
/**
29
InitQueryCode函数必须在<body onload>事件的响应函数中调用,其中:
30
queryFieldName为文本框控件的id,
31
hiddenDivName为显示下拉区div的id
32
*/
33
function
InitQueryCode (queryFieldName, hiddenDivName)
34
{
35
// 指定文本输入框的onblur和onkeydown响应函数
36
queryField = document.getElementById(queryFieldName);
37
queryField.onblur = hideDiv;
38
queryField.onkeydown = keypressHandler;
39
40
// 设置queryField的autocomplete属性为"off"
41
queryField.autocomplete = "off";
42
43
// 如果没有指定hiddenDivName,取默认值"querydiv"
44
if (hiddenDivName)
45
{
46
divName = hiddenDivName;
47
}
48
else
49
{
50
divName = "querydiv";
51
}
52
53
// IFrame的name
54
ifName = "queryiframe";
55
56
// 100ms后调用mainLoop函数
57
setTimeout("mainLoop()", 100);
58
}
59
60
/**/
/**
61
获取下拉区的div,如果没有则创建之
62
*/
63
function
getDiv (divID)
64
{
65
if (!globalDiv)
66
{
67
// 如果div在页面中不存在,创建一个新的div
68
69
if (!document.getElementById(divID))
70
{
71
var newNode = document.createElement("div");
72
newNode.setAttribute("id", divID);
73
document.body.appendChild(newNode);
74
}
75
76
// globalDiv设置为div的引用
77
globalDiv = document.getElementById(divID);
78
79
// 计算div左上角的位置
80
var x = queryField.offsetLeft;
81
var y = queryField.offsetTop + queryField.offsetHeight;
82
var parent = queryField;
83
while (parent.offsetParent)
84
{
85
parent = parent.offsetParent;
86
x += parent.offsetLeft;
87
y += parent.offsetTop;
88
}
89
90
// 如果没有对div设置格式,则为其设置相应的显示样式
91
if (!divFormatted)
92
{
93
globalDiv.style.backgroundColor = DIV_BG_COLOR;
94
globalDiv.style.fontFamily = DIV_FONT;
95
globalDiv.style.padding = DIV_PADDING;
96
globalDiv.style.border = DIV_BORDER;
97
globalDiv.style.width = "100px";
98
globalDiv.style.fontSize = "90%";
99
100
globalDiv.style.position = "absolute";
101
globalDiv.style.left = x + "px";
102
globalDiv.style.top = y + "px";
103
globalDiv.style.visibility = "hidden";
104
globalDiv.style.zIndex = 10000;
105
106
divFormatted = true;
107
}
108
}
109
110
return globalDiv;
111
}
112
113
/**/
/**
114
根据返回的结果集显示下拉区
115
*/
116
function
showQueryDiv(resultArray)
117
{
118
// 获取div的引用
119
var div = getDiv(divName);
120
121
// 如果div中有内容,则删除之
122
while (div.childNodes.length > 0)
123
div.removeChild(div.childNodes[0]);
124
125
// 依次添加结果
126
for (var i = 0; i < resultArray.length; i++)
127
{
128
// 每一个结果也是一个div
129
var result = document.createElement("div");
130
// 设置结果div的显示样式
131
result.style.cursor = "pointer";
132
result.style.padding = "2px 0px 2px 0px";
133
// 设置为未选中
134
_unhighlightResult(result);
135
// 设置鼠标移进、移出等事件响应函数
136
result.onmousedown = selectResult;
137
result.onmouseover = highlightResult;
138
result.onmouseout = unhighlightResult;
139
140
// 结果的文本是一个span
141
var result1 = document.createElement("span");
142
// 设置文本span的显示样式
143
result1.className = "result1";
144
result1.style.textAlign = "left";
145
result1.style.fontWeight = "bold";
146
result1.innerHTML = resultArray[i];
147
148
// 将span添加为结果div的子节点
149
result.appendChild(result1);
150
151
// 将结果div添加为下拉区的子节点
152
div.appendChild(result);
153
}
154
155
// 如果结果集不为空,则显示,否则不显示
156
showDiv(resultArray.length > 0);
157
}
158
159
/**/
/**
160
用户点击某个结果时,将文本框的内容替换为结果的文本,
161
并隐藏下拉区
162
*/
163
function
selectResult()
164
{
165
_selectResult(this);
166
}
167
168
//
选择一个条目
169
function
_selectResult(item)
170
{
171
var spans = item.getElementsByTagName("span");
172
if (spans)
173
{
174
for (var i = 0; i < spans.length; i++)
175
{
176
if (spans[i].className == "result1")
177
{
178
queryField.value = spans[i].innerHTML;
179
lastVal = val = escape(queryField.value);
180
mainLoop();
181
queryField.focus();
182
showDiv(false);
183
return;
184
}
185
}
186
}
187
}
188
189
/**/
/**
190
当鼠标移到某个条目之上时,高亮显示该条目
191
*/
192
function
highlightResult()
193
{
194
_highlightResult(this);
195
}
196
197
function
_highlightResult(item)
198
{
199
item.style.backgroundColor = DIV_HIGHLIGHT_COLOR;
200
}
201
202
/**/
/**
203
当鼠标移出某个条目时,正常显示该条目
204
*/
205
function
unhighlightResult()
206
{
207
_unhighlightResult(this);
208
}
209
210
function
_unhighlightResult(item)
211
{
212
item.style.backgroundColor = DIV_BG_COLOR;
213
}
214
215
/**/
/**
216
显示/不显示下拉区
217
*/
218
function
showDiv (show)
219
{
220
var div = getDiv(divName);
221
if (show)
222
{
223
div.style.visibility = "visible";
224
}
225
else
226
{
227
div.style.visibility = "hidden";
228
}
229
//adjustiFrame();
230
}
231
232
/**/
/**
233
隐藏下拉区
234
*/
235
function
hideDiv ()
236
{
237
showDiv(false);
238
}
239
240
/**/
/**
241
调整IFrame的位置,这是为了解决div可能会显示在输入框后面的问题
242
*/
243
function
adjustiFrame()
244
{
245
// 如果没有IFrame,则创建之
246
if (!document.getElementById(ifName))
247
{
248
var newNode = document.createElement("iFrame");
249
newNode.setAttribute("id", ifName);
250
newNode.setAttribute("src", "javascript:false;");
251
newNode.setAttribute("scrolling", "no");
252
newNode.setAttribute("frameborder", "0");
253
document.body.appendChild(newNode);
254
}
255
256
iFrameDiv = document.getElementById(ifName);
257
var div = getDiv(divName);
258
259
// 调整IFrame的位置与div重合,并在div的下一层
260
try
261
{
262
iFrameDiv.style.position = "absolute";
263
iFrameDiv.style.width = div.offsetWidth;
264
iFrameDiv.style.height = div.offsetHeight;
265
iFrameDiv.style.top = div.style.top;
266
iFrameDiv.style.left = div.style.left;
267
iFrameDiv.style.zIndex = div.style.zIndex - 1;
268
iFrameDiv.style.visibility = div.style.visibility;
269
}
270
catch (e)
271
{
272
}
273
}
274
275
/**/
/**
276
文本输入框的onkeydown响应函数
277
*/
278
function
keypressHandler (evt)
279
{
280
// 获取对下拉区的引用
281
var div = getDiv(divName);
282
283
// 如果下拉区不显示,则什么也不做
284
if (div.style.visibility == "hidden")
285
{
286
return true;
287
}
288
289
// 确保evt是一个有效的事件
290
if (!evt && window.event)
291
{
292
evt = window.event;
293
}
294
var key = evt.keyCode;
295
296
var KEYUP = 38;
297
var KEYDOWN = 40;
298
var KEYENTER = 13;
299
var KEYTAB = 9;
300
301
// 只处理上下键、回车键和Tab键的响应
302
if ((key != KEYUP) && (key != KEYDOWN) && (key != KEYENTER) && (key != KEYTAB))
303
{
304
return true;
305
}
306
307
var selNum = getSelectedSpanNum(div);
308
var selSpan = setSelectedSpan(div, selNum);
309
310
// 如果键入回车和Tab,则选择当前选择条目
311
if ((key == KEYENTER) || (key == KEYTAB))
312
{
313
if (selSpan)
314
{
315
_selectResult(selSpan);
316
}
317
evt.cancelBubble = true;
318
return false;
319
}
320
else //如果键入上下键,则上下移动选中条目
321
{
322
if (key == KEYUP)
323
{
324
selSpan = setSelectedSpan(div, selNum - 1);
325
}
326
if (key == KEYDOWN)
327
{
328
selSpan = setSelectedSpan(div, selNum + 1);
329
}
330
if (selSpan)
331
{
332
_highlightResult(selSpan);
333
}
334
}
335
336
// 显示下拉区
337
showDiv(true);
338
return true;
339
}
340
341
/**/
/**
342
获取当前选中的条目的序号
343
*/
344
function
getSelectedSpanNum(div)
345
{
346
var count = -1;
347
var spans = div.getElementsByTagName("div");
348
if (spans)
349
{
350
for (var i = 0; i < spans.length; i++)
351
{
352
count++;
353
if (spans[i].style.backgroundColor != div.style.backgroundColor)
354
{
355
return count;
356
}
357
}
358
}
359
360
return -1;
361
}
362
363
/**/
/**
364
选择指定序号的结果条目
365
*/
366
function
setSelectedSpan(div, spanNum)
367
{
368
var count = -1;
369
var thisSpan;
370
var spans = div.getElementsByTagName("div");
371
if (spans)
372
{
373
for (var i = 0; i < spans.length; i++)
374
{
375
if (++count == spanNum)
376
{
377
_highlightResult(spans[i]);
378
thisSpan = spans[i];
379
}
380
else
381
{
382
_unhighlightResult(spans[i]);
383
}
384
}
385
}
386
387
return thisSpan;
388
}
389
InitQueryCode
函数必须在页面的
onload
响应中执行,该函数最后调用
setTimeout
方法执行了
mainLoop
方法。注意,
mainLoop
方法并没有在
lookup.js
中定义,必须在包含
lookup.js
文件的页面文件中增加该函数的定义。于此,我们就需要在Default.aspx页面上加入如下定义:
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

109

110

111

112

113


114

115

116

117



118

119

120

121

122

123

124

125

126

127



128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159


160

161

162

163

164



165

166

167

168

169

170



171

172

173



174

175



176

177



178

179

180

181

182

183

184

185

186

187

188

189


190

191

192

193



194

195

196

197

198



199

200

201

202


203

204

205

206



207

208

209

210

211



212

213

214

215


216

217

218

219



220

221

222



223

224

225

226



227

228

229

230

231

232


233

234

235

236



237

238

239

240


241

242

243

244



245

246

247



248

249

250

251

252

253

254

255

256

257

258

259

260

261



262

263

264

265

266

267

268

269

270

271



272

273

274

275


276

277

278

279



280

281

282

283

284

285



286

287

288

289

290

291



292

293

294

295

296

297

298

299

300

301

302

303



304

305

306

307

308

309

310

311

312



313

314



315

316

317

318

319

320

321



322

323



324

325

326

327



328

329

330

331



332

333

334

335

336

337

338

339

340

341


342

343

344

345



346

347

348

349



350

351



352

353

354



355

356

357

358

359

360

361

362

363


364

365

366

367



368

369

370

371

372



373

374



375

376



377

378

379

380

381



382

383

384

385

386

387

388

389

1
<
script language
=
"
javascript
"
src
=
"
lookup.js
"
><
/
script>
2
<
script language
=
"
javascript
"
>
3
mainLoop
=
function
()
4
{
5
val = escape(queryField.value);
6
if (lastVal != val)
7
{
8
var response = _Default.GetSearchItems(val);
9
showQueryDiv(response.value);
10
lastVal = val;
11
}
12
setTimeout('mainLoop()', 100);
13
return true;
14
}
15
<
/
script>
由上述代码可以看到
mainLoop
函数每隔
100ms
会执行一次,它会判断当前文本输入框的值和上次提交查询的值是否相同,如果不同,它会重新向服务器发送请求进行查询,并且更新下拉区域的显示。
2

3

4



5

6

7



8

9

10

11

12

13

14

15

于此,一个基于 Ajax的自动完成功能就实现了。
本文借鉴于《 ajax web2.0快速入门与项目实践》。
这本书上还使用了控件将该功能进行了封装,这样要实现Ajax的自动完成功能就更加方便了。在此就不做过多解说。
本文示例代码下载:AutoComplete.rar
---------------------------------------------------------------------------------------------------------