Recent Comments

Archives

Categories

Meta

Passing Custom Arguments to Drupal 7 Autocomplete

Recently I was was tasked with adding a Drupal autocomplete whose list to populate from would change based on another filed in a form, specifically a drop down. I wanted to use the build in Drupal autocomplete functionality (no sense in breaking something nice that works) but couldn’t really find documentation on how to manage multiple use cases for a single autocomplete on a form. This is my attempt at fixing that.

The use case in this particular scenario is a custom form that builds a referral. When you select what referral type you want to use, the autocomplete needs to change to only give results for providers that match that type. Please note this is not the best use case. I actually changed this autocomplete to a select menu at the end of the day. But the exercise was fun.

The High-Level Solution

The basic idea behind the solution is that we’re going to use more AJAX in the form to replace the autocomplete field when our type changes. This lets us change the URL that drives the autocomplete to include different arguments in the path, which we can then use in the autocomplete function.

The Code

So we start with the hook_menu setup:

The code for hook_menu

PHP

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

<?php

/**

* Implements hook_menu

*/

functioncustom_forms_menu(){

$items['referral/create']=array(

'page callback'=>'drupal_get_form',

'page arguments'=>array('custom_forms_referral_create'),

'access arguments'=>array('create referral content'),

);

$items['providers/autocomplete/%']=array(

'page callback'=>'custom_forms_providers_autocomplete',

'page arguments'=>array(2),

'access arguments'=>array('create referral content'),

'type'=>MENU_CALLBACK,

);

return$items;

}

?>

Nothing too wild at first glance. The first item is a straightforward call to create a page-based form using the function ‘custom_forms_referral_create’.

There are some changes from the standard straightforward autocomplete in the second item. Notably you have the wildcard in the actual path where your argument will go. Additionally you have the ‘page_arguments’ variable, which is what you’ll use to pass along your data.

So now let’s build the form:

This function builds out the form for the referral

PHP

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

<?php

/**

* Builds the form to create a referral

*/

functioncustom_forms_referral_create(){

$reftypes=get_referral_types();

$type=get_default_referral_type();

$form['referral_type']=array(

'#type'=>'select',

'#title'=>'Select a Referral Type',

'#options'=>$reftypes,

'#default_value'=>$type,

'#ajax'=>array(

'callback'=>'custom_forms_referral_update',

'wrapper'=>'referral_provider',

'progress'=>array(

'message'=>'',

'type'=>'throbber',

),

),

);

$form['provider']=array(

'#type'=>'textfield',

'#title'=>'Select a Provider',

'#description'=>'Please note available providers change based on the referral type.',

'#required'=>TRUE,

'#autocomplete_path'=>'providers/autocomplete/'.$type,

'#prefix'=>'<div id="referral_provider">',

'#suffix'=>'</div>',

);

$form['submit']=array(

'#type'=>'submit',

'#value'=>'Submit',

);

return$form;

}

?>

If you are familiar with the Form API this should be pretty straightforward for you. We’re building out the select field (‘referral_type’) which drives the argument for the autocomplete (‘provider’).

Of particular note is the’#ajax’ in the select field. This sets up a callback that will fire every time the value of the field is changed. If you look, the ‘wrapper’ is set to the same DIV id that we have placed around the autocomplete field using the ‘#prefix’ and ‘#suffix’ values. This can be important, but is not vital for this to work. If this is the only field you’re changing when you fire the callback, then make sure this matches.

Before we check out that AJAX callback let’s take a look the the other AJAX in the room, the autocomplete function:

A function to drive the autocomplete

PHP

1

2

3

4

5

6

7

8

9

10

11

12

13

14

<?php

/**

* Creates the JSON that drives the autocomplete

*/

functioncustom_forms_providers_autocomplete($type,$text){

$selected_providers=array();

$providers=get_providers_of_type($type);

foreach($providersas$nid=>$provider){

if(strpos(strtoupper($provider->title),strtoupper($text))!==FALSE)

$selected_providers[$provider->title.' ('.$nid.')']=$provider->title;

}

drupal_json_output($selected_providers);

}

?>

This is grossly simplified. Usually you’d probably use an Entity Field Query or even just a straight up SQL query to drive this (gotta love the LIKE) instead of a rinky-dink strpos() loop. The important thing to note here is that the custom autocomplete variable you’re passing via the URL is appearing first here, before the standard autocomplete text argument. That threw me off for a little longer than I’d like to admit, so I figured I’d point it out.

Also note that if you’re planning on actually using this value for associations, you’ll need to do some processing on the back end that won’t always end up the way you like since people can ignore autocomplete suggestions and put whatever they like. That’s why ultimately in this use case I went with a second select box.

Finally, let’s take a look at the actual AJAX replacement of the autocomplete field, set up in the callback of the select field:

Credit for this actually comes from this StackExchange answer. Essentially you’re just re-rendering the autocomplete field and replacing it with a version that has the correct argument in the menu path. The argument is being pulled from the value of the select box you have in place, or whatever else you happen to be pulling from.

Note that we don’t have to specify precisely what we’re replacing in this instance. Our ‘wrapper’ that we set in the form’s ‘#ajax’ callback does that.

You can AJAX just about any part of a form this way, whether it’s a property of a field like this or wholesale sections of a form. You just need to be able to manage it on the back end submission. Here’s how you’d change multiple fields on the same form in this callback:

Method to replace a single form field with a newer version with allowance for multiple fields

This solution comes from a Drupal.org dev comment. You can fill out the ‘commands’ array with as many fields as you need, making sure to properly specify the location in the first argument of ajax_command_replace(). This is wholly not dependent on the ‘wrapper’ mentioned before, and you may be able to drop that argument if you use this method though I have not tested it.

Questions and comments on this solution are definitely welcome, as well as alternate solutions entirely. Some of this code falls in some grey areas I have so if you can identify any areas where cleanup would be nice just let me know.