1 // script.aculo.us controls.js v1.8.0, Tue Nov 06 15:01:40 +0300 2007 |
1 // script.aculo.us controls.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009 |
2 |
2 |
3 // Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) |
3 // Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) |
4 // (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan) |
4 // (c) 2005-2009 Ivan Krstic (http://blogs.law.harvard.edu/ivan) |
5 // (c) 2005-2007 Jon Tirsen (http://www.tirsen.com) |
5 // (c) 2005-2009 Jon Tirsen (http://www.tirsen.com) |
6 // Contributors: |
6 // Contributors: |
7 // Richard Livsey |
7 // Richard Livsey |
8 // Rahul Bhargava |
8 // Rahul Bhargava |
9 // Rob Wills |
9 // Rob Wills |
10 // |
10 // |
11 // script.aculo.us is freely distributable under the terms of an MIT-style license. |
11 // script.aculo.us is freely distributable under the terms of an MIT-style license. |
12 // For details, see the script.aculo.us web site: http://script.aculo.us/ |
12 // For details, see the script.aculo.us web site: http://script.aculo.us/ |
13 |
13 |
14 // Autocompleter.Base handles all the autocompletion functionality |
14 // Autocompleter.Base handles all the autocompletion functionality |
15 // that's independent of the data source for autocompletion. This |
15 // that's independent of the data source for autocompletion. This |
16 // includes drawing the autocompletion menu, observing keyboard |
16 // includes drawing the autocompletion menu, observing keyboard |
17 // and mouse events, and similar. |
17 // and mouse events, and similar. |
18 // |
18 // |
19 // Specific autocompleters need to provide, at the very least, |
19 // Specific autocompleters need to provide, at the very least, |
20 // a getUpdatedChoices function that will be invoked every time |
20 // a getUpdatedChoices function that will be invoked every time |
21 // the text inside the monitored textbox changes. This method |
21 // the text inside the monitored textbox changes. This method |
22 // should get the text for which to provide autocompletion by |
22 // should get the text for which to provide autocompletion by |
23 // invoking this.getToken(), NOT by directly accessing |
23 // invoking this.getToken(), NOT by directly accessing |
24 // this.element.value. This is to allow incremental tokenized |
24 // this.element.value. This is to allow incremental tokenized |
25 // autocompletion. Specific auto-completion logic (AJAX, etc) |
25 // autocompletion. Specific auto-completion logic (AJAX, etc) |
26 // belongs in getUpdatedChoices. |
26 // belongs in getUpdatedChoices. |
30 // in the options parameter, e.g.: |
30 // in the options parameter, e.g.: |
31 // new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' }); |
31 // new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' }); |
32 // will incrementally autocomplete with a comma as the token. |
32 // will incrementally autocomplete with a comma as the token. |
33 // Additionally, ',' in the above example can be replaced with |
33 // Additionally, ',' in the above example can be replaced with |
34 // a token array, e.g. { tokens: [',', '\n'] } which |
34 // a token array, e.g. { tokens: [',', '\n'] } which |
35 // enables autocompletion on multiple tokens. This is most |
35 // enables autocompletion on multiple tokens. This is most |
36 // useful when one of the tokens is \n (a newline), as it |
36 // useful when one of the tokens is \n (a newline), as it |
37 // allows smart autocompletion after linebreaks. |
37 // allows smart autocompletion after linebreaks. |
38 |
38 |
39 if(typeof Effect == 'undefined') |
39 if(typeof Effect == 'undefined') |
40 throw("controls.js requires including script.aculo.us' effects.js library"); |
40 throw("controls.js requires including script.aculo.us' effects.js library"); |
41 |
41 |
42 var Autocompleter = { } |
42 var Autocompleter = { }; |
43 Autocompleter.Base = Class.create({ |
43 Autocompleter.Base = Class.create({ |
44 baseInitialize: function(element, update, options) { |
44 baseInitialize: function(element, update, options) { |
45 element = $(element) |
45 element = $(element); |
46 this.element = element; |
46 this.element = element; |
47 this.update = $(update); |
47 this.update = $(update); |
48 this.hasFocus = false; |
48 this.hasFocus = false; |
49 this.changed = false; |
49 this.changed = false; |
50 this.active = false; |
50 this.active = false; |
51 this.index = 0; |
51 this.index = 0; |
52 this.entryCount = 0; |
52 this.entryCount = 0; |
53 this.oldElementValue = this.element.value; |
53 this.oldElementValue = this.element.value; |
54 |
54 |
55 if(this.setOptions) |
55 if(this.setOptions) |
56 this.setOptions(options); |
56 this.setOptions(options); |
59 |
59 |
60 this.options.paramName = this.options.paramName || this.element.name; |
60 this.options.paramName = this.options.paramName || this.element.name; |
61 this.options.tokens = this.options.tokens || []; |
61 this.options.tokens = this.options.tokens || []; |
62 this.options.frequency = this.options.frequency || 0.4; |
62 this.options.frequency = this.options.frequency || 0.4; |
63 this.options.minChars = this.options.minChars || 1; |
63 this.options.minChars = this.options.minChars || 1; |
64 this.options.onShow = this.options.onShow || |
64 this.options.onShow = this.options.onShow || |
65 function(element, update){ |
65 function(element, update){ |
66 if(!update.style.position || update.style.position=='absolute') { |
66 if(!update.style.position || update.style.position=='absolute') { |
67 update.style.position = 'absolute'; |
67 update.style.position = 'absolute'; |
68 Position.clone(element, update, { |
68 Position.clone(element, update, { |
69 setHeight: false, |
69 setHeight: false, |
70 offsetTop: element.offsetHeight |
70 offsetTop: element.offsetHeight |
71 }); |
71 }); |
72 } |
72 } |
73 Effect.Appear(update,{duration:0.15}); |
73 Effect.Appear(update,{duration:0.15}); |
74 }; |
74 }; |
75 this.options.onHide = this.options.onHide || |
75 this.options.onHide = this.options.onHide || |
76 function(element, update){ new Effect.Fade(update,{duration:0.15}) }; |
76 function(element, update){ new Effect.Fade(update,{duration:0.15}) }; |
77 |
77 |
78 if(typeof(this.options.tokens) == 'string') |
78 if(typeof(this.options.tokens) == 'string') |
79 this.options.tokens = new Array(this.options.tokens); |
79 this.options.tokens = new Array(this.options.tokens); |
80 // Force carriage returns as token delimiters anyway |
80 // Force carriage returns as token delimiters anyway |
81 if (!this.options.tokens.include('\n')) |
81 if (!this.options.tokens.include('\n')) |
82 this.options.tokens.push('\n'); |
82 this.options.tokens.push('\n'); |
83 |
83 |
84 this.observer = null; |
84 this.observer = null; |
85 |
85 |
86 this.element.setAttribute('autocomplete','off'); |
86 this.element.setAttribute('autocomplete','off'); |
87 |
87 |
88 Element.hide(this.update); |
88 Element.hide(this.update); |
89 |
89 |
90 Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this)); |
90 Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this)); |
91 Event.observe(this.element, 'keypress', this.onKeyPress.bindAsEventListener(this)); |
91 Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this)); |
92 }, |
92 }, |
93 |
93 |
94 show: function() { |
94 show: function() { |
95 if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); |
95 if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); |
96 if(!this.iefix && |
96 if(!this.iefix && |
97 (Prototype.Browser.IE) && |
97 (Prototype.Browser.IE) && |
98 (Element.getStyle(this.update, 'position')=='absolute')) { |
98 (Element.getStyle(this.update, 'position')=='absolute')) { |
99 new Insertion.After(this.update, |
99 new Insertion.After(this.update, |
100 '<iframe id="' + this.update.id + '_iefix" '+ |
100 '<iframe id="' + this.update.id + '_iefix" '+ |
101 'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' + |
101 'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' + |
102 'src="javascript:false;" frameborder="0" scrolling="no"></iframe>'); |
102 'src="javascript:false;" frameborder="0" scrolling="no"></iframe>'); |
103 this.iefix = $(this.update.id+'_iefix'); |
103 this.iefix = $(this.update.id+'_iefix'); |
104 } |
104 } |
105 if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50); |
105 if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50); |
106 }, |
106 }, |
107 |
107 |
108 fixIEOverlapping: function() { |
108 fixIEOverlapping: function() { |
109 Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)}); |
109 Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)}); |
110 this.iefix.style.zIndex = 1; |
110 this.iefix.style.zIndex = 1; |
111 this.update.style.zIndex = 2; |
111 this.update.style.zIndex = 2; |
112 Element.show(this.iefix); |
112 Element.show(this.iefix); |
170 this.getUpdatedChoices(); |
170 this.getUpdatedChoices(); |
171 }, |
171 }, |
172 |
172 |
173 onHover: function(event) { |
173 onHover: function(event) { |
174 var element = Event.findElement(event, 'LI'); |
174 var element = Event.findElement(event, 'LI'); |
175 if(this.index != element.autocompleteIndex) |
175 if(this.index != element.autocompleteIndex) |
176 { |
176 { |
177 this.index = element.autocompleteIndex; |
177 this.index = element.autocompleteIndex; |
178 this.render(); |
178 this.render(); |
179 } |
179 } |
180 Event.stop(event); |
180 Event.stop(event); |
181 }, |
181 }, |
182 |
182 |
183 onClick: function(event) { |
183 onClick: function(event) { |
184 var element = Event.findElement(event, 'LI'); |
184 var element = Event.findElement(event, 'LI'); |
185 this.index = element.autocompleteIndex; |
185 this.index = element.autocompleteIndex; |
186 this.selectEntry(); |
186 this.selectEntry(); |
187 this.hide(); |
187 this.hide(); |
188 }, |
188 }, |
189 |
189 |
190 onBlur: function(event) { |
190 onBlur: function(event) { |
191 // needed to make click events working |
191 // needed to make click events working |
192 setTimeout(this.hide.bind(this), 250); |
192 setTimeout(this.hide.bind(this), 250); |
193 this.hasFocus = false; |
193 this.hasFocus = false; |
194 this.active = false; |
194 this.active = false; |
195 }, |
195 }, |
196 |
196 |
197 render: function() { |
197 render: function() { |
198 if(this.entryCount > 0) { |
198 if(this.entryCount > 0) { |
199 for (var i = 0; i < this.entryCount; i++) |
199 for (var i = 0; i < this.entryCount; i++) |
200 this.index==i ? |
200 this.index==i ? |
201 Element.addClassName(this.getEntry(i),"selected") : |
201 Element.addClassName(this.getEntry(i),"selected") : |
202 Element.removeClassName(this.getEntry(i),"selected"); |
202 Element.removeClassName(this.getEntry(i),"selected"); |
203 if(this.hasFocus) { |
203 if(this.hasFocus) { |
204 this.show(); |
204 this.show(); |
205 this.active = true; |
205 this.active = true; |
206 } |
206 } |
207 } else { |
207 } else { |
208 this.active = false; |
208 this.active = false; |
209 this.hide(); |
209 this.hide(); |
210 } |
210 } |
211 }, |
211 }, |
212 |
212 |
213 markPrevious: function() { |
213 markPrevious: function() { |
214 if(this.index > 0) this.index-- |
214 if(this.index > 0) this.index--; |
215 else this.index = this.entryCount-1; |
215 else this.index = this.entryCount-1; |
216 this.getEntry(this.index).scrollIntoView(true); |
216 this.getEntry(this.index).scrollIntoView(true); |
217 }, |
217 }, |
218 |
218 |
219 markNext: function() { |
219 markNext: function() { |
220 if(this.index < this.entryCount-1) this.index++ |
220 if(this.index < this.entryCount-1) this.index++; |
221 else this.index = 0; |
221 else this.index = 0; |
222 this.getEntry(this.index).scrollIntoView(false); |
222 this.getEntry(this.index).scrollIntoView(false); |
223 }, |
223 }, |
224 |
224 |
225 getEntry: function(index) { |
225 getEntry: function(index) { |
226 return this.update.firstChild.childNodes[index]; |
226 return this.update.firstChild.childNodes[index]; |
227 }, |
227 }, |
228 |
228 |
229 getCurrentEntry: function() { |
229 getCurrentEntry: function() { |
230 return this.getEntry(this.index); |
230 return this.getEntry(this.index); |
231 }, |
231 }, |
232 |
232 |
233 selectEntry: function() { |
233 selectEntry: function() { |
234 this.active = false; |
234 this.active = false; |
235 this.updateElement(this.getCurrentEntry()); |
235 this.updateElement(this.getCurrentEntry()); |
236 }, |
236 }, |
237 |
237 |
244 if (this.options.select) { |
244 if (this.options.select) { |
245 var nodes = $(selectedElement).select('.' + this.options.select) || []; |
245 var nodes = $(selectedElement).select('.' + this.options.select) || []; |
246 if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select); |
246 if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select); |
247 } else |
247 } else |
248 value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); |
248 value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); |
249 |
249 |
250 var bounds = this.getTokenBounds(); |
250 var bounds = this.getTokenBounds(); |
251 if (bounds[0] != -1) { |
251 if (bounds[0] != -1) { |
252 var newValue = this.element.value.substr(0, bounds[0]); |
252 var newValue = this.element.value.substr(0, bounds[0]); |
253 var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/); |
253 var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/); |
254 if (whitespace) |
254 if (whitespace) |
269 this.update.innerHTML = choices; |
269 this.update.innerHTML = choices; |
270 Element.cleanWhitespace(this.update); |
270 Element.cleanWhitespace(this.update); |
271 Element.cleanWhitespace(this.update.down()); |
271 Element.cleanWhitespace(this.update.down()); |
272 |
272 |
273 if(this.update.firstChild && this.update.down().childNodes) { |
273 if(this.update.firstChild && this.update.down().childNodes) { |
274 this.entryCount = |
274 this.entryCount = |
275 this.update.down().childNodes.length; |
275 this.update.down().childNodes.length; |
276 for (var i = 0; i < this.entryCount; i++) { |
276 for (var i = 0; i < this.entryCount; i++) { |
277 var entry = this.getEntry(i); |
277 var entry = this.getEntry(i); |
278 entry.autocompleteIndex = i; |
278 entry.autocompleteIndex = i; |
279 this.addObservers(entry); |
279 this.addObservers(entry); |
280 } |
280 } |
281 } else { |
281 } else { |
282 this.entryCount = 0; |
282 this.entryCount = 0; |
283 } |
283 } |
284 |
284 |
285 this.stopIndicator(); |
285 this.stopIndicator(); |
286 this.index = 0; |
286 this.index = 0; |
287 |
287 |
288 if(this.entryCount==1 && this.options.autoSelect) { |
288 if(this.entryCount==1 && this.options.autoSelect) { |
289 this.selectEntry(); |
289 this.selectEntry(); |
290 this.hide(); |
290 this.hide(); |
291 } else { |
291 } else { |
292 this.render(); |
292 this.render(); |
351 this.url = url; |
351 this.url = url; |
352 }, |
352 }, |
353 |
353 |
354 getUpdatedChoices: function() { |
354 getUpdatedChoices: function() { |
355 this.startIndicator(); |
355 this.startIndicator(); |
356 |
356 |
357 var entry = encodeURIComponent(this.options.paramName) + '=' + |
357 var entry = encodeURIComponent(this.options.paramName) + '=' + |
358 encodeURIComponent(this.getToken()); |
358 encodeURIComponent(this.getToken()); |
359 |
359 |
360 this.options.parameters = this.options.callback ? |
360 this.options.parameters = this.options.callback ? |
361 this.options.callback(this.element, entry) : entry; |
361 this.options.callback(this.element, entry) : entry; |
362 |
362 |
363 if(this.options.defaultParams) |
363 if(this.options.defaultParams) |
364 this.options.parameters += '&' + this.options.defaultParams; |
364 this.options.parameters += '&' + this.options.defaultParams; |
365 |
365 |
366 new Ajax.Request(this.url, this.options); |
366 new Ajax.Request(this.url, this.options); |
367 }, |
367 }, |
368 |
368 |
369 onComplete: function(request) { |
369 onComplete: function(request) { |
370 this.updateChoices(request.responseText); |
370 this.updateChoices(request.responseText); |
382 // |
382 // |
383 // Extra local autocompletion options: |
383 // Extra local autocompletion options: |
384 // - choices - How many autocompletion choices to offer |
384 // - choices - How many autocompletion choices to offer |
385 // |
385 // |
386 // - partialSearch - If false, the autocompleter will match entered |
386 // - partialSearch - If false, the autocompleter will match entered |
387 // text only at the beginning of strings in the |
387 // text only at the beginning of strings in the |
388 // autocomplete array. Defaults to true, which will |
388 // autocomplete array. Defaults to true, which will |
389 // match text at the beginning of any *word* in the |
389 // match text at the beginning of any *word* in the |
390 // strings in the autocomplete array. If you want to |
390 // strings in the autocomplete array. If you want to |
391 // search anywhere in the string, additionally set |
391 // search anywhere in the string, additionally set |
392 // the option fullSearch to true (default: off). |
392 // the option fullSearch to true (default: off). |
427 var ret = []; // Beginning matches |
427 var ret = []; // Beginning matches |
428 var partial = []; // Inside matches |
428 var partial = []; // Inside matches |
429 var entry = instance.getToken(); |
429 var entry = instance.getToken(); |
430 var count = 0; |
430 var count = 0; |
431 |
431 |
432 for (var i = 0; i < instance.options.array.length && |
432 for (var i = 0; i < instance.options.array.length && |
433 ret.length < instance.options.choices ; i++) { |
433 ret.length < instance.options.choices ; i++) { |
434 |
434 |
435 var elem = instance.options.array[i]; |
435 var elem = instance.options.array[i]; |
436 var foundPos = instance.options.ignoreCase ? |
436 var foundPos = instance.options.ignoreCase ? |
437 elem.toLowerCase().indexOf(entry.toLowerCase()) : |
437 elem.toLowerCase().indexOf(entry.toLowerCase()) : |
438 elem.indexOf(entry); |
438 elem.indexOf(entry); |
439 |
439 |
440 while (foundPos != -1) { |
440 while (foundPos != -1) { |
441 if (foundPos == 0 && elem.length != entry.length) { |
441 if (foundPos == 0 && elem.length != entry.length) { |
442 ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" + |
442 ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" + |
443 elem.substr(entry.length) + "</li>"); |
443 elem.substr(entry.length) + "</li>"); |
444 break; |
444 break; |
445 } else if (entry.length >= instance.options.partialChars && |
445 } else if (entry.length >= instance.options.partialChars && |
446 instance.options.partialSearch && foundPos != -1) { |
446 instance.options.partialSearch && foundPos != -1) { |
447 if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { |
447 if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { |
448 partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" + |
448 partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" + |
449 elem.substr(foundPos, entry.length) + "</strong>" + elem.substr( |
449 elem.substr(foundPos, entry.length) + "</strong>" + elem.substr( |
450 foundPos + entry.length) + "</li>"); |
450 foundPos + entry.length) + "</li>"); |
451 break; |
451 break; |
452 } |
452 } |
453 } |
453 } |
454 |
454 |
455 foundPos = instance.options.ignoreCase ? |
455 foundPos = instance.options.ignoreCase ? |
456 elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : |
456 elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : |
457 elem.indexOf(entry, foundPos + 1); |
457 elem.indexOf(entry, foundPos + 1); |
458 |
458 |
459 } |
459 } |
460 } |
460 } |
461 if (partial.length) |
461 if (partial.length) |
462 ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)) |
462 ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)); |
463 return "<ul>" + ret.join('') + "</ul>"; |
463 return "<ul>" + ret.join('') + "</ul>"; |
464 } |
464 } |
465 }, options || { }); |
465 }, options || { }); |
466 } |
466 } |
467 }); |
467 }); |
604 this.element.addClassName(this.options.hoverClassName); |
604 this.element.addClassName(this.options.hoverClassName); |
605 if (this._saving) return; |
605 if (this._saving) return; |
606 this.triggerCallback('onEnterHover'); |
606 this.triggerCallback('onEnterHover'); |
607 }, |
607 }, |
608 getText: function() { |
608 getText: function() { |
609 return this.element.innerHTML; |
609 return this.element.innerHTML.unescapeHTML(); |
610 }, |
610 }, |
611 handleAJAXFailure: function(transport) { |
611 handleAJAXFailure: function(transport) { |
612 this.triggerCallback('onFailure', transport); |
612 this.triggerCallback('onFailure', transport); |
613 if (this._oldInnerHTML) { |
613 if (this._oldInnerHTML) { |
614 this.element.innerHTML = this._oldInnerHTML; |
614 this.element.innerHTML = this._oldInnerHTML; |
780 parameters: 'editorId=' + encodeURIComponent(this.element.id), |
780 parameters: 'editorId=' + encodeURIComponent(this.element.id), |
781 onComplete: Prototype.emptyFunction, |
781 onComplete: Prototype.emptyFunction, |
782 onSuccess: function(transport) { |
782 onSuccess: function(transport) { |
783 var js = transport.responseText.strip(); |
783 var js = transport.responseText.strip(); |
784 if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check |
784 if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check |
785 throw 'Server returned an invalid collection representation.'; |
785 throw('Server returned an invalid collection representation.'); |
786 this._collection = eval(js); |
786 this._collection = eval(js); |
787 this.checkForExternalText(); |
787 this.checkForExternalText(); |
788 }.bind(this), |
788 }.bind(this), |
789 onFailure: this.onFailure |
789 onFailure: this.onFailure |
790 }); |
790 }); |
937 |
937 |
938 Ajax.InPlaceCollectionEditor.DefaultOptions = { |
938 Ajax.InPlaceCollectionEditor.DefaultOptions = { |
939 loadingCollectionText: 'Loading options...' |
939 loadingCollectionText: 'Loading options...' |
940 }; |
940 }; |
941 |
941 |
942 // Delayed observer, like Form.Element.Observer, |
942 // Delayed observer, like Form.Element.Observer, |
943 // but waits for delay after last key input |
943 // but waits for delay after last key input |
944 // Ideal for live-search fields |
944 // Ideal for live-search fields |
945 |
945 |
946 Form.Element.DelayedObserver = Class.create({ |
946 Form.Element.DelayedObserver = Class.create({ |
947 initialize: function(element, delay, callback) { |
947 initialize: function(element, delay, callback) { |
948 this.delay = delay || 0.5; |
948 this.delay = delay || 0.5; |
949 this.element = $(element); |
949 this.element = $(element); |
950 this.callback = callback; |
950 this.callback = callback; |
951 this.timer = null; |
951 this.timer = null; |
952 this.lastValue = $F(this.element); |
952 this.lastValue = $F(this.element); |
953 Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this)); |
953 Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this)); |
954 }, |
954 }, |
955 delayedListener: function(event) { |
955 delayedListener: function(event) { |
956 if(this.lastValue == $F(this.element)) return; |
956 if(this.lastValue == $F(this.element)) return; |
957 if(this.timer) clearTimeout(this.timer); |
957 if(this.timer) clearTimeout(this.timer); |