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 += ' – ' + row.pid_highlight; |
54 html += ' – ' + 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); |