1(function ($) { 2 3/** 4 * Attaches the autocomplete behavior to all required fields. 5 */ 6Drupal.behaviors.autocomplete = { 7 attach: function (context, settings) { 8 var acdb = []; 9 $('input.autocomplete', context).once('autocomplete', function () { 10 var uri = this.value; 11 if (!acdb[uri]) { 12 acdb[uri] = new Drupal.ACDB(uri); 13 } 14 var $input = $('#' + this.id.substr(0, this.id.length - 13)) 15 .attr('autocomplete', 'OFF') 16 .attr('aria-autocomplete', 'list'); 17 $($input[0].form).submit(Drupal.autocompleteSubmit); 18 $input.parent() 19 .attr('role', 'application') 20 .append($('<span class="element-invisible" aria-live="assertive"></span>') 21 .attr('id', $input.attr('id') + '-autocomplete-aria-live') 22 ); 23 new Drupal.jsAC($input, acdb[uri]); 24 }); 25 } 26}; 27 28/** 29 * Prevents the form from submitting if the suggestions popup is open 30 * and closes the suggestions popup when doing so. 31 */ 32Drupal.autocompleteSubmit = function () { 33 return $('#autocomplete').each(function () { 34 this.owner.hidePopup(); 35 }).length == 0; 36}; 37 38/** 39 * An AutoComplete object. 40 */ 41Drupal.jsAC = function ($input, db) { 42 var ac = this; 43 this.input = $input[0]; 44 this.ariaLive = $('#' + this.input.id + '-autocomplete-aria-live'); 45 this.db = db; 46 47 $input 48 .keydown(function (event) { return ac.onkeydown(this, event); }) 49 .keyup(function (event) { ac.onkeyup(this, event); }) 50 .blur(function () { ac.hidePopup(); ac.db.cancel(); }); 51 52}; 53 54/** 55 * Handler for the "keydown" event. 56 */ 57Drupal.jsAC.prototype.onkeydown = function (input, e) { 58 if (!e) { 59 e = window.event; 60 } 61 switch (e.keyCode) { 62 case 40: // down arrow. 63 this.selectDown(); 64 return false; 65 case 38: // up arrow. 66 this.selectUp(); 67 return false; 68 default: // All other keys. 69 return true; 70 } 71}; 72 73/** 74 * Handler for the "keyup" event. 75 */ 76Drupal.jsAC.prototype.onkeyup = function (input, e) { 77 if (!e) { 78 e = window.event; 79 } 80 switch (e.keyCode) { 81 case 16: // Shift. 82 case 17: // Ctrl. 83 case 18: // Alt. 84 case 20: // Caps lock. 85 case 33: // Page up. 86 case 34: // Page down. 87 case 35: // End. 88 case 36: // Home. 89 case 37: // Left arrow. 90 case 38: // Up arrow. 91 case 39: // Right arrow. 92 case 40: // Down arrow. 93 return true; 94 95 case 9: // Tab. 96 case 13: // Enter. 97 case 27: // Esc. 98 this.hidePopup(e.keyCode); 99 return true; 100 101 default: // All other keys. 102 if (input.value.length > 0 && !input.readOnly) { 103 this.populatePopup(); 104 } 105 else { 106 this.hidePopup(e.keyCode); 107 } 108 return true; 109 } 110}; 111 112/** 113 * Puts the currently highlighted suggestion into the autocomplete field. 114 */ 115Drupal.jsAC.prototype.select = function (node) { 116 this.input.value = $(node).data('autocompleteValue'); 117 $(this.input).trigger('autocompleteSelect', [node]); 118}; 119 120/** 121 * Highlights the next suggestion. 122 */ 123Drupal.jsAC.prototype.selectDown = function () { 124 if (this.selected && this.selected.nextSibling) { 125 this.highlight(this.selected.nextSibling); 126 } 127 else if (this.popup) { 128 var lis = $('li', this.popup); 129 if (lis.length > 0) { 130 this.highlight(lis.get(0)); 131 } 132 } 133}; 134 135/** 136 * Highlights the previous suggestion. 137 */ 138Drupal.jsAC.prototype.selectUp = function () { 139 if (this.selected && this.selected.previousSibling) { 140 this.highlight(this.selected.previousSibling); 141 } 142}; 143 144/** 145 * Highlights a suggestion. 146 */ 147Drupal.jsAC.prototype.highlight = function (node) { 148 if (this.selected) { 149 $(this.selected).removeClass('selected'); 150 } 151 $(node).addClass('selected'); 152 this.selected = node; 153 $(this.ariaLive).html($(this.selected).html()); 154}; 155 156/** 157 * Unhighlights a suggestion. 158 */ 159Drupal.jsAC.prototype.unhighlight = function (node) { 160 $(node).removeClass('selected'); 161 this.selected = false; 162 $(this.ariaLive).empty(); 163}; 164 165/** 166 * Hides the autocomplete suggestions. 167 */ 168Drupal.jsAC.prototype.hidePopup = function (keycode) { 169 // Select item if the right key or mousebutton was pressed. 170 if (this.selected && ((keycode && keycode != 46 && keycode != 8 && keycode != 27) || !keycode)) { 171 this.select(this.selected); 172 } 173 // Hide popup. 174 var popup = this.popup; 175 if (popup) { 176 this.popup = null; 177 $(popup).fadeOut('fast', function () { $(popup).remove(); }); 178 } 179 this.selected = false;
180 $(this.ariaLive).empty(); 181}; 182 183/** 184 * Positions the suggestions popup and starts a search. 185 */ 186Drupal.jsAC.prototype.populatePopup = function () { 187 var $input = $(this.input); 188 var position = $input.position(); 189 // Show popup. 190 if (this.popup) { 191 $(this.popup).remove(); 192 } 193 this.selected = false; 194 this.popup = $('<div id="autocomplete"></div>')[0]; 195 this.popup.owner = this; 196 $(this.popup).css({ 197 top: parseInt(position.top + this.input.offsetHeight, 10) + 'px', 198 left: parseInt(position.left, 10) + 'px', 199 width: $input.innerWidth() + 'px', 200 display: 'none' 201 }); 202 $input.before(this.popup); 203 204 // Do search. 205 this.db.owner = this; 206 this.db.search(this.input.value); 207}; 208 209/** 210 * Fills the suggestion popup with any matches received. 211 */ 212Drupal.jsAC.prototype.found = function (matches) { 213 // If no value in the textfield, do not show the popup. 214 if (!this.input.value.length) { 215 return false; 216 } 217 218 // Prepare matches. 219 var ul = $('<ul></ul>'); 220 var ac = this; 221 for (key in matches) { 222 $('<li></li>') 223 .html($('<div></div>').html(matches[key])) 224 .mousedown(function () { ac.hidePopup(this); }) 225 .mouseover(function () { ac.highlight(this); }) 226 .mouseout(function () { ac.unhighlight(this); }) 227 .data('autocompleteValue', key) 228 .appendTo(ul); 229 } 230 231 // Show popup with matches, if any. 232 if (this.popup) { 233 if (ul.children().length) { 234 $(this.popup).empty().append(ul).show(); 235 $(this.ariaLive).html(Drupal.t('Autocomplete popup')); 236 } 237 else { 238 $(this.popup).css({ visibility: 'hidden' }); 239 this.hidePopup(); 240 } 241 } 242}; 243 244Drupal.jsAC.prototype.setStatus = function (status) { 245 switch (status) { 246 case 'begin': 247 $(this.input).addClass('throbbing'); 248 $(this.ariaLive).html(Drupal.t('Searching for matches...')); 249 break; 250 case 'cancel': 251 case 'error': 252 case 'found': 253 $(this.input).removeClass('throbbing'); 254 break; 255 } 256}; 257 258/** 259 * An AutoComplete DataBase object. 260 */ 261Drupal.ACDB = function (uri) { 262 this.uri = uri; 263 this.delay = 300; 264 this.cache = {}; 265}; 266 267/** 268 * Performs a cached and delayed search. 269 */ 270Drupal.ACDB.prototype.search = function (searchString) { 271 var db = this; 272 this.searchString = searchString; 273 274 // See if this string needs to be searched for anyway. The pattern ../ is 275 // stripped since it may be misinterpreted by the browser. 276 searchString = searchString.replace(/^\s+|\.{2,}\/|\s+$/g, ''); 277 // Skip empty search strings, or search strings ending with a comma, since 278 // that is the separator between search terms. 279 if (searchString.length <= 0 || 280 searchString.charAt(searchString.length - 1) == ',') { 281 return; 282 } 283 284 // See if this key has been searched for before. 285 if (this.cache[searchString]) { 286 return this.owner.found(this.cache[searchString]); 287 } 288 289 // Initiate delayed search. 290 if (this.timer) { 291 clearTimeout(this.timer); 292 } 293 this.timer = setTimeout(function () { 294 db.owner.setStatus('begin'); 295 296 // Ajax GET request for autocompletion. We use Drupal.encodePath instead of 297 // encodeURIComponent to allow autocomplete search terms to contain slashes. 298 $.ajax({ 299 type: 'GET', 300 url: Drupal.sanitizeAjaxUrl(db.uri + '/' + Drupal.encodePath(searchString)), 301 dataType: 'json', 302 jsonp: false, 303 success: function (matches) { 304 if (typeof matches.status == 'undefined' || matches.status != 0) { 305 db.cache[searchString] = matches; 306 // Verify if these are still the matches the user wants to see. 307 if (db.searchString == searchString) { 308 db.owner.found(matches); 309 } 310 db.owner.setStatus('found'); 311 } 312 }, 313 error: function (xmlhttp) { 314 Drupal.displayAjaxError(Drupal.ajaxError(xmlhttp, db.uri)); 315 } 316 }); 317 }, this.delay); 318}; 319 320/** 321 * Cancels the current autocomplete request. 322 */ 323Drupal.ACDB.prototype.cancel = function () { 324 if (this.owner) this.owner.setStatus('cancel'); 325 if (this.timer) clearTimeout(this.timer); 326 this.searchString = ''; 327}; 328 329})(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.