Building custom frontend autocompletes using Drupal

12 Oct 2014

Our tech blog is English, so the whole world can enjoy the drill :)

Once to often I’ve built an autocomplete function without using the Drupal core. But why should we reinvent the wheel when the Drupal core offers you exactly what you need. I’ve searched and found many backend tutorials but there where no clear frontend versions.

The first and most important step for using a functionality is reading through the existing code to see which parts we need. In this case all the code we need to use is found in the “Drupal.behaviors.autocomplete” which is found in the autocomplete.js located in the misc folder in your Drupal root directory. I’ve taken the liberty to add a few comments to split the code into readable pieces.

Drupal.behaviors.autocomplete = {  attach: function (context, settings) {    // For storing autocomplete database objects.    var acdb = [];    // Only trigger once.    $('input.autocomplete', context).once('autocomplete', function () {      // Create new autocomplete database object if not in array.      var uri = this.value;      if (!acdb[uri]) {        acdb[uri] = new Drupal.ACDB(uri);      }      // Add required attributes to autocomplete input.      var $input = $('#' + this.id.substr(0, this.id.length - 13))        .attr('autocomplete', 'OFF')        .attr('aria-autocomplete', 'list');      // Attach autocomplete submit (this hides the popup).      $($input[0].form).submit(Drupal.autocompleteSubmit);      // Attach attributes to parent and append span for the autocomplete.      $input.parent()        .attr('role', 'application')        .append($('')          .attr('id', $input.attr('id') + '-autocomplete-aria-live')        );      // Create autocomplete AutoComplete object.      new Drupal.jsAC($input, acdb[uri]);    });  }};

The autocomplete behavior has only a few lines of code and isn’t very complicated. It uses an array to store autocomplete database objects. This array is filled using all input elements with the class ‘autocomplete’. After this step the input element receives a few attributes and the autocomplete submit function, which hides the popup. The last few lines add a few attributes to the parent element and append a span with an id. This span is used to the match the autocomplete input element. After all these lines the ‘jsAC’ or ‘AutoComplete object’ is created. You now have a fully operational autocomplete object, which triggers on keydown, keyup and blur.

Now that we’ve read the code we can get down to the nitty gritty. Unless you want to apply the autocomplete to all input elements with a certain class I suggest we create a function to attach the autocomplete to an input element. This is also a piece of functionality you might want to recycle.

To keep things simple I’ve chosen to use one fixed path for our autocomplete.

(function ($) {var acdb = [];  /**   * Custom function to attach drupal autocomplete behaviour to a custom textfield.   * @param target   *  The object which should get the search behaviour.   */  function mymodule_attach_search(target) {    var uri = Drupal.settings.basePath + 'some_autocomplete_path';    if (!acdb[uri]) {      acdb[uri] = new Drupal.ACDB(uri);    }    // Append class for throbber.    if (!target.hasClass('form-autocomplete')) {      target.addClass('form-autocomplete');    }    // Just in case remove any previously attached behaviours.    target.unbind();    target.attr('autocomplete', 'OFF')      .attr('aria-autocomplete', 'list');    $(target[0].form).submit(Drupal.autocompleteSubmit);    target.parent()      .attr('role', 'application')      .append($('')        .attr('id', target[0].id + '-autocomplete-aria-live')      );    new Drupal.jsAC(target, acdb[uri]);  }})(jQuery);

The function accepts a target (jQuery(‘input.someclass’)) and binds the autocomplete functionality to it. The fixed uri can be replaced by a function parameter. This function also adds the ‘form-autocomplete’ class, which has the throbber. I’ve also added the unbind function, so the autocomplete can be re-attached. (This can be used when making conditional autocompletes)

In order to make this work we still need to add a few system libraries and our custom Javascript file. This can be done on a custom page callback or in the preprocessing of a page / node.

  drupal_add_library('system', 'drupal.autocomplete');  drupal_add_library('system', 'ui.autocomplete');  drupal_add_js('/path/to/the/js_file/somefile.js');

Now the Javascript side is done and we can start building the server side code. This is done using a menu hook and an autocomplete callback function.

/** * Implements hook_menu(). */function mymodule_menu() {  $items = array();  $items['some_autocomplete_path/%'] = array(    'page callback'    => '_mymodule_autocomplete',    'access arguments' => array('access content'),    'page arguments'   => array(1),  );  return $items;}/** * Custom autocomplete. * @param string $string *   The item the user is looking for. */function _mymodule_autocomplete($string) {  // Make certain that we have a safe search value.  $search = filter_xss($string);  // Initialize array.  $matches = array();  $result = db_select('node', 'n')    ->fields('n', array('nid', 'title'))    ->condition('n.title', db_like($search) . '%', 'LIKE')    // Add order and limit to 10.    ->orderBy('n.created', 'DESC')    ->range(0, 10)    ->execute();  // Build array for autocomplete.  while ($match = $result->fetchObject()) {    $matches[$match->title . '[nid:' . $match->nid . ']'] = $match->title;  }  // Return for JS.  return drupal_json_output($matches);}

The menu path for this example requires one parameter, which is passed to the private function _mymodule_autocomplete. The search string is filtered using the XSS filter. This prevents anything nasty from being entered.
The query itself is just a simple select on node title. The results are added to an array where the key contains the title and de node id in the format Drupal uses.
( title [nid:123] ) If you don’t need the node id you might want to remove it.

Comments

Nóg meer
kennis nodig?

Check ons ons blog archief >