Drupal: Overriding Drupal autocompletion to pass more parameters

Update 2014-02-19: See LittleDynamo’s comment with a link to this StackOverflow answer.
Update 2014-02-11: See Claus’ comment below for a better way to do this.

Drupal autocompletion is easy – just add #autocomplete_path to a Form API element, set it to something that returns a JSON hash, and off you go.

What if you want to pass form values into your autocompletion function so that you can filter results?

Searching, I found some pages that suggested changing the value in the hidden autocomplete field so that it would go to a different URL. However, that probably doesn’t handle the autocomplete cache. Here’s another way to do it:

Drupal.ACDB.prototype.customSearch = function (searchString) {
    searchString = searchString + "/" + $("#otherfield").val();
    return this.search(searchString);
};

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;
  if (this.input.id == 'edit-your-search-field') {
    this.db.customSearch(this.input.value);
  } else {
    this.db.search(this.input.value);
  }
}

Drupal.behaviors.rebindAutocomplete = function(context) {
    // Unbind the behaviors to prevent multiple search handlers
    $("#edit-your-search-field").unbind('keydown').unbind('keyup').unbind('blur').removeClass('autocomplete-processed');
    // Rebind autocompletion with the new code
    Drupal.behaviors.autocomplete(context);
}

You’ll need to use drupal_add_js to add misc/autocomplete.js before you add the Javascript file for your form.

Hope this helps!

2011-08-08 Mon 19:16

  • http://freegeekchicago.org David Eads

    You can also override these methods in a JS file you create yourself as long as it is loaded after misc/autocomplete.js. That’s a safer and definitely more preferred technique.

  • http://sachachua.com Sacha Chua

    Yup, that’s what I did – I put the custom code in a Javascript file that I included after misc/autocomplete.js. =)

  • Will Hartmann

    Thanks. This was very helpful, but to get it to work, I also had to copy Drupal.ACDB.prototype.search() from autocomplete.js into my custom JS file and remove the encodeURIComponent(). Otherwise, the / between the first and second argument gets encoded into /.

    Also, make sure your custom JS file loads after the autocomplete.js by adding a weight to the drupal_add_js().

    $path = drupal_get_path(‘module’, ‘my_module’);
    drupal_add_js($path . ‘/my_module.js’, array(‘weight’ => 20));

    See http://api.drupal.org/api/drupal/includes–common.inc/function/drupal_add_js/7#comment-14514

    • http://sachachua.com Sacha Chua

      Will: Thanks for sharing the missing steps! =)

  • Coen Pelckmans

    I made a few changes for more flexibility (Version: Drupal 7)

    In the custom do search in Drupal.jsAC.prototype.populatePopup
    // Do search
    this.db.owner = this;
    if ($(this.input).attr('custom-autocomplete') == 'autocomplete') {
    this.db.customSearch(this.input.value);
    } else {
    this.db.search(this.input.value);
    }

    Changed the customSearch function
    Drupal.ACDB.prototype.customSearch = function (searchString) {

    // loop through all elements with the filter attribute and add them to the string
    $.each($("[custom-autocomplete=filter]"), function(i,v) {
    var element = $(v);
    searchString = element.val() + "/" + searchString;
    });

    return this.search(searchString);
    };

    Added a few attributes to the elements in my form

    addedform['myFilterElement']['#attributes'] = array( 'custom-autocomplete' => 'filter');
    added form['myAutoCompleteElement']['#attributes'] = array( 'custom-autocomplete' => 'autocomplete');

    checked if the args are set in the right order
    my_autocomplete_path_function(arg1, arg2, arg3, ..., $string) {
    }

    I also made the change like Hartmann said: removed the encodeURIComponent

  • Frippuz

    Great post Sacha!

    If you want to use this to add or replace existing autocomple functionality in a multi value field, you will need a way to separate the form elements containing the custom-autocomplete properties. Without this addition the searches will contain values from ALL the field instances using ‘custom-autocomplete’ => ‘filter’.
    You will for example get a search URL like this: orange/orange/orange/apple/gold instead of just apple/gold

    I solved it like this:
    Add a new #attributes value:

    $form_element['#attributes'] = array ('custom-autocomplete' => 'filter',
    'custom-autocomplete-index' => $index);

    $index can contain anything, just as long as it is different between the field instances. I’ve used the field delta value which works nicely.

    Also add support for this in your jQuery functionality:

    First, change the row: this.db.customSearch(this.input.value);
    to
    this.db.customSearch(this.input.value, index);

    Second, change the customSearch function:


    Drupal.ACDB.prototype.customSearch = function (searchString, index) {
    // loop through all elements with the filter attribute and add them to the string
    jQuery.each(jQuery("[custom-autocomplete=filter]"), function(i,v) {

    var element = jQuery(v);
    // Extra check to manage forms with multiple field deltas
    if (element.attr('custom-autocomplete-index') == index) {
    searchString = element.val() + "/" + searchString;
    }
    });

    return this.search(searchString);
    };

    I hope the can be of use to someone.

    //Frippuz

    • http://sachachua.com Sacha Chua

      Thanks for sharing!

  • Shinto

    Good !!!!! Thanks :)

  • Claus

    Thanks for this post, I referred to it several times and it’s still helpful two and a half year later :-)

    Maybe this helps someone: for a related use case, I overrode the search function in Drupal.ACDB.prototype. By tying the override to the document.ready event I didn’t have to ensure that misc/autocomplete.js got included before my code:

    jQuery(function () {
    if (Drupal && Drupal.ACDB) {
    Drupal.ACDB.prototype.search = function (searchString) {
    /* ... */
    };
    }
    });

    On another note, I found this snippet from your post a little problematic:

    $("#edit-your-search-field").unbind('keydown').unbind('keyup').unbind('blur') ...

    This unbinds all events, not just those added by autocomplete.js. Even if you control all code on the site, it has the potential to break browser plugins and user scripts. Sorry for criticizing it without offering a solution, but I felt a need to advise against that kind of coding :/

    • http://sachachua.com sachac

      That’s a much better way to do it! =D

  • LittleDynamo

    I figured out a way to do this without having to edit Javascript. It involves overriding the textfield theme function. http://stackoverflow.com/questions/6776661/drupal-autocomplete-callback-with-multiple-parameters/#answer-21853762

    • http://sachachua.com sachac

      Great, thanks! =D

    • http://sachachua.com Sacha Chua

      Even better. Thanks for sharing! =D

  • rpwils

    does anyone know how to override the url in Drupal.ACDB.prototype for the entire site?

    • http://sachachua.com sachac

      Hmm, would you do that in a .js file that you add with drupal_add_js on all pages?

  • Rylyn

    Save my time !
    Thanks !

  • Rylyn

    I found use this way can solute drupal core form item #autocomplete_path can’t cross-domain problem.

    Thanks !

    • http://sachachua.com Sacha Chua

      Glad it’s working for you! =)