AHAH, Node Forms And Select Lists

Photo of Greg Harvey
Thu, 2009-07-16 09:35By greg

There are a number of decent resources helping you to make your first steps in to AHAH with Drupal 6.x. I won't try and create another start-to-finish tutorial. I just want to highlight a couple of existing resources and raise some specific issues I had (which I found to be undocumented) and pull out some fundamentals so they are more obvious.

Firstly, this page on Drupal.org is by far an away the most useful resource I found:

Secondly, the Form API reference guide's section on #ahah is most handy too:

So before reading those two resources, a very quick overview. I'm doing this, because the more lengthy overviews are a little long-winded and confusing. Here's how you do it:

  1. Define an array of AHAH instructions using FAPI's #ahah - within the array define:
  • 'wrapper' - define the div element we are going to act upon
  • 'method' - how we are going to act on this element - replace, append, etc.
  • 'event' - what is the event *on this form field* that will trigger our AHAH - click, change, blur, etc.
  • 'path' - define a path to a page (yes a page created with hook_menu) which will be responsible for squirting back the new mark-up to be used on the 'wrapper' applying the 'method'
  • There are other documented options, but these are the essentials - RTFM ;-)
  • Define a menu item in your hook_menu() for the page you defined in the 'path' element above
  • Create a callback function (there is a set formula for this, see below) for your menu item
  • So, quick look at the AHAH array on a form item:

    '#ahah' => array(
    'wrapper' => 'edit-body-wrapper',
    // default so not really needed, but included for clarity
    'method' => 'replace',
    'event' => 'change',
    'path' => 'markup_snippets/js',

    What is this saying? Forgetting the context, because it doesn't matter for explanation purposes, basically this says we will act on the HTML

    element in the current form (that's our 'wrapper'), we will replace the contents (the 'method'), we will do so when this form field changes (the 'event') and the page at 'markup_snippets/js' will send us back the mark-up to replace the contents of

    So now you know what the Form API stuff means, you know you have to create a hook_menu() item that has a callback function that will build our returning HTML, so the only complication left is the callback function itself.

    The first link I referenced above should be read carefully at this point. But if you don't get it, don't worry. The fundamental point when looking at the example on that page (where Robert goes through the nine steps in the Poll module's AHAH callback function) is you can ignore steps 1 to 8. They will always be the same when you are working with the node forms, so just copy and paste.

    With two important caveats.

    Firstly, the include path for the node module's admin page include will probably not work. You should replace it with something like this:

    // The form is generated in an include file which we need to include manually.
    // Changed from Poll module example:
    include_once drupal_get_path('module', 'node') . '/node.pages.inc';

    Secondly, one of the comments beneath that article notes that if you are REPLACING elements in the node form then Drupal will lock the node to editing as it will detect "someone else has edited the node". This is not actually the case, but if you see this error then you need to make one small change in step 3, documented here:

    // Step #3 - Preparing to retrieve form from cache
    // Old line - REMOVE THIS:
    // $form_state = array('storage' => NULL, 'submitted' => FALSE);
    // Our replacement to get rid of the error:
    $form_state = array('storage' => NULL, 'rebuild' => TRUE);

    Otherwise steps 1 though 8 are good to go. Your magic happens in step 9, where you build the mark-up to send back to your wrapper, as defined in your form. Imagine it's a theme function because, in essence, that's exactly what it is. In fact, you probably *should* pull it out to a theme function, because then people would be able to override your module's AHAH callback mark-up, which would be neat.

    For comparison purposes, my step 9 in the Mark-up Snippets module I just wrote looks like this:


    // Now we have the latest $form, with the changes made by our
    // AHAH enabled field - we can now build our mark-up to return

    // get the node type info we need for our body field
    $node = $form['#node'];
    $type = node_get_types('type', $node);

    // load the snippet from the posted data
    $mid = $form_state['post']['markup_snippets_select'];
    $snippet = markup_snippets_get_snippets(array(), TRUE, $mid);

    // build the returned mark-up
    // we are mimicking the output of the node_get_body() function
    $output = '';
    $output .= '';
    $output .= '

    $output .= '';
    $output .= '


    // ALL DONE

    And then, of course, don't forget to send your mark-up back:

    // Final rendering callback - send our mark-up back to the div we
    // specified in our AHAH form information
    drupal_json(array('status' => TRUE, 'data' => $output));

    There's not a lot more to say about this, except for one thing that took me a few minutes to work out. In my example my AHAH was attached to a select list and invoked on the change event. There is no "submission" in this case, so the value in $form_state['values'] will not be the value you might expect. It will still be the default value. So where do you get the posted data from so you can see what the changed value of the select list is and react accordingly? The data is available but it's hiding in $form['post']['your_field_name']. Yay!

    You can see my full example in the Mark-up Snippets module here:

    No project page yet, but the code is there and works.

    Finally, though this stuff is all about the node form, the page referenced above also touches on using AHAH on any form, and after playing with the techniques on the node form you should be all set to use AHAH anywhere.

    Happy scripting! =)