includes/clientside/static/autofill.js
changeset 787 b0d0d429d8cf
parent 779 609e35845ec3
child 1046 cfc6c30c5e2d
equal deleted inserted replaced
786:eafd171f0474 787:b0d0d429d8cf
   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   });