Form API Changes for Drupal 7, Part 2: AJAX/AHAH Changes

Continuing from Form API Changes for Drupal 7, Part 1: $form_state changes.

One of the many pleasant improvements to the Drupal Form API is that AJAX Forms are now really easy. Really easy. We used to call them AHAH forms, and the concept was the same, but they were so complicated that few people attempted them. In D7 we even changed the name to distance ourselves from "AHAH" :-)

In Drupal 6 you had to make a menu entry in hook_menu() that pointed to an AJAX callback handler that was complete black magic. (See a sanitized example.) People did this part differently, and it was hard to figure out the right way. (If you're doing D6 AHAH, the gory details and resources are here.)

In Drupal 7 there is none of that. But we still have to start with the basics so that you'll understand what's going on:

  • The key idea here is that manipulating a form element will make something active happen (usually replacement of a piece of the form) without a full page refresh. For example, changing a select might change the options in the rest of the form, without a page refresh.
  • To use AJAX forms, you don't need to know or use or see any javascript. It's all handled for you by the Form API.
  • To AJAX-enable a form element (to make it do something active when it's manipulated) you just set the #ajax property on it
  • When the form element changes (or is clicked, or whatever) a form submission is done by the client in the background.
  • The server-side code rebuilds the form (in the formbuilder function) in a different way
  • The #ajax['callback'] function chooses what part of the form to replace on the page
  • Replacement HTML is returned to the client javascript code, which replaces a part of the page.

OK, so here's all you have to do to AJAXify a form:

  1. In your form, set #ajax on an element, perhaps a select.
  2. Your formbuilder function should have logic to rebuild differently when the select changes. (See the simple formbuilder in ajax_example_autocheckboxes.)
  3. #ajax['callback'] will point to a (typically simple) function which often only selects and returns the changed piece of the form. (See the tremendously simple ajax_example_autocheckboxes_callback.)

That's basically it. Of course you can do lots and lots more than this, but the basic entry point for AJAX forms is mighty easy.

Submit handler is not called when a non-submit is called

One key change from Drupal 6 to be aware of is that when you activate a non-submit AJAX-enabled form element, the submit handlers are not called. In Drupal 6, submit handlers were called, and a bit of spaghetti code was typically done there.

Drupal 7 AJAX Forms Resources

Key D7 AJAX Open Issues

There are still a few of important open issues for D7 AJAX that you may want to look at:

Next time: Drupal 7 multistep form changes.

11 comments

by Visitor on Tue, 2010-09-14 12:07

I like your article on the new AJAX functionality and it is pretty darn sweet. However I had to overcome two major issues that you didn't mention, issues I think are even more important.

  1. You cannot use #AJAX on settings forms unless you use some trickery and another hook (Let me know if it would be of benefit to share how to do this). An example form would be the Instance Settings Form for a field.

  2. You cannot pass any extra parameters to the ajax callback functions, making it that dynamic callbacks are extremely difficult and more over you have to define one callback per ajax action which can lead to extra, unnecessary code.

Anyways, thanks so much for the articles on D7, they have been and continue to be a good D& source whilst we float in 'The Alpha Zone' ;)

Kevin

by rfay on Tue, 2010-09-14 15:42

Your recipe for system_settings_form() would be appreciated :-)

The callback functions get $form and $form_state, so you can drive them with anything in $form_state['values']. I'm not sure why you'd want to have more than that... But the basic idea is that all logic is to be accomplished in the formbuilder function; logic doesn't belong in the callback.

You should also know that despite the (over)simplification in this article, the callback function can return an array of AJAX commands, so one callback can accomplish many things on the page. Take a look at http://api.drupal.org/api/group/ajax/7.

by Kevin on Thu, 2010-09-16 11:01

Hi-

In regards to the AJAX callback parameters I was actually referring to what I can send to the callback, not from the callback. For example, say I have a list of names, each with a remove button next to it and an add button at the bottom. When a remove button is pressed how do I know which one was pressed and which name to remove if all remove buttons call the same callback? (I ended up looking at the _triggering_element_name and set a unique name).

As for the 'recipe' I will work up a demo but here is the short list :)

First build the form as normal (ex hook_field_settings where we don't have the form_state)
Then use hook_form_alter to capture the form, add the ajax stuff to it and most important REBUILD the form here based on form_states.
Setup your callbacks

The really important piece here is that the form must be rebuilt in hook_form_alter (you can just simply clone the form on the rebuild), by doing this it allows the rebuild functionaliy of the ajax system to work... and presto-chango you have ajax on your settings forms now :)

Ideally I would like to see the form_state on the settings forms in the first place which would negate the hook_form_alter but it works pretty well and is pretty clean and concise as well.

Randy-

I noticed D7a7 is out now, can you speak more on what changes we can no expect in the form/field api's?

Thanks,

Kevin

by rfay on Thu, 2010-09-16 16:20

The callback receives $form_state, which has, among other things, $form_state['triggering_element'], which tells you which button was pressed or element clicked. See http://randyfay.com/node/66.

I hope there won't be any more Form API changes. There are a couple of fixes that are still crucial - Lazy-Loading JS and CSS especially.

by Corbacho on Tue, 2011-03-22 07:38

Randy, I think he was referring to AJAX Links callbacks, not AJAX Forms callbacks. I'm facing exactly the same problem today. See your example, the only parameter that you receive in the callback is $type = 'ajax' or 'nojs'

http://api.drupal.org/api/examples/ajax_example--ajax_example_misc.inc/7...

Do you have any work-around to receive the triggering element? Or the only way is through forms?

THANKS
David

by rfay on Tue, 2011-03-22 07:41

Better to pursue this in the Examples issue queue, where katbailey and company can take a look. I don't think that links even have the concept of triggering_element; they're actually not a part of the form system now, they're a part of the render api.

by Corbacho on Tue, 2011-03-22 08:39

True,
I've opened it http://drupal.org/node/1101492

by Begun on Fri, 2011-11-04 21:14

Nice article. I recently implemented ajax in my form but have encountered a problem. I have an ajax call which runs a database query and then returns the results to display on the form. This works fine for the first button click, but on subsequent clicks it reloads the same results, even if the data in the underlining database has changed. Only way to fix it is to reload the page, then click the button to rerun the ajax call.

It would appear that in forms the ajax results are some how cached. Have you encountered this issue, and do you have any ideas on how to solve it?

by rfay on Fri, 2011-11-04 21:48

It sounds to me like you might be doing a db query in the callback... but all work of this type (with few exceptions) should be done in the form builder function. It's not cached, the entire form is rebuilt for every AJAX call, but if you're changing things, they should be changed in the form builder function. References: My AJAX tutorial, AJAX Example in Examples Project.

by Begun on Sat, 2011-11-05 20:53

It is correct that I am doing a database query in the #ajax callback function. However, I am still confused as to why this does not work, since the database call is a direct call to the database and thus independant of the cached form (at least that is what I would have thought). In all the examples I have seen, the callback returns a form element (e.g. $form['some_element']). But I also understand that the callback can include a string (as it does in my function). As a string I thought it should be independant of the cached form. See the following link to see my current implementation (http://drupal.org/node/1331592).

by Begun on Tue, 2011-11-08 02:09

Finally worked it out, based on your previous comment. Many thanks for pointing me in the right direction.

Drupal theme by Kiwi Themes.