webmaster@1
|
1 // $Id: autocomplete.js,v 1.23 2008/01/04 11:53:21 goba Exp $ |
webmaster@1
|
2 |
webmaster@1
|
3 /** |
webmaster@1
|
4 * Attaches the autocomplete behavior to all required fields |
webmaster@1
|
5 */ |
webmaster@1
|
6 Drupal.behaviors.autocomplete = function (context) { |
webmaster@1
|
7 var acdb = []; |
webmaster@1
|
8 $('input.autocomplete:not(.autocomplete-processed)', context).each(function () { |
webmaster@1
|
9 var uri = this.value; |
webmaster@1
|
10 if (!acdb[uri]) { |
webmaster@1
|
11 acdb[uri] = new Drupal.ACDB(uri); |
webmaster@1
|
12 } |
webmaster@1
|
13 var input = $('#' + this.id.substr(0, this.id.length - 13)) |
webmaster@1
|
14 .attr('autocomplete', 'OFF')[0]; |
webmaster@1
|
15 $(input.form).submit(Drupal.autocompleteSubmit); |
webmaster@1
|
16 new Drupal.jsAC(input, acdb[uri]); |
webmaster@1
|
17 $(this).addClass('autocomplete-processed'); |
webmaster@1
|
18 }); |
webmaster@1
|
19 }; |
webmaster@1
|
20 |
webmaster@1
|
21 /** |
webmaster@1
|
22 * Prevents the form from submitting if the suggestions popup is open |
webmaster@1
|
23 * and closes the suggestions popup when doing so. |
webmaster@1
|
24 */ |
webmaster@1
|
25 Drupal.autocompleteSubmit = function () { |
webmaster@1
|
26 return $('#autocomplete').each(function () { |
webmaster@1
|
27 this.owner.hidePopup(); |
webmaster@1
|
28 }).size() == 0; |
webmaster@1
|
29 }; |
webmaster@1
|
30 |
webmaster@1
|
31 /** |
webmaster@1
|
32 * An AutoComplete object |
webmaster@1
|
33 */ |
webmaster@1
|
34 Drupal.jsAC = function (input, db) { |
webmaster@1
|
35 var ac = this; |
webmaster@1
|
36 this.input = input; |
webmaster@1
|
37 this.db = db; |
webmaster@1
|
38 |
webmaster@1
|
39 $(this.input) |
webmaster@1
|
40 .keydown(function (event) { return ac.onkeydown(this, event); }) |
webmaster@1
|
41 .keyup(function (event) { ac.onkeyup(this, event); }) |
webmaster@1
|
42 .blur(function () { ac.hidePopup(); ac.db.cancel(); }); |
webmaster@1
|
43 |
webmaster@1
|
44 }; |
webmaster@1
|
45 |
webmaster@1
|
46 /** |
webmaster@1
|
47 * Handler for the "keydown" event |
webmaster@1
|
48 */ |
webmaster@1
|
49 Drupal.jsAC.prototype.onkeydown = function (input, e) { |
webmaster@1
|
50 if (!e) { |
webmaster@1
|
51 e = window.event; |
webmaster@1
|
52 } |
webmaster@1
|
53 switch (e.keyCode) { |
webmaster@1
|
54 case 40: // down arrow |
webmaster@1
|
55 this.selectDown(); |
webmaster@1
|
56 return false; |
webmaster@1
|
57 case 38: // up arrow |
webmaster@1
|
58 this.selectUp(); |
webmaster@1
|
59 return false; |
webmaster@1
|
60 default: // all other keys |
webmaster@1
|
61 return true; |
webmaster@1
|
62 } |
webmaster@1
|
63 }; |
webmaster@1
|
64 |
webmaster@1
|
65 /** |
webmaster@1
|
66 * Handler for the "keyup" event |
webmaster@1
|
67 */ |
webmaster@1
|
68 Drupal.jsAC.prototype.onkeyup = function (input, e) { |
webmaster@1
|
69 if (!e) { |
webmaster@1
|
70 e = window.event; |
webmaster@1
|
71 } |
webmaster@1
|
72 switch (e.keyCode) { |
webmaster@1
|
73 case 16: // shift |
webmaster@1
|
74 case 17: // ctrl |
webmaster@1
|
75 case 18: // alt |
webmaster@1
|
76 case 20: // caps lock |
webmaster@1
|
77 case 33: // page up |
webmaster@1
|
78 case 34: // page down |
webmaster@1
|
79 case 35: // end |
webmaster@1
|
80 case 36: // home |
webmaster@1
|
81 case 37: // left arrow |
webmaster@1
|
82 case 38: // up arrow |
webmaster@1
|
83 case 39: // right arrow |
webmaster@1
|
84 case 40: // down arrow |
webmaster@1
|
85 return true; |
webmaster@1
|
86 |
webmaster@1
|
87 case 9: // tab |
webmaster@1
|
88 case 13: // enter |
webmaster@1
|
89 case 27: // esc |
webmaster@1
|
90 this.hidePopup(e.keyCode); |
webmaster@1
|
91 return true; |
webmaster@1
|
92 |
webmaster@1
|
93 default: // all other keys |
webmaster@1
|
94 if (input.value.length > 0) |
webmaster@1
|
95 this.populatePopup(); |
webmaster@1
|
96 else |
webmaster@1
|
97 this.hidePopup(e.keyCode); |
webmaster@1
|
98 return true; |
webmaster@1
|
99 } |
webmaster@1
|
100 }; |
webmaster@1
|
101 |
webmaster@1
|
102 /** |
webmaster@1
|
103 * Puts the currently highlighted suggestion into the autocomplete field |
webmaster@1
|
104 */ |
webmaster@1
|
105 Drupal.jsAC.prototype.select = function (node) { |
webmaster@1
|
106 this.input.value = node.autocompleteValue; |
webmaster@1
|
107 }; |
webmaster@1
|
108 |
webmaster@1
|
109 /** |
webmaster@1
|
110 * Highlights the next suggestion |
webmaster@1
|
111 */ |
webmaster@1
|
112 Drupal.jsAC.prototype.selectDown = function () { |
webmaster@1
|
113 if (this.selected && this.selected.nextSibling) { |
webmaster@1
|
114 this.highlight(this.selected.nextSibling); |
webmaster@1
|
115 } |
webmaster@1
|
116 else { |
webmaster@1
|
117 var lis = $('li', this.popup); |
webmaster@1
|
118 if (lis.size() > 0) { |
webmaster@1
|
119 this.highlight(lis.get(0)); |
webmaster@1
|
120 } |
webmaster@1
|
121 } |
webmaster@1
|
122 }; |
webmaster@1
|
123 |
webmaster@1
|
124 /** |
webmaster@1
|
125 * Highlights the previous suggestion |
webmaster@1
|
126 */ |
webmaster@1
|
127 Drupal.jsAC.prototype.selectUp = function () { |
webmaster@1
|
128 if (this.selected && this.selected.previousSibling) { |
webmaster@1
|
129 this.highlight(this.selected.previousSibling); |
webmaster@1
|
130 } |
webmaster@1
|
131 }; |
webmaster@1
|
132 |
webmaster@1
|
133 /** |
webmaster@1
|
134 * Highlights a suggestion |
webmaster@1
|
135 */ |
webmaster@1
|
136 Drupal.jsAC.prototype.highlight = function (node) { |
webmaster@1
|
137 if (this.selected) { |
webmaster@1
|
138 $(this.selected).removeClass('selected'); |
webmaster@1
|
139 } |
webmaster@1
|
140 $(node).addClass('selected'); |
webmaster@1
|
141 this.selected = node; |
webmaster@1
|
142 }; |
webmaster@1
|
143 |
webmaster@1
|
144 /** |
webmaster@1
|
145 * Unhighlights a suggestion |
webmaster@1
|
146 */ |
webmaster@1
|
147 Drupal.jsAC.prototype.unhighlight = function (node) { |
webmaster@1
|
148 $(node).removeClass('selected'); |
webmaster@1
|
149 this.selected = false; |
webmaster@1
|
150 }; |
webmaster@1
|
151 |
webmaster@1
|
152 /** |
webmaster@1
|
153 * Hides the autocomplete suggestions |
webmaster@1
|
154 */ |
webmaster@1
|
155 Drupal.jsAC.prototype.hidePopup = function (keycode) { |
webmaster@1
|
156 // Select item if the right key or mousebutton was pressed |
webmaster@1
|
157 if (this.selected && ((keycode && keycode != 46 && keycode != 8 && keycode != 27) || !keycode)) { |
webmaster@1
|
158 this.input.value = this.selected.autocompleteValue; |
webmaster@1
|
159 } |
webmaster@1
|
160 // Hide popup |
webmaster@1
|
161 var popup = this.popup; |
webmaster@1
|
162 if (popup) { |
webmaster@1
|
163 this.popup = null; |
webmaster@1
|
164 $(popup).fadeOut('fast', function() { $(popup).remove(); }); |
webmaster@1
|
165 } |
webmaster@1
|
166 this.selected = false; |
webmaster@1
|
167 }; |
webmaster@1
|
168 |
webmaster@1
|
169 /** |
webmaster@1
|
170 * Positions the suggestions popup and starts a search |
webmaster@1
|
171 */ |
webmaster@1
|
172 Drupal.jsAC.prototype.populatePopup = function () { |
webmaster@1
|
173 // Show popup |
webmaster@1
|
174 if (this.popup) { |
webmaster@1
|
175 $(this.popup).remove(); |
webmaster@1
|
176 } |
webmaster@1
|
177 this.selected = false; |
webmaster@1
|
178 this.popup = document.createElement('div'); |
webmaster@1
|
179 this.popup.id = 'autocomplete'; |
webmaster@1
|
180 this.popup.owner = this; |
webmaster@1
|
181 $(this.popup).css({ |
webmaster@1
|
182 marginTop: this.input.offsetHeight +'px', |
webmaster@1
|
183 width: (this.input.offsetWidth - 4) +'px', |
webmaster@1
|
184 display: 'none' |
webmaster@1
|
185 }); |
webmaster@1
|
186 $(this.input).before(this.popup); |
webmaster@1
|
187 |
webmaster@1
|
188 // Do search |
webmaster@1
|
189 this.db.owner = this; |
webmaster@1
|
190 this.db.search(this.input.value); |
webmaster@1
|
191 }; |
webmaster@1
|
192 |
webmaster@1
|
193 /** |
webmaster@1
|
194 * Fills the suggestion popup with any matches received |
webmaster@1
|
195 */ |
webmaster@1
|
196 Drupal.jsAC.prototype.found = function (matches) { |
webmaster@1
|
197 // If no value in the textfield, do not show the popup. |
webmaster@1
|
198 if (!this.input.value.length) { |
webmaster@1
|
199 return false; |
webmaster@1
|
200 } |
webmaster@1
|
201 |
webmaster@1
|
202 // Prepare matches |
webmaster@1
|
203 var ul = document.createElement('ul'); |
webmaster@1
|
204 var ac = this; |
webmaster@1
|
205 for (key in matches) { |
webmaster@1
|
206 var li = document.createElement('li'); |
webmaster@1
|
207 $(li) |
webmaster@1
|
208 .html('<div>'+ matches[key] +'</div>') |
webmaster@1
|
209 .mousedown(function () { ac.select(this); }) |
webmaster@1
|
210 .mouseover(function () { ac.highlight(this); }) |
webmaster@1
|
211 .mouseout(function () { ac.unhighlight(this); }); |
webmaster@1
|
212 li.autocompleteValue = key; |
webmaster@1
|
213 $(ul).append(li); |
webmaster@1
|
214 } |
webmaster@1
|
215 |
webmaster@1
|
216 // Show popup with matches, if any |
webmaster@1
|
217 if (this.popup) { |
webmaster@1
|
218 if (ul.childNodes.length > 0) { |
webmaster@1
|
219 $(this.popup).empty().append(ul).show(); |
webmaster@1
|
220 } |
webmaster@1
|
221 else { |
webmaster@1
|
222 $(this.popup).css({visibility: 'hidden'}); |
webmaster@1
|
223 this.hidePopup(); |
webmaster@1
|
224 } |
webmaster@1
|
225 } |
webmaster@1
|
226 }; |
webmaster@1
|
227 |
webmaster@1
|
228 Drupal.jsAC.prototype.setStatus = function (status) { |
webmaster@1
|
229 switch (status) { |
webmaster@1
|
230 case 'begin': |
webmaster@1
|
231 $(this.input).addClass('throbbing'); |
webmaster@1
|
232 break; |
webmaster@1
|
233 case 'cancel': |
webmaster@1
|
234 case 'error': |
webmaster@1
|
235 case 'found': |
webmaster@1
|
236 $(this.input).removeClass('throbbing'); |
webmaster@1
|
237 break; |
webmaster@1
|
238 } |
webmaster@1
|
239 }; |
webmaster@1
|
240 |
webmaster@1
|
241 /** |
webmaster@1
|
242 * An AutoComplete DataBase object |
webmaster@1
|
243 */ |
webmaster@1
|
244 Drupal.ACDB = function (uri) { |
webmaster@1
|
245 this.uri = uri; |
webmaster@1
|
246 this.delay = 300; |
webmaster@1
|
247 this.cache = {}; |
webmaster@1
|
248 }; |
webmaster@1
|
249 |
webmaster@1
|
250 /** |
webmaster@1
|
251 * Performs a cached and delayed search |
webmaster@1
|
252 */ |
webmaster@1
|
253 Drupal.ACDB.prototype.search = function (searchString) { |
webmaster@1
|
254 var db = this; |
webmaster@1
|
255 this.searchString = searchString; |
webmaster@1
|
256 |
webmaster@1
|
257 // See if this key has been searched for before |
webmaster@1
|
258 if (this.cache[searchString]) { |
webmaster@1
|
259 return this.owner.found(this.cache[searchString]); |
webmaster@1
|
260 } |
webmaster@1
|
261 |
webmaster@1
|
262 // Initiate delayed search |
webmaster@1
|
263 if (this.timer) { |
webmaster@1
|
264 clearTimeout(this.timer); |
webmaster@1
|
265 } |
webmaster@1
|
266 this.timer = setTimeout(function() { |
webmaster@1
|
267 db.owner.setStatus('begin'); |
webmaster@1
|
268 |
webmaster@1
|
269 // Ajax GET request for autocompletion |
webmaster@1
|
270 $.ajax({ |
webmaster@1
|
271 type: "GET", |
webmaster@1
|
272 url: db.uri +'/'+ Drupal.encodeURIComponent(searchString), |
webmaster@1
|
273 dataType: 'json', |
webmaster@1
|
274 success: function (matches) { |
webmaster@1
|
275 if (typeof matches['status'] == 'undefined' || matches['status'] != 0) { |
webmaster@1
|
276 db.cache[searchString] = matches; |
webmaster@1
|
277 // Verify if these are still the matches the user wants to see |
webmaster@1
|
278 if (db.searchString == searchString) { |
webmaster@1
|
279 db.owner.found(matches); |
webmaster@1
|
280 } |
webmaster@1
|
281 db.owner.setStatus('found'); |
webmaster@1
|
282 } |
webmaster@1
|
283 }, |
webmaster@1
|
284 error: function (xmlhttp) { |
webmaster@1
|
285 alert(Drupal.ahahError(xmlhttp, db.uri)); |
webmaster@1
|
286 } |
webmaster@1
|
287 }); |
webmaster@1
|
288 }, this.delay); |
webmaster@1
|
289 }; |
webmaster@1
|
290 |
webmaster@1
|
291 /** |
webmaster@1
|
292 * Cancels the current autocomplete request |
webmaster@1
|
293 */ |
webmaster@1
|
294 Drupal.ACDB.prototype.cancel = function() { |
webmaster@1
|
295 if (this.owner) this.owner.setStatus('cancel'); |
webmaster@1
|
296 if (this.timer) clearTimeout(this.timer); |
webmaster@1
|
297 this.searchString = ''; |
webmaster@1
|
298 }; |