|
1 /** |
|
2 * Javascript auto-completion for form fields. |
|
3 */ |
|
4 |
|
5 var af_current = false; |
|
6 |
|
7 function AutofillUsername(parent, event, allowanon) |
|
8 { |
|
9 // if this is IE, use the old code |
|
10 if ( IE ) |
|
11 { |
|
12 ajaxUserNameComplete(parent); |
|
13 return false; |
|
14 } |
|
15 if ( parent.afobj ) |
|
16 { |
|
17 parent.afobj.go(); |
|
18 return true; |
|
19 } |
|
20 |
|
21 parent.autocomplete = 'off'; |
|
22 parent.setAttribute('autocomplete', 'off'); |
|
23 |
|
24 this.repeat = false; |
|
25 this.event = event; |
|
26 this.box_id = false; |
|
27 this.boxes = new Array(); |
|
28 this.state = false; |
|
29 this.allowanon = ( allowanon ) ? true : false; |
|
30 |
|
31 if ( !parent.id ) |
|
32 parent.id = 'afuser_' + Math.floor(Math.random() * 1000000); |
|
33 |
|
34 this.field_id = parent.id; |
|
35 |
|
36 // constants |
|
37 this.KEY_UP = 38; |
|
38 this.KEY_DOWN = 40; |
|
39 this.KEY_ESC = 27; |
|
40 this.KEY_TAB = 9; |
|
41 this.KEY_ENTER = 13; |
|
42 |
|
43 // response cache |
|
44 this.responses = new Object(); |
|
45 |
|
46 // ajax placeholder |
|
47 this.process_dataset = function(resp_json) |
|
48 { |
|
49 // window.console.info('Processing the following dataset.'); |
|
50 // window.console.debug(resp_json); |
|
51 var autofill = this; |
|
52 |
|
53 if ( typeof(autofill.event) == 'object' ) |
|
54 { |
|
55 if ( autofill.event.keyCode ) |
|
56 { |
|
57 if ( autofill.event.keyCode == autofill.KEY_ENTER && autofill.boxes.length < 1 && !autofill.box_id ) |
|
58 { |
|
59 // user hit enter after accepting a suggestion - submit the form |
|
60 var frm = findParentForm($(autofill.field_id).object); |
|
61 frm._af_acting = false; |
|
62 frm.submit(); |
|
63 // window.console.info('Submitting form'); |
|
64 return false; |
|
65 } |
|
66 if ( autofill.event.keyCode == autofill.KEY_UP || autofill.event.keyCode == autofill.KEY_DOWN || autofill.event.keyCode == autofill.KEY_ESC || autofill.event.keyCode == autofill.KEY_TAB || autofill.event.keyCode == autofill.KEY_ENTER ) |
|
67 { |
|
68 autofill.keyhandler(); |
|
69 // window.console.info('Control key detected, called keyhandler and exiting'); |
|
70 return true; |
|
71 } |
|
72 } |
|
73 } |
|
74 |
|
75 if ( this.box_id ) |
|
76 { |
|
77 this.destroy(); |
|
78 // window.console.info('already have a box open - destroying and exiting'); |
|
79 //return false; |
|
80 } |
|
81 |
|
82 var users = new Array(); |
|
83 for ( var i = 0; i < resp_json.users_real.length; i++ ) |
|
84 { |
|
85 try |
|
86 { |
|
87 var user = resp_json.users_real[i].toLowerCase(); |
|
88 var inp = $(autofill.field_id).object.value; |
|
89 inp = inp.toLowerCase(); |
|
90 if ( user.indexOf(inp) > -1 ) |
|
91 { |
|
92 users.push(resp_json.users_real[i]); |
|
93 } |
|
94 } |
|
95 catch(e) |
|
96 { |
|
97 users.push(resp_json.users_real[i]); |
|
98 } |
|
99 } |
|
100 |
|
101 // This was used ONLY for debugging the DOM and list logic |
|
102 // resp_json.users = resp_json.users_real; |
|
103 |
|
104 // construct table |
|
105 var div = document.createElement('div'); |
|
106 div.className = 'tblholder'; |
|
107 div.style.clip = 'rect(0px,auto,auto,0px)'; |
|
108 div.style.maxHeight = '200px'; |
|
109 div.style.overflow = 'auto'; |
|
110 div.style.zIndex = '9999'; |
|
111 var table = document.createElement('table'); |
|
112 table.border = '0'; |
|
113 table.cellSpacing = '1'; |
|
114 table.cellPadding = '3'; |
|
115 |
|
116 var tr = document.createElement('tr'); |
|
117 var th = document.createElement('th'); |
|
118 th.appendChild(document.createTextNode('Username suggestions')); |
|
119 tr.appendChild(th); |
|
120 table.appendChild(tr); |
|
121 |
|
122 if ( users.length < 1 ) |
|
123 { |
|
124 var tr = document.createElement('tr'); |
|
125 var td = document.createElement('td'); |
|
126 td.className = 'row1'; |
|
127 td.appendChild(document.createTextNode('No suggestions')); |
|
128 td.afobj = autofill; |
|
129 tr.appendChild(td); |
|
130 table.appendChild(tr); |
|
131 } |
|
132 else |
|
133 |
|
134 for ( var i = 0; i < users.length; i++ ) |
|
135 { |
|
136 var user = users[i]; |
|
137 var tr = document.createElement('tr'); |
|
138 var td = document.createElement('td'); |
|
139 td.className = ( i == 0 ) ? 'row2' : 'row1'; |
|
140 td.appendChild(document.createTextNode(user)); |
|
141 td.afobj = autofill; |
|
142 td.style.cursor = 'pointer'; |
|
143 td.onclick = function() |
|
144 { |
|
145 this.afobj.set(this.firstChild.nodeValue); |
|
146 } |
|
147 tr.appendChild(td); |
|
148 table.appendChild(tr); |
|
149 } |
|
150 |
|
151 // Finalize div |
|
152 var tb_top = $(autofill.field_id).Top(); |
|
153 var tb_height = $(autofill.field_id).Height(); |
|
154 var af_top = tb_top + tb_height - 9; |
|
155 var tb_left = $(autofill.field_id).Left(); |
|
156 var af_left = tb_left; |
|
157 |
|
158 div.style.position = 'absolute'; |
|
159 div.style.left = af_left + 'px'; |
|
160 div.style.top = af_top + 'px'; |
|
161 div.style.width = '200px'; |
|
162 div.style.fontSize = '7pt'; |
|
163 div.style.fontFamily = 'Trebuchet MS, arial, helvetica, sans-serif'; |
|
164 div.id = 'afuserdrop_' + Math.floor(Math.random() * 1000000); |
|
165 div.appendChild(table); |
|
166 |
|
167 autofill.boxes.push(div.id); |
|
168 autofill.box_id = div.id; |
|
169 if ( users.length > 0 ) |
|
170 autofill.state = users[0]; |
|
171 |
|
172 var body = document.getElementsByTagName('body')[0]; |
|
173 body.appendChild(div); |
|
174 |
|
175 autofill.repeat = true; |
|
176 } |
|
177 |
|
178 // perform ajax call |
|
179 this.fetch_and_process = function() |
|
180 { |
|
181 af_current = this; |
|
182 var processResponse = function() |
|
183 { |
|
184 if ( ajax.readyState == 4 ) |
|
185 { |
|
186 var afobj = af_current; |
|
187 af_current = false; |
|
188 // parse the JSON response |
|
189 var response = String(ajax.responseText) + ' '; |
|
190 if ( response.substr(0,1) != '{' ) |
|
191 { |
|
192 new messagebox(MB_OK|MB_ICONSTOP, 'Invalid response', 'Invalid or unexpected JSON response from server:<pre>' + ajax.responseText + '</pre>'); |
|
193 return false; |
|
194 } |
|
195 if ( $(afobj.field_id).object.value.length < 3 ) |
|
196 return false; |
|
197 var resp_json = parseJSON(response); |
|
198 var resp_code = $(afobj.field_id).object.value.toLowerCase().substr(0, 3); |
|
199 afobj.responses[resp_code] = resp_json; |
|
200 afobj.process_dataset(resp_json); |
|
201 } |
|
202 } |
|
203 var usernamefragment = ajaxEscape($(this.field_id).object.value); |
|
204 ajaxGet(stdAjaxPrefix + '&_mode=fillusername&name=' + usernamefragment + '&allowanon=' + ( this.allowanon ? '1' : '0' ), processResponse); |
|
205 } |
|
206 |
|
207 this.go = function() |
|
208 { |
|
209 if ( document.getElementById(this.field_id).value.length < 3 ) |
|
210 { |
|
211 this.destroy(); |
|
212 return false; |
|
213 } |
|
214 |
|
215 if ( af_current ) |
|
216 return false; |
|
217 |
|
218 var resp_code = $(this.field_id).object.value.toLowerCase().substr(0, 3); |
|
219 if ( this.responses.length < 1 || ! this.responses[ resp_code ] ) |
|
220 { |
|
221 // window.console.info('Cannot find dataset ' + resp_code + ' in cache, sending AJAX request'); |
|
222 this.fetch_and_process(); |
|
223 } |
|
224 else |
|
225 { |
|
226 // window.console.info('Using cached dataset: ' + resp_code); |
|
227 var resp_json = this.responses[ resp_code ]; |
|
228 this.process_dataset(resp_json); |
|
229 } |
|
230 document.getElementById(this.field_id).onkeyup = function(event) |
|
231 { |
|
232 this.afobj.event = event; |
|
233 this.afobj.go(); |
|
234 } |
|
235 document.getElementById(this.field_id).onkeydown = function(event) |
|
236 { |
|
237 var form = findParentForm(this); |
|
238 if ( typeof(event) != 'object' ) |
|
239 var event = window.event; |
|
240 if ( typeof(event) == 'object' ) |
|
241 { |
|
242 if ( event.keyCode == this.afobj.KEY_ENTER && this.afobj.boxes.length < 1 && !this.afobj.box_id ) |
|
243 { |
|
244 // user hit enter after accepting a suggestion - submit the form |
|
245 form._af_acting = false; |
|
246 return true; |
|
247 } |
|
248 } |
|
249 form._af_acting = true; |
|
250 } |
|
251 } |
|
252 |
|
253 this.keyhandler = function() |
|
254 { |
|
255 var key = this.event.keyCode; |
|
256 if ( key == this.KEY_ENTER && !this.repeat ) |
|
257 { |
|
258 var form = findParentForm($(this.field_id).object); |
|
259 form._af_acting = false; |
|
260 return true; |
|
261 } |
|
262 switch(key) |
|
263 { |
|
264 case this.KEY_UP: |
|
265 this.focus_up(); |
|
266 break; |
|
267 case this.KEY_DOWN: |
|
268 this.focus_down(); |
|
269 break; |
|
270 case this.KEY_ESC: |
|
271 this.destroy(); |
|
272 break; |
|
273 case this.KEY_TAB: |
|
274 this.destroy(); |
|
275 break; |
|
276 case this.KEY_ENTER: |
|
277 this.set(); |
|
278 break; |
|
279 } |
|
280 |
|
281 var form = findParentForm($(this.field_id).object); |
|
282 form._af_acting = false; |
|
283 } |
|
284 |
|
285 this.get_state_td = function() |
|
286 { |
|
287 var div = document.getElementById(this.box_id); |
|
288 if ( !div ) |
|
289 return false; |
|
290 if ( !this.state ) |
|
291 return false; |
|
292 var table = div.firstChild; |
|
293 for ( var i = 1; i < table.childNodes.length; i++ ) |
|
294 { |
|
295 // the table is DOM-constructed so no cruddy HTML hacks :-) |
|
296 var child = table.childNodes[i]; |
|
297 var tn = child.firstChild.firstChild; |
|
298 if ( tn.nodeValue == this.state ) |
|
299 return child.firstChild; |
|
300 } |
|
301 return false; |
|
302 } |
|
303 |
|
304 this.focus_down = function() |
|
305 { |
|
306 var state_td = this.get_state_td(); |
|
307 if ( !state_td ) |
|
308 return false; |
|
309 if ( state_td.parentNode.nextSibling ) |
|
310 { |
|
311 // Ooh boy, DOM stuff can be so complicated... |
|
312 // <tr> --> <tr> |
|
313 // <td> <td> |
|
314 // user user |
|
315 |
|
316 var newstate = state_td.parentNode.nextSibling.firstChild.firstChild.nodeValue; |
|
317 if ( !newstate ) |
|
318 return false; |
|
319 this.state = newstate; |
|
320 state_td.className = 'row1'; |
|
321 state_td.parentNode.nextSibling.firstChild.className = 'row2'; |
|
322 |
|
323 // Exception - automatically scroll around if the item is off-screen |
|
324 var height = $(this.box_id).Height(); |
|
325 var top = $(this.box_id).object.scrollTop; |
|
326 var scroll_bottom = height + top; |
|
327 |
|
328 var td_top = $(state_td.parentNode.nextSibling.firstChild).Top() - $(this.box_id).Top(); |
|
329 var td_height = $(state_td.parentNode.nextSibling.firstChild).Height(); |
|
330 var td_bottom = td_top + td_height; |
|
331 |
|
332 if ( td_bottom > scroll_bottom ) |
|
333 { |
|
334 var scrollY = td_top - height + 2*td_height - 7; |
|
335 // window.console.debug(scrollY); |
|
336 $(this.box_id).object.scrollTop = scrollY; |
|
337 /* |
|
338 var newtd = state_td.parentNode.nextSibling.firstChild; |
|
339 var a = document.createElement('a'); |
|
340 var id = 'autofill' + Math.floor(Math.random() * 100000); |
|
341 a.name = id; |
|
342 a.id = id; |
|
343 newtd.appendChild(a); |
|
344 window.location.hash = '#' + id; |
|
345 */ |
|
346 |
|
347 // In firefox, scrolling like that makes the field get unfocused |
|
348 $(this.field_id).object.focus(); |
|
349 } |
|
350 } |
|
351 else |
|
352 { |
|
353 return false; |
|
354 } |
|
355 } |
|
356 |
|
357 this.focus_up = function() |
|
358 { |
|
359 var state_td = this.get_state_td(); |
|
360 if ( !state_td ) |
|
361 return false; |
|
362 if ( state_td.parentNode.previousSibling && state_td.parentNode.previousSibling.firstChild.tagName != 'TH' ) |
|
363 { |
|
364 // Ooh boy, DOM stuff can be so complicated... |
|
365 // <tr> <-- <tr> |
|
366 // <td> <td> |
|
367 // user user |
|
368 |
|
369 var newstate = state_td.parentNode.previousSibling.firstChild.firstChild.nodeValue; |
|
370 if ( !newstate ) |
|
371 { |
|
372 return false; |
|
373 } |
|
374 this.state = newstate; |
|
375 state_td.className = 'row1'; |
|
376 state_td.parentNode.previousSibling.firstChild.className = 'row2'; |
|
377 |
|
378 // Exception - automatically scroll around if the item is off-screen |
|
379 var top = $(this.box_id).object.scrollTop; |
|
380 |
|
381 var td_top = $(state_td.parentNode.previousSibling.firstChild).Top() - $(this.box_id).Top(); |
|
382 |
|
383 if ( td_top < top ) |
|
384 { |
|
385 $(this.box_id).object.scrollTop = td_top - 10; |
|
386 /* |
|
387 var newtd = state_td.parentNode.previousSibling.firstChild; |
|
388 var a = document.createElement('a'); |
|
389 var id = 'autofill' + Math.floor(Math.random() * 100000); |
|
390 a.name = id; |
|
391 a.id = id; |
|
392 newtd.appendChild(a); |
|
393 window.location.hash = '#' + id; |
|
394 */ |
|
395 |
|
396 // In firefox, scrolling like that makes the field get unfocused |
|
397 $(this.field_id).object.focus(); |
|
398 } |
|
399 } |
|
400 else |
|
401 { |
|
402 $(this.box_id).object.scrollTop = 0; |
|
403 return false; |
|
404 } |
|
405 } |
|
406 |
|
407 this.destroy = function() |
|
408 { |
|
409 this.repeat = false; |
|
410 var body = document.getElementsByTagName('body')[0]; |
|
411 var div = document.getElementById(this.box_id); |
|
412 if ( !div ) |
|
413 return false; |
|
414 setTimeout('var body = document.getElementsByTagName("body")[0]; body.removeChild(document.getElementById("'+div.id+'"));', 20); |
|
415 // hackish workaround for divs that stick around past their welcoming period |
|
416 for ( var i = 0; i < this.boxes.length; i++ ) |
|
417 { |
|
418 var div = document.getElementById(this.boxes[i]); |
|
419 if ( div ) |
|
420 setTimeout('var body = document.getElementsByTagName("body")[0]; var div = document.getElementById("'+div.id+'"); if ( div ) body.removeChild(div);', 20); |
|
421 delete(this.boxes[i]); |
|
422 } |
|
423 this.box_id = false; |
|
424 this.state = false; |
|
425 } |
|
426 |
|
427 this.set = function(val) |
|
428 { |
|
429 var ta = document.getElementById(this.field_id); |
|
430 if ( val ) |
|
431 ta.value = val; |
|
432 else if ( this.state ) |
|
433 ta.value = this.state; |
|
434 this.destroy(); |
|
435 } |
|
436 |
|
437 this.sleep = function() |
|
438 { |
|
439 if ( this.box_id ) |
|
440 { |
|
441 var div = document.getElementById(this.box_id); |
|
442 div.style.display = 'none'; |
|
443 } |
|
444 var el = $(this.field_id).object; |
|
445 var fr = findParentForm(el); |
|
446 el._af_acting = false; |
|
447 } |
|
448 |
|
449 this.wake = function() |
|
450 { |
|
451 if ( this.box_id ) |
|
452 { |
|
453 var div = document.getElementById(this.box_id); |
|
454 div.style.display = 'block'; |
|
455 } |
|
456 } |
|
457 |
|
458 parent.onblur = function() |
|
459 { |
|
460 af_current = this.afobj; |
|
461 window.setTimeout('if ( af_current ) af_current.sleep(); af_current = false;', 50); |
|
462 } |
|
463 |
|
464 parent.onfocus = function() |
|
465 { |
|
466 af_current = this.afobj; |
|
467 window.setTimeout('if ( af_current ) af_current.wake(); af_current = false;', 50); |
|
468 } |
|
469 |
|
470 parent.afobj = this; |
|
471 var frm = findParentForm(parent); |
|
472 if ( frm.onsubmit ) |
|
473 { |
|
474 frm.orig_onsubmit = frm.onsubmit; |
|
475 frm.onsubmit = function(e) |
|
476 { |
|
477 if ( this._af_acting ) |
|
478 return false; |
|
479 this.orig_onsubmit(e); |
|
480 } |
|
481 } |
|
482 else |
|
483 { |
|
484 frm.onsubmit = function() |
|
485 { |
|
486 if ( this._af_acting ) |
|
487 return false; |
|
488 } |
|
489 } |
|
490 |
|
491 if ( parent.value.length < 3 ) |
|
492 { |
|
493 this.destroy(); |
|
494 return false; |
|
495 } |
|
496 } |
|
497 |
|
498 function findParentForm(o) |
|
499 { |
|
500 if ( o.tagName == 'FORM' ) |
|
501 return o; |
|
502 while(true) |
|
503 { |
|
504 o = o.parentNode; |
|
505 if ( !o ) |
|
506 return false; |
|
507 if ( o.tagName == 'FORM' ) |
|
508 return o; |
|
509 } |
|
510 return false; |
|
511 } |
|
512 |