124 el.onkeyup = null; |
124 el.onkeyup = null; |
125 el.className = 'autofill page'; |
125 el.className = 'autofill page'; |
126 autofill_init_element(el, {}); |
126 autofill_init_element(el, {}); |
127 } |
127 } |
128 |
128 |
129 addOnloadHook(function() |
129 window.autofill_init = function() |
130 { |
130 { |
131 load_component(['l10n', 'jquery', 'jquery-ui']); |
131 load_component(['l10n', 'jquery', 'jquery-ui']); |
|
132 |
|
133 if ( !window.jQuery ) |
|
134 { |
|
135 throw('jQuery didn\'t load properly. Aborting auto-complete init.'); |
|
136 } |
|
137 |
|
138 jQuery.autocomplete = function(input, options) { |
|
139 // Create a link to self |
|
140 var me = this; |
|
141 |
|
142 // Create jQuery object for input element |
|
143 var $input = $(input).attr("autocomplete", "off"); |
|
144 |
|
145 // Apply inputClass if necessary |
|
146 if (options.inputClass) { |
|
147 $input.addClass(options.inputClass); |
|
148 } |
|
149 |
|
150 // Create results |
|
151 var results = document.createElement("div"); |
|
152 $(results).addClass('tblholder').css('z-index', getHighestZ() + 1).css('margin-top', 0); |
|
153 $(results).css('clip', 'rect(0px,auto,auto,0px)').css('overflow', 'auto').css('max-height', '300px'); |
|
154 |
|
155 // Create jQuery object for results |
|
156 // var $results = $(results); |
|
157 var $results = $(results).hide().addClass(options.resultsClass).css("position", "absolute"); |
|
158 if( options.width > 0 ) { |
|
159 $results.css("width", options.width); |
|
160 } |
|
161 |
|
162 // Add to body element |
|
163 $("body").append(results); |
|
164 |
|
165 input.autocompleter = me; |
|
166 |
|
167 var timeout = null; |
|
168 var prev = ""; |
|
169 var active = -1; |
|
170 var cache = {}; |
|
171 var keyb = false; |
|
172 // hasFocus was false by default, see if making it true helps |
|
173 var hasFocus = true; |
|
174 var hasNoResults = false; |
|
175 var lastKeyPressCode = null; |
|
176 var mouseDownOnSelect = false; |
|
177 var hidingResults = false; |
|
178 |
|
179 // flush cache |
|
180 function flushCache(){ |
|
181 cache = {}; |
|
182 cache.data = {}; |
|
183 cache.length = 0; |
|
184 }; |
|
185 |
|
186 // flush cache |
|
187 flushCache(); |
|
188 |
|
189 // if there is a data array supplied |
|
190 if( options.data != null ){ |
|
191 var sFirstChar = "", stMatchSets = {}, row = []; |
|
192 |
|
193 // no url was specified, we need to adjust the cache length to make sure it fits the local data store |
|
194 if( typeof options.url != "string" ) { |
|
195 options.cacheLength = 1; |
|
196 } |
|
197 |
|
198 // loop through the array and create a lookup structure |
|
199 for( var i=0; i < options.data.length; i++ ){ |
|
200 // if row is a string, make an array otherwise just reference the array |
|
201 row = ((typeof options.data[i] == "string") ? [options.data[i]] : options.data[i]); |
|
202 |
|
203 // if the length is zero, don't add to list |
|
204 if( row[0].length > 0 ){ |
|
205 // get the first character |
|
206 sFirstChar = row[0].substring(0, 1).toLowerCase(); |
|
207 // if no lookup array for this character exists, look it up now |
|
208 if( !stMatchSets[sFirstChar] ) stMatchSets[sFirstChar] = []; |
|
209 // if the match is a string |
|
210 stMatchSets[sFirstChar].push(row); |
|
211 } |
|
212 } |
|
213 |
|
214 // add the data items to the cache |
|
215 if ( options.cacheLength ) |
|
216 { |
|
217 for( var k in stMatchSets ) { |
|
218 // increase the cache size |
|
219 options.cacheLength++; |
|
220 // add to the cache |
|
221 addToCache(k, stMatchSets[k]); |
|
222 } |
|
223 } |
|
224 } |
|
225 |
|
226 $input |
|
227 .keydown(function(e) { |
|
228 // track last key pressed |
|
229 lastKeyPressCode = e.keyCode; |
|
230 switch(e.keyCode) { |
|
231 case 38: // up |
|
232 e.preventDefault(); |
|
233 moveSelect(-1); |
|
234 break; |
|
235 case 40: // down |
|
236 e.preventDefault(); |
|
237 moveSelect(1); |
|
238 break; |
|
239 case 9: // tab |
|
240 case 13: // return |
|
241 if( selectCurrent() ){ |
|
242 // make sure to blur off the current field |
|
243 // (Enano edit - why do we want this, again?) |
|
244 // $input.get(0).blur(); |
|
245 e.preventDefault(); |
|
246 } |
|
247 break; |
|
248 default: |
|
249 active = -1; |
|
250 if (timeout) clearTimeout(timeout); |
|
251 timeout = setTimeout(function(){onChange();}, options.delay); |
|
252 break; |
|
253 } |
|
254 }) |
|
255 .focus(function(){ |
|
256 // track whether the field has focus, we shouldn't process any results if the field no longer has focus |
|
257 hasFocus = true; |
|
258 }) |
|
259 .blur(function() { |
|
260 // track whether the field has focus |
|
261 hasFocus = false; |
|
262 if (!mouseDownOnSelect) { |
|
263 hideResults(); |
|
264 } |
|
265 }); |
|
266 |
|
267 hideResultsNow(); |
|
268 |
|
269 function onChange() { |
|
270 // ignore if the following keys are pressed: [del] [shift] [capslock] |
|
271 if( lastKeyPressCode == 46 || (lastKeyPressCode > 8 && lastKeyPressCode < 32) ) return $results.hide(); |
|
272 var v = $input.val(); |
|
273 if (v == prev) return; |
|
274 prev = v; |
|
275 if (v.length >= options.minChars) { |
|
276 $input.addClass(options.loadingClass); |
|
277 requestData(v); |
|
278 } else { |
|
279 $input.removeClass(options.loadingClass); |
|
280 $results.hide(); |
|
281 } |
|
282 }; |
|
283 |
|
284 function moveSelect(step) { |
|
285 |
|
286 var lis = $("td", results); |
|
287 if (!lis || hasNoResults) return; |
|
288 |
|
289 active += step; |
|
290 |
|
291 if (active < 0) { |
|
292 active = 0; |
|
293 } else if (active >= lis.size()) { |
|
294 active = lis.size() - 1; |
|
295 } |
|
296 |
|
297 lis.removeClass("row2"); |
|
298 |
|
299 $(lis[active]).addClass("row2"); |
|
300 |
|
301 // scroll the results div |
|
302 // are we going up or down? |
|
303 var td_top = $dynano(lis[active]).Top() - $dynano(results).Top(); |
|
304 var td_height = $dynano(lis[active]).Height(); |
|
305 var td_bottom = td_top + td_height; |
|
306 var visibleTopBoundary = getScrollOffset(results); |
|
307 var results_height = $dynano(results).Height(); |
|
308 var visibleBottomBoundary = visibleTopBoundary + results_height; |
|
309 var scrollTo = false; |
|
310 if ( td_top < visibleTopBoundary && step < 0 ) |
|
311 { |
|
312 // going up: scroll the results div to just higher than the result we're trying to see |
|
313 scrollTo = td_top - 7; |
|
314 } |
|
315 else if ( td_bottom > visibleBottomBoundary && step > 0 ) |
|
316 { |
|
317 // going down is a little harder, we want the result to be at the bottom |
|
318 scrollTo = td_top - results_height + td_height + 7; |
|
319 } |
|
320 if ( scrollTo ) |
|
321 { |
|
322 results.scrollTop = scrollTo; |
|
323 } |
|
324 |
|
325 // Weird behaviour in IE |
|
326 // if (lis[active] && lis[active].scrollIntoView) { |
|
327 // lis[active].scrollIntoView(false); |
|
328 // } |
|
329 |
|
330 }; |
|
331 |
|
332 function selectCurrent() { |
|
333 var li = $("td.row2", results)[0]; |
|
334 if (!li) { |
|
335 var $li = $("td", results); |
|
336 if (options.selectOnly) { |
|
337 if ($li.length == 1) li = $li[0]; |
|
338 } else if (options.selectFirst) { |
|
339 li = $li[0]; |
|
340 } |
|
341 } |
|
342 if (li) { |
|
343 selectItem(li); |
|
344 return true; |
|
345 } else { |
|
346 return false; |
|
347 } |
|
348 }; |
|
349 |
|
350 function selectItem(li) { |
|
351 if (!li) { |
|
352 li = document.createElement("li"); |
|
353 li.extra = []; |
|
354 li.selectValue = ""; |
|
355 } |
|
356 var v = $.trim(li.selectValue ? li.selectValue : li.innerHTML); |
|
357 input.lastSelected = v; |
|
358 prev = v; |
|
359 $results.html(""); |
|
360 $input.val(v); |
|
361 hideResultsNow(); |
|
362 if (options.onItemSelect) { |
|
363 setTimeout(function() { options.onItemSelect(li) }, 1); |
|
364 } |
|
365 }; |
|
366 |
|
367 // selects a portion of the input string |
|
368 function createSelection(start, end){ |
|
369 // get a reference to the input element |
|
370 var field = $input.get(0); |
|
371 if( field.createTextRange ){ |
|
372 var selRange = field.createTextRange(); |
|
373 selRange.collapse(true); |
|
374 selRange.moveStart("character", start); |
|
375 selRange.moveEnd("character", end); |
|
376 selRange.select(); |
|
377 } else if( field.setSelectionRange ){ |
|
378 field.setSelectionRange(start, end); |
|
379 } else { |
|
380 if( field.selectionStart ){ |
|
381 field.selectionStart = start; |
|
382 field.selectionEnd = end; |
|
383 } |
|
384 } |
|
385 field.focus(); |
|
386 }; |
|
387 |
|
388 // fills in the input box w/the first match (assumed to be the best match) |
|
389 function autoFill(sValue){ |
|
390 // if the last user key pressed was backspace, don't autofill |
|
391 if( lastKeyPressCode != 8 ){ |
|
392 // fill in the value (keep the case the user has typed) |
|
393 $input.val($input.val() + sValue.substring(prev.length)); |
|
394 // select the portion of the value not typed by the user (so the next character will erase) |
|
395 createSelection(prev.length, sValue.length); |
|
396 } |
|
397 }; |
|
398 |
|
399 function showResults() { |
|
400 // get the position of the input field right now (in case the DOM is shifted) |
|
401 var pos = findPos(input); |
|
402 // either use the specified width, or autocalculate based on form element |
|
403 var iWidth = (options.width > 0) ? options.width : $input.width(); |
|
404 // reposition |
|
405 $results.css({ |
|
406 width: parseInt(iWidth) + "px", |
|
407 top: (pos.y + input.offsetHeight) + "px", |
|
408 left: pos.x + "px" |
|
409 }); |
|
410 if ( !$results.is(":visible") ) |
|
411 { |
|
412 $results.show("blind", {}, 200); |
|
413 } |
|
414 else |
|
415 { |
|
416 $results.show(); |
|
417 } |
|
418 }; |
|
419 |
|
420 function hideResults() { |
|
421 if (timeout) clearTimeout(timeout); |
|
422 timeout = setTimeout(hideResultsNow, 200); |
|
423 }; |
|
424 |
|
425 function hideResultsNow() { |
|
426 if (hidingResults) { |
|
427 return; |
|
428 } |
|
429 hidingResults = true; |
132 |
430 |
133 if ( !window.jQuery ) |
431 if (timeout) { |
134 { |
432 clearTimeout(timeout); |
135 throw('jQuery didn\'t load properly. Aborting auto-complete init.'); |
433 } |
136 } |
434 |
137 |
435 var v = $input.removeClass(options.loadingClass).val(); |
138 jQuery.autocomplete = function(input, options) { |
436 |
139 // Create a link to self |
437 if ($results.is(":visible")) { |
140 var me = this; |
438 $results.hide(); |
141 |
439 } |
142 // Create jQuery object for input element |
440 |
143 var $input = $(input).attr("autocomplete", "off"); |
441 if (options.mustMatch) { |
144 |
442 if (!input.lastSelected || input.lastSelected != v) { |
145 // Apply inputClass if necessary |
443 selectItem(null); |
146 if (options.inputClass) { |
444 } |
147 $input.addClass(options.inputClass); |
445 } |
148 } |
446 |
149 |
447 hidingResults = false; |
150 // Create results |
448 }; |
151 var results = document.createElement("div"); |
449 |
152 $(results).addClass('tblholder').css('z-index', getHighestZ() + 1).css('margin-top', 0); |
450 function receiveData(q, data) { |
153 $(results).css('clip', 'rect(0px,auto,auto,0px)').css('overflow', 'auto').css('max-height', '300px'); |
451 if (data) { |
154 |
452 $input.removeClass(options.loadingClass); |
155 // Create jQuery object for results |
453 results.innerHTML = ""; |
156 // var $results = $(results); |
454 |
157 var $results = $(results).hide().addClass(options.resultsClass).css("position", "absolute"); |
455 // if the field no longer has focus or if there are no matches, do not display the drop down |
158 if( options.width > 0 ) { |
456 if( !hasFocus ) |
159 $results.css("width", options.width); |
457 { |
160 } |
458 return hideResultsNow(); |
161 |
459 } |
162 // Add to body element |
460 if ( data.length == 0 && !options.showWhenNoResults ) |
163 $("body").append(results); |
461 { |
164 |
462 return hideResultsNow(); |
165 input.autocompleter = me; |
463 } |
166 |
464 hasNoResults = false; |
167 var timeout = null; |
465 |
168 var prev = ""; |
466 if ($.browser.msie) { |
169 var active = -1; |
467 // we put a styled iframe behind the calendar so HTML SELECT elements don't show through |
170 var cache = {}; |
468 $results.append(document.createElement('iframe')); |
171 var keyb = false; |
469 } |
172 // hasFocus was false by default, see if making it true helps |
470 results.appendChild(dataToDom(data)); |
173 var hasFocus = true; |
471 // autofill in the complete box w/the first match as long as the user hasn't entered in more data |
174 var hasNoResults = false; |
472 if( options.autoFill && ($input.val().toLowerCase() == q.toLowerCase()) ) autoFill(data[0][0]); |
175 var lastKeyPressCode = null; |
473 showResults(); |
176 var mouseDownOnSelect = false; |
474 } else { |
177 var hidingResults = false; |
475 hideResultsNow(); |
178 |
476 } |
179 // flush cache |
477 }; |
180 function flushCache(){ |
478 |
181 cache = {}; |
479 function parseData(data) { |
182 cache.data = {}; |
480 if (!data) return null; |
183 cache.length = 0; |
481 var parsed = parseJSON(data); |
184 }; |
482 return parsed; |
185 |
483 }; |
186 // flush cache |
484 |
187 flushCache(); |
485 function dataToDom(data) { |
188 |
486 var ul = document.createElement("table"); |
189 // if there is a data array supplied |
487 $(ul).attr("border", "0").attr("cellspacing", "1").attr("cellpadding", "3"); |
190 if( options.data != null ){ |
488 var num = data.length; |
191 var sFirstChar = "", stMatchSets = {}, row = []; |
489 |
192 |
490 if ( options.tableHeader ) |
193 // no url was specified, we need to adjust the cache length to make sure it fits the local data store |
491 { |
194 if( typeof options.url != "string" ) { |
492 ul.innerHTML = options.tableHeader; |
195 options.cacheLength = 1; |
493 } |
196 } |
494 |
197 |
495 if ( num == 0 ) |
198 // loop through the array and create a lookup structure |
496 { |
199 for( var i=0; i < options.data.length; i++ ){ |
497 // not showing any results |
200 // if row is a string, make an array otherwise just reference the array |
498 if ( options.noResultsHTML ) |
201 row = ((typeof options.data[i] == "string") ? [options.data[i]] : options.data[i]); |
499 ul.innerHTML += options.noResultsHTML; |
202 |
500 |
203 // if the length is zero, don't add to list |
501 hasNoResults = true; |
204 if( row[0].length > 0 ){ |
502 return ul; |
205 // get the first character |
503 } |
206 sFirstChar = row[0].substring(0, 1).toLowerCase(); |
504 |
207 // if no lookup array for this character exists, look it up now |
505 // limited results to a max number |
208 if( !stMatchSets[sFirstChar] ) stMatchSets[sFirstChar] = []; |
506 if( (options.maxItemsToShow > 0) && (options.maxItemsToShow < num) ) num = options.maxItemsToShow; |
209 // if the match is a string |
507 |
210 stMatchSets[sFirstChar].push(row); |
508 for (var i=0; i < num; i++) { |
|
509 var row = data[i]; |
|
510 if (!row) continue; |
|
511 |
|
512 if ( typeof(row[0]) != 'string' ) |
|
513 { |
|
514 // last ditch resort if it's a 1.1.4 autocomplete plugin that doesn't provide an automatic result. |
|
515 // hopefully this doesn't slow it down a lot. |
|
516 for ( var i in row ) |
|
517 { |
|
518 if ( i == "0" || i == 0 ) |
|
519 break; |
|
520 row[0] = row[i]; |
|
521 break; |
211 } |
522 } |
212 } |
523 } |
213 |
524 |
214 // add the data items to the cache |
525 var li = document.createElement("tr"); |
215 if ( options.cacheLength ) |
526 var td = document.createElement("td"); |
|
527 td.selectValue = row[0]; |
|
528 $(td).addClass('row1'); |
|
529 $(td).css("font-size", "smaller"); |
|
530 |
|
531 if ( options.formatItem ) |
216 { |
532 { |
217 for( var k in stMatchSets ) { |
533 td.innerHTML = options.formatItem(row, i, num); |
218 // increase the cache size |
|
219 options.cacheLength++; |
|
220 // add to the cache |
|
221 addToCache(k, stMatchSets[k]); |
|
222 } |
|
223 } |
|
224 } |
|
225 |
|
226 $input |
|
227 .keydown(function(e) { |
|
228 // track last key pressed |
|
229 lastKeyPressCode = e.keyCode; |
|
230 switch(e.keyCode) { |
|
231 case 38: // up |
|
232 e.preventDefault(); |
|
233 moveSelect(-1); |
|
234 break; |
|
235 case 40: // down |
|
236 e.preventDefault(); |
|
237 moveSelect(1); |
|
238 break; |
|
239 case 9: // tab |
|
240 case 13: // return |
|
241 if( selectCurrent() ){ |
|
242 // make sure to blur off the current field |
|
243 // (Enano edit - why do we want this, again?) |
|
244 // $input.get(0).blur(); |
|
245 e.preventDefault(); |
|
246 } |
|
247 break; |
|
248 default: |
|
249 active = -1; |
|
250 if (timeout) clearTimeout(timeout); |
|
251 timeout = setTimeout(function(){onChange();}, options.delay); |
|
252 break; |
|
253 } |
|
254 }) |
|
255 .focus(function(){ |
|
256 // track whether the field has focus, we shouldn't process any results if the field no longer has focus |
|
257 hasFocus = true; |
|
258 }) |
|
259 .blur(function() { |
|
260 // track whether the field has focus |
|
261 hasFocus = false; |
|
262 if (!mouseDownOnSelect) { |
|
263 hideResults(); |
|
264 } |
|
265 }); |
|
266 |
|
267 hideResultsNow(); |
|
268 |
|
269 function onChange() { |
|
270 // ignore if the following keys are pressed: [del] [shift] [capslock] |
|
271 if( lastKeyPressCode == 46 || (lastKeyPressCode > 8 && lastKeyPressCode < 32) ) return $results.hide(); |
|
272 var v = $input.val(); |
|
273 if (v == prev) return; |
|
274 prev = v; |
|
275 if (v.length >= options.minChars) { |
|
276 $input.addClass(options.loadingClass); |
|
277 requestData(v); |
|
278 } else { |
|
279 $input.removeClass(options.loadingClass); |
|
280 $results.hide(); |
|
281 } |
|
282 }; |
|
283 |
|
284 function moveSelect(step) { |
|
285 |
|
286 var lis = $("td", results); |
|
287 if (!lis || hasNoResults) return; |
|
288 |
|
289 active += step; |
|
290 |
|
291 if (active < 0) { |
|
292 active = 0; |
|
293 } else if (active >= lis.size()) { |
|
294 active = lis.size() - 1; |
|
295 } |
|
296 |
|
297 lis.removeClass("row2"); |
|
298 |
|
299 $(lis[active]).addClass("row2"); |
|
300 |
|
301 // scroll the results div |
|
302 // are we going up or down? |
|
303 var td_top = $dynano(lis[active]).Top() - $dynano(results).Top(); |
|
304 var td_height = $dynano(lis[active]).Height(); |
|
305 var td_bottom = td_top + td_height; |
|
306 var visibleTopBoundary = getScrollOffset(results); |
|
307 var results_height = $dynano(results).Height(); |
|
308 var visibleBottomBoundary = visibleTopBoundary + results_height; |
|
309 var scrollTo = false; |
|
310 if ( td_top < visibleTopBoundary && step < 0 ) |
|
311 { |
|
312 // going up: scroll the results div to just higher than the result we're trying to see |
|
313 scrollTo = td_top - 7; |
|
314 } |
|
315 else if ( td_bottom > visibleBottomBoundary && step > 0 ) |
|
316 { |
|
317 // going down is a little harder, we want the result to be at the bottom |
|
318 scrollTo = td_top - results_height + td_height + 7; |
|
319 } |
|
320 if ( scrollTo ) |
|
321 { |
|
322 console.debug('scrolling the results div to %d', scrollTo); |
|
323 results.scrollTop = scrollTo; |
|
324 } |
|
325 |
|
326 // Weird behaviour in IE |
|
327 // if (lis[active] && lis[active].scrollIntoView) { |
|
328 // lis[active].scrollIntoView(false); |
|
329 // } |
|
330 |
|
331 }; |
|
332 |
|
333 function selectCurrent() { |
|
334 var li = $("td.row2", results)[0]; |
|
335 if (!li) { |
|
336 var $li = $("td", results); |
|
337 if (options.selectOnly) { |
|
338 if ($li.length == 1) li = $li[0]; |
|
339 } else if (options.selectFirst) { |
|
340 li = $li[0]; |
|
341 } |
|
342 } |
|
343 if (li) { |
|
344 selectItem(li); |
|
345 return true; |
|
346 } else { |
|
347 return false; |
|
348 } |
|
349 }; |
|
350 |
|
351 function selectItem(li) { |
|
352 if (!li) { |
|
353 li = document.createElement("li"); |
|
354 li.extra = []; |
|
355 li.selectValue = ""; |
|
356 } |
|
357 var v = $.trim(li.selectValue ? li.selectValue : li.innerHTML); |
|
358 input.lastSelected = v; |
|
359 prev = v; |
|
360 $results.html(""); |
|
361 $input.val(v); |
|
362 hideResultsNow(); |
|
363 if (options.onItemSelect) { |
|
364 setTimeout(function() { options.onItemSelect(li) }, 1); |
|
365 } |
|
366 }; |
|
367 |
|
368 // selects a portion of the input string |
|
369 function createSelection(start, end){ |
|
370 // get a reference to the input element |
|
371 var field = $input.get(0); |
|
372 if( field.createTextRange ){ |
|
373 var selRange = field.createTextRange(); |
|
374 selRange.collapse(true); |
|
375 selRange.moveStart("character", start); |
|
376 selRange.moveEnd("character", end); |
|
377 selRange.select(); |
|
378 } else if( field.setSelectionRange ){ |
|
379 field.setSelectionRange(start, end); |
|
380 } else { |
|
381 if( field.selectionStart ){ |
|
382 field.selectionStart = start; |
|
383 field.selectionEnd = end; |
|
384 } |
|
385 } |
|
386 field.focus(); |
|
387 }; |
|
388 |
|
389 // fills in the input box w/the first match (assumed to be the best match) |
|
390 function autoFill(sValue){ |
|
391 // if the last user key pressed was backspace, don't autofill |
|
392 if( lastKeyPressCode != 8 ){ |
|
393 // fill in the value (keep the case the user has typed) |
|
394 $input.val($input.val() + sValue.substring(prev.length)); |
|
395 // select the portion of the value not typed by the user (so the next character will erase) |
|
396 createSelection(prev.length, sValue.length); |
|
397 } |
|
398 }; |
|
399 |
|
400 function showResults() { |
|
401 // get the position of the input field right now (in case the DOM is shifted) |
|
402 var pos = findPos(input); |
|
403 // either use the specified width, or autocalculate based on form element |
|
404 var iWidth = (options.width > 0) ? options.width : $input.width(); |
|
405 // reposition |
|
406 $results.css({ |
|
407 width: parseInt(iWidth) + "px", |
|
408 top: (pos.y + input.offsetHeight) + "px", |
|
409 left: pos.x + "px" |
|
410 }); |
|
411 if ( !$results.is(":visible") ) |
|
412 { |
|
413 $results.show("blind", {}, 200); |
|
414 } |
534 } |
415 else |
535 else |
416 { |
536 { |
417 $results.show(); |
537 td.innerHTML = row[0]; |
418 } |
538 } |
419 }; |
539 li.appendChild(td); |
420 |
540 ul.appendChild(li); |
421 function hideResults() { |
|
422 if (timeout) clearTimeout(timeout); |
|
423 timeout = setTimeout(hideResultsNow, 200); |
|
424 }; |
|
425 |
|
426 function hideResultsNow() { |
|
427 if (hidingResults) { |
|
428 return; |
|
429 } |
|
430 hidingResults = true; |
|
431 |
|
432 if (timeout) { |
|
433 clearTimeout(timeout); |
|
434 } |
|
435 |
541 |
436 var v = $input.removeClass(options.loadingClass).val(); |
542 $(td).hover( |
437 |
543 function() { $("tr", ul).removeClass("row2"); $(this).addClass("row2"); active = $("tr", ul).indexOf($(this).get(0)); }, |
438 if ($results.is(":visible")) { |
544 function() { $(this).removeClass("row2"); } |
439 $results.hide(); |
545 ).click(function(e) { |
440 } |
546 e.preventDefault(); |
441 |
547 e.stopPropagation(); |
442 if (options.mustMatch) { |
548 selectItem(this) |
443 if (!input.lastSelected || input.lastSelected != v) { |
549 }); |
444 selectItem(null); |
550 } |
|
551 |
|
552 $(ul).mousedown(function() { |
|
553 mouseDownOnSelect = true; |
|
554 }).mouseup(function() { |
|
555 mouseDownOnSelect = false; |
|
556 }); |
|
557 return ul; |
|
558 }; |
|
559 |
|
560 function requestData(q) { |
|
561 if (!options.matchCase) q = q.toLowerCase(); |
|
562 var data = options.cacheLength ? loadFromCache(q) : null; |
|
563 // recieve the cached data |
|
564 if (data) { |
|
565 receiveData(q, data); |
|
566 // if an AJAX url has been supplied, try loading the data now |
|
567 } else if( (typeof options.url == "string") && (options.url.length > 0) ){ |
|
568 $.get(makeUrl(q), function(data) { |
|
569 data = parseData(data); |
|
570 addToCache(q, data); |
|
571 receiveData(q, data); |
|
572 }); |
|
573 // if there's been no data found, remove the loading class |
|
574 } else { |
|
575 $input.removeClass(options.loadingClass); |
|
576 } |
|
577 }; |
|
578 |
|
579 function makeUrl(q) { |
|
580 var sep = options.url.indexOf('?') == -1 ? '?' : '&'; |
|
581 var url = options.url + encodeURI(q); |
|
582 for (var i in options.extraParams) { |
|
583 url += "&" + i + "=" + encodeURI(options.extraParams[i]); |
|
584 } |
|
585 return url; |
|
586 }; |
|
587 |
|
588 function loadFromCache(q) { |
|
589 if (!q) return null; |
|
590 if (cache.data[q]) return cache.data[q]; |
|
591 if (options.matchSubset) { |
|
592 for (var i = q.length - 1; i >= options.minChars; i--) { |
|
593 var qs = q.substr(0, i); |
|
594 var c = cache.data[qs]; |
|
595 if (c) { |
|
596 var csub = []; |
|
597 for (var j = 0; j < c.length; j++) { |
|
598 var x = c[j]; |
|
599 var x0 = x[0]; |
|
600 if (matchSubset(x0, q)) { |
|
601 csub[csub.length] = x; |
|
602 } |
|
603 } |
|
604 return csub; |
445 } |
605 } |
446 } |
606 } |
447 |
607 } |
448 hidingResults = false; |
608 return null; |
449 }; |
609 }; |
450 |
610 |
451 function receiveData(q, data) { |
611 function matchSubset(s, sub) { |
452 if (data) { |
612 if (!options.matchCase) s = s.toLowerCase(); |
453 $input.removeClass(options.loadingClass); |
613 var i = s.indexOf(sub); |
454 results.innerHTML = ""; |
614 if (i == -1) return false; |
455 |
615 return i == 0 || options.matchContains; |
456 // if the field no longer has focus or if there are no matches, do not display the drop down |
616 }; |
457 if( !hasFocus ) |
617 |
458 { |
618 this.flushCache = function() { |
459 return hideResultsNow(); |
619 flushCache(); |
|
620 }; |
|
621 |
|
622 this.setExtraParams = function(p) { |
|
623 options.extraParams = p; |
|
624 }; |
|
625 |
|
626 this.findValue = function(){ |
|
627 var q = $input.val(); |
|
628 |
|
629 if (!options.matchCase) q = q.toLowerCase(); |
|
630 var data = options.cacheLength ? loadFromCache(q) : null; |
|
631 if (data) { |
|
632 findValueCallback(q, data); |
|
633 } else if( (typeof options.url == "string") && (options.url.length > 0) ){ |
|
634 $.get(makeUrl(q), function(data) { |
|
635 data = parseData(data) |
|
636 addToCache(q, data); |
|
637 findValueCallback(q, data); |
|
638 }); |
|
639 } else { |
|
640 // no matches |
|
641 findValueCallback(q, null); |
|
642 } |
|
643 } |
|
644 |
|
645 function findValueCallback(q, data){ |
|
646 if (data) $input.removeClass(options.loadingClass); |
|
647 |
|
648 var num = (data) ? data.length : 0; |
|
649 var li = null; |
|
650 |
|
651 for (var i=0; i < num; i++) { |
|
652 var row = data[i]; |
|
653 |
|
654 if( row[0].toLowerCase() == q.toLowerCase() ){ |
|
655 li = document.createElement("li"); |
|
656 if (options.formatItem) { |
|
657 li.innerHTML = options.formatItem(row, i, num); |
|
658 li.selectValue = row[0]; |
|
659 } else { |
|
660 li.innerHTML = row[0]; |
|
661 li.selectValue = row[0]; |
460 } |
662 } |
461 if ( data.length == 0 && !options.showWhenNoResults ) |
663 var extra = null; |
462 { |
664 if( row.length > 1 ){ |
463 return hideResultsNow(); |
665 extra = []; |
464 } |
666 for (var j=1; j < row.length; j++) { |
465 hasNoResults = false; |
667 extra[extra.length] = row[j]; |
466 |
|
467 if ($.browser.msie) { |
|
468 // we put a styled iframe behind the calendar so HTML SELECT elements don't show through |
|
469 $results.append(document.createElement('iframe')); |
|
470 } |
|
471 results.appendChild(dataToDom(data)); |
|
472 // autofill in the complete box w/the first match as long as the user hasn't entered in more data |
|
473 if( options.autoFill && ($input.val().toLowerCase() == q.toLowerCase()) ) autoFill(data[0][0]); |
|
474 showResults(); |
|
475 } else { |
|
476 hideResultsNow(); |
|
477 } |
|
478 }; |
|
479 |
|
480 function parseData(data) { |
|
481 if (!data) return null; |
|
482 var parsed = parseJSON(data); |
|
483 return parsed; |
|
484 }; |
|
485 |
|
486 function dataToDom(data) { |
|
487 var ul = document.createElement("table"); |
|
488 $(ul).attr("border", "0").attr("cellspacing", "1").attr("cellpadding", "3"); |
|
489 var num = data.length; |
|
490 |
|
491 if ( options.tableHeader ) |
|
492 { |
|
493 ul.innerHTML = options.tableHeader; |
|
494 } |
|
495 |
|
496 if ( num == 0 ) |
|
497 { |
|
498 // not showing any results |
|
499 if ( options.noResultsHTML ) |
|
500 ul.innerHTML += options.noResultsHTML; |
|
501 |
|
502 hasNoResults = true; |
|
503 return ul; |
|
504 } |
|
505 |
|
506 // limited results to a max number |
|
507 if( (options.maxItemsToShow > 0) && (options.maxItemsToShow < num) ) num = options.maxItemsToShow; |
|
508 |
|
509 for (var i=0; i < num; i++) { |
|
510 var row = data[i]; |
|
511 if (!row) continue; |
|
512 |
|
513 if ( typeof(row[0]) != 'string' ) |
|
514 { |
|
515 // last ditch resort if it's a 1.1.4 autocomplete plugin that doesn't provide an automatic result. |
|
516 // hopefully this doesn't slow it down a lot. |
|
517 for ( var i in row ) |
|
518 { |
|
519 if ( i == "0" || i == 0 ) |
|
520 break; |
|
521 row[0] = row[i]; |
|
522 break; |
|
523 } |
668 } |
524 } |
669 } |
525 |
670 li.extra = extra; |
526 var li = document.createElement("tr"); |
671 } |
527 var td = document.createElement("td"); |
672 } |
528 td.selectValue = row[0]; |
673 |
529 $(td).addClass('row1'); |
674 if( options.onFindValue ) setTimeout(function() { options.onFindValue(li) }, 1); |
530 $(td).css("font-size", "smaller"); |
675 } |
531 |
676 |
532 if ( options.formatItem ) |
677 function addToCache(q, data) { |
533 { |
678 if (!data || !q || !options.cacheLength) return; |
534 td.innerHTML = options.formatItem(row, i, num); |
679 if (!cache.length || cache.length > options.cacheLength) { |
535 } |
|
536 else |
|
537 { |
|
538 td.innerHTML = row[0]; |
|
539 } |
|
540 li.appendChild(td); |
|
541 ul.appendChild(li); |
|
542 |
|
543 $(td).hover( |
|
544 function() { $("tr", ul).removeClass("row2"); $(this).addClass("row2"); active = $("tr", ul).indexOf($(this).get(0)); }, |
|
545 function() { $(this).removeClass("row2"); } |
|
546 ).click(function(e) { |
|
547 e.preventDefault(); |
|
548 e.stopPropagation(); |
|
549 selectItem(this) |
|
550 }); |
|
551 } |
|
552 |
|
553 $(ul).mousedown(function() { |
|
554 mouseDownOnSelect = true; |
|
555 }).mouseup(function() { |
|
556 mouseDownOnSelect = false; |
|
557 }); |
|
558 return ul; |
|
559 }; |
|
560 |
|
561 function requestData(q) { |
|
562 if (!options.matchCase) q = q.toLowerCase(); |
|
563 var data = options.cacheLength ? loadFromCache(q) : null; |
|
564 // recieve the cached data |
|
565 if (data) { |
|
566 receiveData(q, data); |
|
567 // if an AJAX url has been supplied, try loading the data now |
|
568 } else if( (typeof options.url == "string") && (options.url.length > 0) ){ |
|
569 $.get(makeUrl(q), function(data) { |
|
570 data = parseData(data); |
|
571 addToCache(q, data); |
|
572 receiveData(q, data); |
|
573 }); |
|
574 // if there's been no data found, remove the loading class |
|
575 } else { |
|
576 $input.removeClass(options.loadingClass); |
|
577 } |
|
578 }; |
|
579 |
|
580 function makeUrl(q) { |
|
581 var sep = options.url.indexOf('?') == -1 ? '?' : '&'; |
|
582 var url = options.url + encodeURI(q); |
|
583 for (var i in options.extraParams) { |
|
584 url += "&" + i + "=" + encodeURI(options.extraParams[i]); |
|
585 } |
|
586 return url; |
|
587 }; |
|
588 |
|
589 function loadFromCache(q) { |
|
590 if (!q) return null; |
|
591 if (cache.data[q]) return cache.data[q]; |
|
592 if (options.matchSubset) { |
|
593 for (var i = q.length - 1; i >= options.minChars; i--) { |
|
594 var qs = q.substr(0, i); |
|
595 var c = cache.data[qs]; |
|
596 if (c) { |
|
597 var csub = []; |
|
598 for (var j = 0; j < c.length; j++) { |
|
599 var x = c[j]; |
|
600 var x0 = x[0]; |
|
601 if (matchSubset(x0, q)) { |
|
602 csub[csub.length] = x; |
|
603 } |
|
604 } |
|
605 return csub; |
|
606 } |
|
607 } |
|
608 } |
|
609 return null; |
|
610 }; |
|
611 |
|
612 function matchSubset(s, sub) { |
|
613 if (!options.matchCase) s = s.toLowerCase(); |
|
614 var i = s.indexOf(sub); |
|
615 if (i == -1) return false; |
|
616 return i == 0 || options.matchContains; |
|
617 }; |
|
618 |
|
619 this.flushCache = function() { |
|
620 flushCache(); |
680 flushCache(); |
621 }; |
681 cache.length++; |
622 |
682 } else if (!cache[q]) { |
623 this.setExtraParams = function(p) { |
683 cache.length++; |
624 options.extraParams = p; |
684 } |
625 }; |
685 cache.data[q] = data; |
626 |
686 }; |
627 this.findValue = function(){ |
687 |
628 var q = $input.val(); |
688 function findPos(obj) { |
629 |
689 var curleft = obj.offsetLeft || 0; |
630 if (!options.matchCase) q = q.toLowerCase(); |
690 var curtop = obj.offsetTop || 0; |
631 var data = options.cacheLength ? loadFromCache(q) : null; |
691 while (obj = obj.offsetParent) { |
632 if (data) { |
692 curleft += obj.offsetLeft |
633 findValueCallback(q, data); |
693 curtop += obj.offsetTop |
634 } else if( (typeof options.url == "string") && (options.url.length > 0) ){ |
694 } |
635 $.get(makeUrl(q), function(data) { |
695 return {x:curleft,y:curtop}; |
636 data = parseData(data) |
|
637 addToCache(q, data); |
|
638 findValueCallback(q, data); |
|
639 }); |
|
640 } else { |
|
641 // no matches |
|
642 findValueCallback(q, null); |
|
643 } |
|
644 } |
|
645 |
|
646 function findValueCallback(q, data){ |
|
647 if (data) $input.removeClass(options.loadingClass); |
|
648 |
|
649 var num = (data) ? data.length : 0; |
|
650 var li = null; |
|
651 |
|
652 for (var i=0; i < num; i++) { |
|
653 var row = data[i]; |
|
654 |
|
655 if( row[0].toLowerCase() == q.toLowerCase() ){ |
|
656 li = document.createElement("li"); |
|
657 if (options.formatItem) { |
|
658 li.innerHTML = options.formatItem(row, i, num); |
|
659 li.selectValue = row[0]; |
|
660 } else { |
|
661 li.innerHTML = row[0]; |
|
662 li.selectValue = row[0]; |
|
663 } |
|
664 var extra = null; |
|
665 if( row.length > 1 ){ |
|
666 extra = []; |
|
667 for (var j=1; j < row.length; j++) { |
|
668 extra[extra.length] = row[j]; |
|
669 } |
|
670 } |
|
671 li.extra = extra; |
|
672 } |
|
673 } |
|
674 |
|
675 if( options.onFindValue ) setTimeout(function() { options.onFindValue(li) }, 1); |
|
676 } |
|
677 |
|
678 function addToCache(q, data) { |
|
679 if (!data || !q || !options.cacheLength) return; |
|
680 if (!cache.length || cache.length > options.cacheLength) { |
|
681 flushCache(); |
|
682 cache.length++; |
|
683 } else if (!cache[q]) { |
|
684 cache.length++; |
|
685 } |
|
686 cache.data[q] = data; |
|
687 }; |
|
688 |
|
689 function findPos(obj) { |
|
690 var curleft = obj.offsetLeft || 0; |
|
691 var curtop = obj.offsetTop || 0; |
|
692 while (obj = obj.offsetParent) { |
|
693 curleft += obj.offsetLeft |
|
694 curtop += obj.offsetTop |
|
695 } |
|
696 return {x:curleft,y:curtop}; |
|
697 } |
|
698 } |
696 } |
699 |
697 } |
700 jQuery.fn.autocomplete = function(url, options, data) { |
698 |
701 // Make sure options exists |
699 jQuery.fn.autocomplete = function(url, options, data) { |
702 options = options || {}; |
700 // Make sure options exists |
703 // Set url as option |
701 options = options || {}; |
704 options.url = url; |
702 // Set url as option |
705 // set some bulk local data |
703 options.url = url; |
706 options.data = ((typeof data == "object") && (data.constructor == Array)) ? data : null; |
704 // set some bulk local data |
707 |
705 options.data = ((typeof data == "object") && (data.constructor == Array)) ? data : null; |
708 // Set default values for required options |
706 |
709 options = $.extend({ |
707 // Set default values for required options |
710 inputClass: "ac_input", |
708 options = $.extend({ |
711 resultsClass: "ac_results", |
709 inputClass: "ac_input", |
712 lineSeparator: "\n", |
710 resultsClass: "ac_results", |
713 cellSeparator: "|", |
711 lineSeparator: "\n", |
714 minChars: 1, |
712 cellSeparator: "|", |
715 delay: 400, |
713 minChars: 1, |
716 matchCase: 0, |
714 delay: 400, |
717 matchSubset: 1, |
715 matchCase: 0, |
718 matchContains: 0, |
716 matchSubset: 1, |
719 cacheLength: false, |
717 matchContains: 0, |
720 mustMatch: 0, |
718 cacheLength: false, |
721 extraParams: {}, |
719 mustMatch: 0, |
722 loadingClass: "ac_loading", |
720 extraParams: {}, |
723 selectFirst: false, |
721 loadingClass: "ac_loading", |
724 selectOnly: false, |
722 selectFirst: false, |
725 maxItemsToShow: -1, |
723 selectOnly: false, |
726 autoFill: false, |
724 maxItemsToShow: -1, |
727 showWhenNoResults: false, |
725 autoFill: false, |
728 width: 0 |
726 showWhenNoResults: false, |
729 }, options); |
727 width: 0 |
730 options.width = parseInt(options.width, 10); |
728 }, options); |
731 |
729 options.width = parseInt(options.width, 10); |
732 this.each(function() { |
730 |
733 var input = this; |
731 this.each(function() { |
734 new jQuery.autocomplete(input, options); |
732 var input = this; |
735 }); |
733 new jQuery.autocomplete(input, options); |
736 |
734 }); |
737 // Don't break the chain |
735 |
738 return this; |
736 // Don't break the chain |
|
737 return this; |
|
738 } |
|
739 |
|
740 jQuery.fn.autocompleteArray = function(data, options) { |
|
741 return this.autocomplete(null, options, data); |
|
742 } |
|
743 |
|
744 jQuery.fn.indexOf = function(e){ |
|
745 for( var i=0; i<this.length; i++ ){ |
|
746 if( this[i] == e ) return i; |
739 } |
747 } |
740 |
748 return -1; |
741 jQuery.fn.autocompleteArray = function(data, options) { |
749 }; |
742 return this.autocomplete(null, options, data); |
750 |
743 } |
751 autofill_onload(); |
744 |
752 }; |
745 jQuery.fn.indexOf = function(e){ |
753 |
746 for( var i=0; i<this.length; i++ ){ |
754 addOnloadHook(autofill_init); |
747 if( this[i] == e ) return i; |
|
748 } |
|
749 return -1; |
|
750 }; |
|
751 |
|
752 autofill_onload(); |
|
753 }); |
|