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