// $Id$ /** * Attaches the autocomplete behavior to all required fields */ Drupal.behaviors.autocomplete = function (context) { var acdb = []; $('input.autocomplete:not(.autocomplete-processed)', context).each(function () { var uri = this.value; if (!acdb[uri]) { acdb[uri] = new Drupal.ACDB(uri); } var input = $('#' + this.id.substr(0, this.id.length - 13)) .attr('autocomplete', 'OFF')[0]; $(input.form).submit(Drupal.autocompleteSubmit); new Drupal.jsAC(input, acdb[uri]); $(this).addClass('autocomplete-processed'); }); }; /** * Prevents the form from submitting if the suggestions popup is open * and closes the suggestions popup when doing so. */ Drupal.autocompleteSubmit = function () { return $('#autocomplete').each(function () { this.owner.hidePopup(); }).size() == 0; }; /** * An AutoComplete object */ Drupal.jsAC = function (input, db) { var ac = this; this.input = input; this.db = db; $(this.input) .keydown(function (event) { return ac.onkeydown(this, event); }) .keyup(function (event) { ac.onkeyup(this, event); }) .blur(function () { ac.hidePopup(); ac.db.cancel(); }); }; /** * Handler for the "keydown" event */ Drupal.jsAC.prototype.onkeydown = function (input, e) { if (!e) { e = window.event; } switch (e.keyCode) { case 40: // down arrow this.selectDown(); return false; case 38: // up arrow this.selectUp(); return false; default: // all other keys return true; } }; /** * Handler for the "keyup" event */ Drupal.jsAC.prototype.onkeyup = function (input, e) { if (!e) { e = window.event; } switch (e.keyCode) { case 16: // shift case 17: // ctrl case 18: // alt case 20: // caps lock case 33: // page up case 34: // page down case 35: // end case 36: // home case 37: // left arrow case 38: // up arrow case 39: // right arrow case 40: // down arrow return true; case 9: // tab case 13: // enter case 27: // esc this.hidePopup(e.keyCode); return true; default: // all other keys if (input.value.length > 0) this.populatePopup(); else this.hidePopup(e.keyCode); return true; } }; /** * Puts the currently highlighted suggestion into the autocomplete field */ Drupal.jsAC.prototype.select = function (node) { this.input.value = node.autocompleteValue; }; /** * Highlights the next suggestion */ Drupal.jsAC.prototype.selectDown = function () { if (this.selected && this.selected.nextSibling) { this.highlight(this.selected.nextSibling); } else { var lis = $('li', this.popup); if (lis.size() > 0) { this.highlight(lis.get(0)); } } }; /** * Highlights the previous suggestion */ Drupal.jsAC.prototype.selectUp = function () { if (this.selected && this.selected.previousSibling) { this.highlight(this.selected.previousSibling); } }; /** * Highlights a suggestion */ Drupal.jsAC.prototype.highlight = function (node) { if (this.selected) { $(this.selected).removeClass('selected'); } $(node).addClass('selected'); this.selected = node; }; /** * Unhighlights a suggestion */ Drupal.jsAC.prototype.unhighlight = function (node) { $(node).removeClass('selected'); this.selected = false; }; /** * Hides the autocomplete suggestions */ Drupal.jsAC.prototype.hidePopup = function (keycode) { // Select item if the right key or mousebutton was pressed if (this.selected && ((keycode && keycode != 46 && keycode != 8 && keycode != 27) || !keycode)) { this.input.value = this.selected.autocompleteValue; } // Hide popup var popup = this.popup; if (popup) { this.popup = null; $(popup).fadeOut('fast', function() { $(popup).remove(); }); } this.selected = false; }; /** * Positions the suggestions popup and starts a search */ Drupal.jsAC.prototype.populatePopup = function () { // Show popup if (this.popup) { $(this.popup).remove(); } this.selected = false; this.popup = document.createElement('div'); this.popup.id = 'autocomplete'; this.popup.owner = this; $(this.popup).css({ marginTop: this.input.offsetHeight +'px', width: (this.input.offsetWidth - 4) +'px', display: 'none' }); $(this.input).before(this.popup); // Do search this.db.owner = this; this.db.search(this.input.value); }; /** * Fills the suggestion popup with any matches received */ Drupal.jsAC.prototype.found = function (matches) { // If no value in the textfield, do not show the popup. if (!this.input.value.length) { return false; } // Prepare matches var ul = document.createElement('ul'); var ac = this; for (key in matches) { var li = document.createElement('li'); $(li) .html('
'+ matches[key] +'
') .mousedown(function () { ac.select(this); }) .mouseover(function () { ac.highlight(this); }) .mouseout(function () { ac.unhighlight(this); }); li.autocompleteValue = key; $(ul).append(li); } // Show popup with matches, if any if (this.popup) { if (ul.childNodes.length > 0) { $(this.popup).empty().append(ul).show(); } else { $(this.popup).css({visibility: 'hidden'}); this.hidePopup(); } } }; Drupal.jsAC.prototype.setStatus = function (status) { switch (status) { case 'begin': $(this.input).addClass('throbbing'); break; case 'cancel': case 'error': case 'found': $(this.input).removeClass('throbbing'); break; } }; /** * An AutoComplete DataBase object */ Drupal.ACDB = function (uri) { this.uri = uri; this.delay = 300; this.cache = {}; }; /** * Performs a cached and delayed search */ Drupal.ACDB.prototype.search = function (searchString) { var db = this; this.searchString = searchString; // See if this key has been searched for before if (this.cache[searchString]) { return this.owner.found(this.cache[searchString]); } // Initiate delayed search if (this.timer) { clearTimeout(this.timer); } this.timer = setTimeout(function() { db.owner.setStatus('begin'); // Ajax GET request for autocompletion $.ajax({ type: "GET", url: db.uri +'/'+ Drupal.encodeURIComponent(searchString), dataType: 'json', success: function (matches) { if (typeof matches['status'] == 'undefined' || matches['status'] != 0) { db.cache[searchString] = matches; // Verify if these are still the matches the user wants to see if (db.searchString == searchString) { db.owner.found(matches); } db.owner.setStatus('found'); } }, error: function (xmlhttp) { alert(Drupal.t("An HTTP error @status occurred. \n@uri", { '@status': xmlhttp.status, '@uri': db.uri })); } }); }, this.delay); }; /** * Cancels the current autocomplete request */ Drupal.ACDB.prototype.cancel = function() { if (this.owner) this.owner.setStatus('cancel'); if (this.timer) clearTimeout(this.timer); this.searchString = ''; };