views:

3376

answers:

6

Drupal has a very well-architected, jQuery-based autocomplete.js. Usually, you don't have to bother with it, since it's configuration and execution is handled by the Drupal form API.

Now, I need a way to reconfigure it at runtime (with JavaScript, that is). I have a standard drop down select box with a text field next to it, and depending what option is selected in the select box, I need to call different URLs for autocompletion, and for one of the options, autocompletion should be disabled entirely. Is it possible to reconfigure the existing autocomplete instance, or will I have to somehow destroy and recreate?

+1  A: 

it should be as simple as dinamically change the "value" of the "hidden" autocomplete input element that comes aside autocomplete form fields. ie.

$('select#myelement').bind('change', function(e) { 
  if (/* something */) {
    $('input#myelement-autocomplete').attr('value', '/mycustom/path');
  }
});
gpilotino
That does not appear to work. Apparently, the URL is stored somewhere in the JavaScript, and not re-read from the input field.
mikl
+3  A: 

Have a look at misc/autocomplete.js.

/**
 * 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');
  });
};

The input's value attribute is used to create ACDB, which is a cache of values for that autocomplete path (uri). That is used in the Drupal.jsAC function to bind the element's keydown, keyup and blur events with triggers the autocomplete ajax operation (which caches its values in the ACDB object for that element), opens popups, etc.

/**
 * 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(); });

};

What you'll need to do is change the input's value and also reattach the behavior. You'll reattach the behavior by removing the '.autocomplete-processed' class on the autocomplete text field input element and then call Drupal.attachBehaviors(thatInputElement).

This may not work. Things can go very badly if you attach the same behavior to the same element over and over again. It may be more sensible to create different autocomplete fields and simply hide and show them based on the value of the select. This would still require calling Drupal.attachBehaviors when you hide and display the widget, but the same behavior would remain attached if the switch happened more than once, and you wouldn't risk attaching the same behavior to the element multiple times.

bangpound
Well, I by no means a JavaScript expert, but should it not be possible to alter the values in JavaScript instead of the "nuclear" option of destroying and recreating the autocomplete each time?
mikl
as the urls are cached in the abcd[] array, you must reattach the behaviour to update it and if you do that you should unbind() all the previous events bounded to the autocomplete element beforeward.
gpilotino
I've managed to get unbinding and reapplying the behaviour to work, but does that cause the old autocomplete object to be garbage collected? Otherwise, this'll leak memory. It's probably not a big deal, but it is decidedly unpretty :)
mikl
Note that this is pretty much what the location module does - have a look at location_autocomplete.js for the juicy details.
John Fiala
+1  A: 

Well, for reference, I've thrown together a hack that works, but if anyone can think of a better solution, I'd be happy to hear it.

Drupal.behaviors.dingCampaignRules = function () {
  $('#campaign-rules')
    .find('.campaign-rule-wrap')
      .each(function (i) {
          var type = $(this).find('select').val();

          $(this).find('.form-text')
            // Remove the current autocomplete bindings.
            .unbind()
            // And remove the autocomplete class
            .removeClass('form-autocomplete')
          .end()
          .find('select:not(.dingcampaignrules-processed)')
            .addClass('dingcampaignrules-processed')
            .change(Drupal.behaviors.dingCampaignRules)
          .end();

          if (type == 'page' || type == 'library' || type == 'taxonomy') {
            $(this).find('input.autocomplete')
              .removeClass('autocomplete-processed')
              .val(Drupal.settings.dingCampaignRules.autocompleteUrl + type)
            .end()
            .find('.form-text')
              .addClass('form-autocomplete');
            Drupal.behaviors.autocomplete(this);
          }
      });
};

This code comes from the ding_campaign module. Feel free to check out the code if you need to do something similar. It's all GPL2.

mikl
+1  A: 

Working solution for Drupal 5

/*
 *  Błażej Owczarczyk
 *  [email protected] 
 * 
 *  Name: Autocomplete City Taxonomy 
 *  Description: Hierarchical city selecting (province select and city autocomplete)
 */

var Act = Act || {};

Act.init = function () {
    $('select.act-province').change(Act.provinceChange);        // select with top taxonomy terms    
}

/*
 *  Change event of select element
 */
Act.provinceChange = function () { 
    var context = $(this).parent().parent();              
    var currentTid = $(this).val();
    Act.rewriteURI(context, currentTid);
    Act.unbind();
    Drupal.autocompleteAutoAttach();
};

/*
 *  Changes the value of hidden autocomplete input
 */
Act.rewriteURI = function (context, newTid) {
    var tempArray;
    tempArray = $('.autocomplete', context).val().split('/');
    tempArray.pop();
    tempArray.push(newTid);
    $('.autocomplete', context).val(tempArray.join('/'));    
};

/*
 *  Prevents muliple binding of the same events
 */
Act.unbind = function () {
    $('.form-autocomplete').unbind().parents('form').unbind('submit');
};

$(document).ready(Act.init);
blazej
A: 

blazej, your select/autocomplete hierarchical code example worked brilliantly for my project. It saved me a bunch of time. Thanks so much for sharing.

John H
A: 

Has anyone converted blazej's script to Drupal 6?

John H