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