web/drupal/misc/autocomplete.js
branchdrupal
changeset 74 0ff3ba646492
equal deleted inserted replaced
73:fcf75e232c5b 74:0ff3ba646492
       
     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 };