First, you'll want to experiment with the AHAH Example in the Examples Project. It's probably easier to start with that code than it is to understand this page.

For an understanding about what AHAH is, and more specifically, what AHAH in Drupal is, please read the Introduction to AHAH in Drupal before reading this section. The Introduction to AHAH in Drupal describes what you need to do in the $form array to add dynamic form elements. Here we will explain the next steps of writing the callback and submit handler without violating Form API security.

1. Understanding how your AHAH form should work

Once you have attached an #ahah binding to a form element, there is a pretty strict set-up involved in getting it to work according to best practices. Here is an overview of the steps:

  • Add code to your main function for building the form to get data from $form_state. This will allow user-submitted data to be used to define the form elements and values in addition to using data already existing in the database.
  • Write an AHAH callback function, which will be called by your #ahah binding. This function will retrieve the form from the cache and go through the process of rebuilding it, which includes calling submit handlers.
  • Write a submit handler for the element your #ahah behaviour is bound to. The handler must retain user-submitted information in $form_state, which will then be used in the rebuilding of the form. This handler will be called during the execution of your callback.

Many people, when they first start trying to build an AHAH-enabled form, make the mistake of using the AHAH callback to make changes to the form, e.g. adding in a new element or altering an existing one. This is NOT what the AHAH callback is for. The callback simply retrieves, processes and rebuilds the form, then returns whatever portion of the form that needs to be re-rendered, replacing the originally rendered version of that portion.

So, how does your form actually get changed?
"Changes" to your form essentially translate as "different ways that your form can be built, depending on what's in $form_state". What this means is that neither the AHAH callback nor the submit handler return form elements (either new or modified) to the form builder; they send information via $form_state that the form builder can use to add or modify new form elements.
If you can grasp that, you are 90% of the way to nailing AHAH in Drupal.

2. The code you need to achieve this

There are essentially two tricks beyond simply creating your form that you must follow for AHAH to work smoothly and soundly while reducing security holes.

First of all, the form you see on screen and the $form array stored in the cache should always be the same.
Second, the form building function should create new form elements based on the contents of $form_state, which is populated in the form's submit handler, and recreate the whole form each time based only on $form_state and data already existing in the database. This function should not use data from $_POST or similar to build the form.

As an example of what should be at the beginning of your form-building function, here is a small excerpt from the top of the function node_form in node.pages.inc:

if (isset($form_state['node'])) {
  $node = $form_state['node'] + (array)$node;
}

While that may seem complex and hard to understand, it's really quite simple: we pretend that the user submitted data is already saved in the database and simply show the form that you will get the next time you edit the node.

If you follow the two tricks above, a nice cycle emerges:

  1. The form generator creates the form.
  2. It gets cached and rendered.
  3. A user action triggers your callback.
  4. You retrieve the form from the cache.
  5. You process it with drupal_process_form. This function calls the submit handlers, which put whatever was worthy of keeping into $form_state.
  6. You call drupal_rebuild_form which destroys $_POST.
  7. The form generator function is called and creates the form again but since it knows to use $form_state, the form will be different.
  8. The new form gets cached and processed again, but because $_POST is destroyed, the submit handlers will not be called again.
  9. Your AHAH callback picks a piece of the form and renders it.

This cycle leads to good and secure code, and also AHAH Zen.

Here's the callback function from poll.module*, an example of AHAH done right.

function poll_choice_js() {
  // The form is generated in an include file which we need to include manually.
  include_once 'modules/node/node.pages.inc';
  // We're starting in step #3, preparing for #4.
  $form_state = array('storage' => NULL, 'submitted' => FALSE);
  $form_build_id = $_POST['form_build_id'];
  // Step #4.
  $form = form_get_cache($form_build_id, $form_state);

  // Preparing for #5.
  $args = $form['#parameters'];
  $form_id = array_shift($args);
  $form_state['post'] = $form['#post'] = $_POST;
  $form['#programmed'] = $form['#redirect'] = FALSE;

  // Step #5.
  drupal_process_form($form_id, $form, $form_state);
  // Step #6 and #7 and #8.
  $form = drupal_rebuild_form($form_id, $form_state, $args, $form_build_id);

  // Step #9.
  $choice_form = $form['choice_wrapper']['choice'];
  unset($choice_form['#prefix'], $choice_form['#suffix']);
  $output = theme('status_messages') . drupal_render($choice_form);

  // Final rendering callback.
  drupal_json(array('status' => TRUE, 'data' => $output));
}

Up to and including drupal_rebuild_form your code should be the very same. Yes, it should be a utility function, and it is in D7.

The following example, which comes from quicktabs.module, shows how the above works for non-node forms. The quicktabs admin form allows you to create blocks of tabbed content, choosing either an existing block or a view for each tab. There are three #ahah elements in this form: a button allowing you to add an extra tab to the form (essentially an extra set of elements); a button allowing you to remove a tab from the form; and a views dropdown which, when changed, instantly updates the view display options in another dropdown.

Below is the submit handler for the "add tab" button, followed by the AHAH callback for all three AHAH elements:

/** 
 * Submit handler for the "Add Tab" button.
 */ 
function qt_more_tabs_submit($form, &$form_state) {
  unset($form_state['submit_handlers']);
  form_execute_handlers('submit', $form, $form_state);
  $quicktabs = $form_state['values'];
  $form_state['quicktabs'] = $quicktabs;
  $form_state['rebuild'] = TRUE;
  if ($form_state['values']['tabs_more']) {
    $form_state['qt_count'] = count($form_state['values']['tabs']) + 1;
  }
  return $quicktabs;
}

/** 
 * AHAH callback.
 */ 
function quicktabs_ahah() {
  $form_state = array('storage' => NULL, 'submitted' => FALSE);
  $form_build_id = $_POST['form_build_id'];
  $form = form_get_cache($form_build_id, $form_state);
  $args = $form['#parameters'];
  $form_id = array_shift($args);
  $form['#post'] = $_POST;
  $form['#redirect'] = FALSE;
  $form['#programmed'] = FALSE;
  $form_state['post'] = $_POST;
  drupal_process_form($form_id, $form, $form_state);
  $form = drupal_rebuild_form($form_id, $form_state, $args, $form_build_id);
  $qt_form = $form['qt_wrapper']['tabs'];
  unset($qt_form['#prefix'], $qt_form['#suffix']); // Prevent duplicate wrappers.
  $javascript = drupal_add_js(NULL, NULL, 'header');
  drupal_json(array(
    'status'   => TRUE,
    'data'     => theme('status_messages') . drupal_render($qt_form),
    'settings' => call_user_func_array('array_merge_recursive', $javascript['setting']),
  ));
}

Here we don't need to include modules/node/node.pages.inc to access $form, because the function that generated the form is in quicktabs.module itself. Notice that the change is being made to the form in the submit handler (incrementing the number of tabs by 1), which is called from drupal_process_form. For this to work, the function generating the form needs to react to $form_state:

  // if the form is being generated from an ahah callback, $form_state['quicktabs'] will
  // contain the posted values of the form - if it's an edit form, the contents of  
  // $quicktabs will be coming from the database
  if (isset($form_state['quicktabs'])) {
    $quicktabs = $form_state['quicktabs'] + (array)$quicktabs;
  }

  // how many sets of tab elements do we want to generate - check $form_state first
  if (isset($form_state['qt_count'])) {
    $qt_count = $form_state['qt_count'];
  }
  else {
    $qt_count = max(2, empty($tabcontent) ? 2 : count($tabcontent));
  }

Again by stating $quicktabs = $form_state['quicktabs'] + (array)$quicktabs; we pretend that the user submitted data is already saved.

Finally, here is the submit handler for the views dropdown:

/** 
 * Submit handler for the Views drop down.
 */ 
function qt_get_displays_submit($form, &$form_state) {
  unset($form_state['submit_handlers']);
  form_execute_handlers('submit', $form, $form_state);
  $quicktabs = $form_state['values'];
  $form_state['quicktabs'] = $quicktabs;
  $form_state['rebuild'] = TRUE;
  return $quicktabs;
}

Notice that it doesn't seem to make any change to the form at all - the form is simply going to be rebuilt but with a different selected value for this dropdown, and that will change the options in the display dropdown. The AHAH callback for this is the same callback used for the remove button, it just has a different submit handler.

One other thing worth mentioning about the AHAH callback is the use of the $javascript array and overriding Drupal.ahah.prototype.success in ahah_helper.js. This trick comes from Wim Leers' AHAH Helper module and enables the re-attachment of AHAH behaviors to the AHAH-generated form items. Normal jQuery behaviors get re-attached anyway if they are used within Drupal.behaviors, but AHAH behaviors only get attached to elements specified in Drupal.settings.ahah. This trick is not necessary for poll.module because the form elements themselves (textfields for entering poll choices) don't have any AHAH functionality, but in the case of Quick Tabs, a new Views dropdown is being included with each new set of elements that's added via the "Add tab" button and this needs an AHAH behavior attached to it.

To see the full code, visit http://drupal.org/project/quicktabs and download the quicktabs-6.x-2.0-rc1 release. To see the form in action, click here. You will need to change the tab type to "View" in order to see the views display dropdown working.

* The code shown from poll.module is from the D7 version; the ahah callback has not yet been changed in D6. Patch here.

Caution With File Uploads

Using something similar to the above with the 'file' type didn't work. It does work well to add fields, but you have a file to upload, the browser (any old browser) throws up the notorious "HTTP error 0" message.

One of the potential issues is detailed here:
http://drupal.org/node/399676

In a nutshell, the code here works perfectly, unless the field type is 'file'

Comments

randallknutson’s picture

I have the example above copied almost line for line except step 9.

When I do the ahah submit the first time, I get the following message without the return value I specified:
Page test has been updated.

Then, every submit after that I get the return value and the message:
This content has been modified by another user, changes cannot be saved.

Any idea what I could be doing wrong?

Also, the ahah bits are part of a custom CCK widget. Don't know if that matters.

randallknutson’s picture

Okay so I figured it out. In step #3, setting 'submitted' => FALSE does not work. Instead, I set 'rebuild' => TRUE.

  $form_state = array('storage' => NULL, 'rebuild' => TRUE);

This causes my forms to rebuild only, not submit.

When I looked at the drupal_process_form, it checks to see if 'submitted' is not empty. By setting 'submitted' => FALSE, it is no longer empty so the function thinks it is a submit. This is probably a bug.

miiimooo’s picture

I tried this but now it doesn't change the form through the callback. Looks like I'm stuck with

Page XYZ has been updated.
This content has been modified by another user, changes cannot be saved.
An illegal choice has been detected. Please contact the site administrator.

Any ideas?

neilt17’s picture

The issue is that on the second time you save a node form with ahah, node_validate() will throw up an error, because the node changed time stored on the form is now earlier than the last time the node was saved (the first time the form was saved). Which of the many places on $form and $form_state where this value is stored is relevant was a source of a number of frustrating hours! But this post gave me the clue.

It turns out that it is only the value of the changed time contained in $_POST that matters. So in my solution, when a user saves their node using a ahah button, the new last changed node time on a hidden input element is sent back, which can then be used on the next save:

Add a hidden input element to $output in step 9:

  // Step #9...
  $output .= '<input type="hidden" name="myahah_node_changed" id="edit-myahah-node-changed" value="' . node_last_changed($form['#node']->nid) . '">';

In preparation for step 5, pick up this value:

// Preparing for #5.
  $args = $form['#parameters'];
  $form_id = array_shift($args);
  $form_state['post'] = $form['#post'] = $_POST;
  $form['#programmed'] = $form['#redirect'] = FALSE;

  // Add these lines to get the new node changed time
   if (isset($form_state['post']['myahah_node_changed'])) {
     $form['#post']['changed'] = $form_state['post']['myahah_node_changed'];
   }
faboulaws’s picture

This helped solve a very similar problem that i had.
Question: do you know how i can rebuild the form while applying form_alter_hook?

burningdog’s picture

Say you've defined your own content type and you'd like to manipulate an element on the node add form using AHAH. In my case, I want to change the value in a textfield based on the value of another element.

The process is:
1. Create a CCK field whose value you want to change.
2. Force drupal to cache your form (in hook_form_alter)
3. Create your menu item (in hook_menu)
4. Link to the menu url in your ahah property of the element you want to trigger the change
5. Write a function to change the value of the element, and make sure that gets stored in $form

Say that we want to change the value of field_flowers to "roses" when a button is clicked (yes, it's a pretty arbitrary example, but the logic will apply for changing the value of any other form element based on an AHAH event). Fleshing out each of the above steps:

1. Create a CCK field whose value you want to change.
In admin/content/types choose the CCK content type you want to work with, and add a text field - call it field_flower. We'll be assigning it the value of 'roses' later.

2. Force drupal to cache your form (in hook_form_alter) by putting a $form['#cache'] = TRUE; in the relevant spot, like so:

function mymodule_form_alter(&$form, $form_state, $form_id) {
  switch ($form_id) {
    case 'my_cck_type_node_form':
      $form['#cache'] = TRUE;
      break;
  }
}

We force drupal to cache the form because it doesn't for a node add form. Once we've clicked on the element which triggers the change, we need to access $form and change some things within it - and we can only access $form if it's cached.

3. Create your menu item (in hook_menu)

 $items['ahah-change-flower'] = array(
    'page callback'    => 'mymodule_change_flower',
    'access callback'  => TRUE,
    'type'             => MENU_CALLBACK,
  );

4. Link to the menu url in your ahah property of the element you want to trigger the change

In your hook_form_alter() function, add the following (after the $form['#cache'] = TRUE; line):

$form['field_my_button'] = array(
        '#type' => 'button',
        '#value' => t('Insert new flower'),
        '#ahah' => array(
          'event' => 'click',
          'path'    => 'ahah-change-flower',
          'wrapper' => 'edit-field-flower-nid-nid-wrapper',
          'method' => 'replace',
        ),
      );

In this case we've added AHAH to a new button (called 'field_my_button') and asked it to load up the url 'ahah-change-flower' (which is loaded in the background because that's the nature of AHAH - it's not a full page reload). Once that url is requested drupal calls the function mymodule_change_flower() - which is our next step.

Note that the value of 'wrapper' is the id of the div which field_flower sits in. To find the value of that particular id, view the source code of your form and look for field_flower. Firebug is your friend!

5. Write a function to change the value of the element, and make sure that gets stored in $form

Somewhere in your module, add this:

function mymodule_change_flower() {
  // The form is generated in an include file which we need to include manually.
  include_once 'modules/node/node.pages.inc';
  
  // We're starting in step #3, preparing for #4.
  $form_state = array('storage' => NULL, 'submitted' => FALSE);
  $form_build_id = $_POST['form_build_id'];
  // Step #4.
  $form = form_get_cache($form_build_id, $form_state);

  // Preparing for #5.
  $args = $form['#parameters'];
  $form_id = array_shift($args);
  $form_id = $args;
  $form_state['post'] = $form['#post'] = $_POST;
  $form['#programmed'] = $form['#redirect'] = FALSE;

  // Step #5.
  //drupal_process_form($form_id, $form, $form_state);
  
  // Step #6 and #7 and #8.
  // $form = drupal_rebuild_form($form_id, $form_state, $args, $form_build_id);
  
  //change your element here
  $form['field_flower']['#value'] = 'roses';
  
  form_set_cache($form_build_id, $form, $form_state);
  
  $form += array(
    '#post' => $_POST,
    '#programmed' => FALSE,
  );
  
  $form = form_builder('node_form', $form, $form_state);
  
  // Step #9.
  $subform = $form['field_flower'];
  unset($choice_form['#prefix'], $choice_form['#suffix']);
  $output = theme('status_messages') . drupal_render($subform);
  
  // Final rendering callback.
  drupal_json(array('status' => TRUE, 'data' => $output));
}

Note how similar this code is to the 9 steps above, yet also different. Firstly, since we're adding or editing a content type, we need to include the relevant form generating function, which is in node.pages.inc (the function is called node_form() if you're interested).

Note that step 5 and step 6/7/8 are commented out - we don't need them. I found that executing these is unnecessary and actually removes the values I want to access in $form. Simply retrieving $form from the cache is enough to make the changes; then save $form to the cache again, rebuild the form, and render it.

This code was taken from Wim Leers post on his AHAH helper module, about why AHAH in drupal is so hard.

6. Summary
So what we've done is set field_flower to the value of "roses" when a button is clicked. Equally, we could change the value of field_flower if we were change the value of a select list, by attaching the #ahah property to the relevant select list.

This logic will work for most CCK field types, but it doesn't work for what I originally researched this for: changing the values in a node reference select list when another element's values change. I hope to find a solution for that soon!

katbailey’s picture

Hi burningdog - unfortunately the method you're using here is actually the "old" way, and it goes against the best practices described on this page. I'm pretty sure Wim Leers's module no longer uses this method.
I have added a new introduction to the top of this page which I hope will clarify things somewhat.

burningdog’s picture

@katbailey: you're right, I'm using the old way, and it goes against the best practices you've outlined here. Unfortunately, the context I need to use this in is on a node edit page, re-populating the values of a CCK nodereference select list, and there is no other AHAH solution.

There are 2 problems for this use case: the form building function is node_form() in modules/node/node.pages.inc and I'm using a CCK noderef element.

For the former, I can't put in custom code into node_form() and make it react to $form_state just how I want it to, and for the latter, well, there's just no way to do it!

In other words, I should probably use a more elegant way of changing my available options in the noderef select (i.e. by using jQuery to remove options), but given that there's no way of using AHAH to do what I want it to, although my hack is incredibly nasty, it works (as long as the values of the noderef elements aren't changed in the process - they're just removed).

I noticed that in a comment on your excellent post on AHAH on your site you said that there needs to be a CCK AHAH initiative - did you find out if anyone is working on it?

rsantiag’s picture

Hi Roger,

I'm just now dealing with AHAH and CCK select list, but all the documentation I found it's not enought to solve that issue.

Did you find something in this way?

Thanks in advance

burningdog’s picture

rsantiag: Nope - as far as I can see, there's no way to use AHAH to repopulate a CCK nodereference select list. My example that I give above is wrong, since it uses the old way (I can't edit it, unfortunately - don't know why). I've asked katbailey if she knows of anyone working on a CCK + AHAH initiative (see above).

WildKitten’s picture

Hi Roger,

thanks for this tutorial. It works great! I tried with select list instead of button, and it worked. But, like you, I wasn't able to change the field_flower when it is CCK select field. Have you found some solution for that?

burningdog’s picture

No, I haven't - see the problems that such a solution will need to solve: http://drupal.org/node/331941#comment-1971870

smoothify’s picture

I have created a module that enables multi level dependent fields with AHAH updates for Node References and other CCK fields (User References to come).

AHAH Dependent Fields.

Xavier’s picture

In the fapi reference, it is said that #ahah can react to a 'change' event. So I understand that I could use '#ahah" on a select.
But there is no '#submit' options for selects, so I understand that it will use the default submit handler, which would submit the form, which is obviously not what is wanted.

Are there any examples of AHAH with selects around ?
Or is it just an inconsistency, meaning that AHAH can't be used with a change event ?

greg.harvey’s picture

I hit on the exact same issue on the exact same day! Value doesn't change in $form_values when you use the "change" method on a select list (which kinda makes sense, I guess) but then there's no way of getting a handle on the "change". Unless I'm missing something, this is a pointless method?

Edit: The data we need is in $form_state['post'] - that's where the changed select value is. =)

netsensei’s picture

Hi!

You have to define an extra submit button and hide it with some CSS. This extra button has it's own submit handler which gets called through the AHAH call coming from the select. The AHAH callback function executes all the submit handlers including the one attached to your hidden button. This way you can pick up the value from your select list, pass it back to the form function where you can use it to render it anew.

I've created two example modules that demonstrate AHAH on select and submit buttons. I'm going to publish those soonish.

greg.harvey’s picture

Thanks - look forward to seeing your examples. In this case how does the button get "pressed" by the change in the select list? Using more JavaScript? In my case pulling the value from $form['post'] was sufficient, but I'd like to see your full technique. =)

netsensei’s picture

The button doesn't really get 'pressed' in a material way. You just need an extra button to register the extra submit handling function.
The AHAH callback function executes all the registered submit handling functions of the form. Including the one of the extra button. Thus allowing you to get the data and resubmit it.

I've uploaded my two examples in the documentation issue queue for review!

http://drupal.org/node/524220

If you think they're any good: I'll add them to the documentation here.

intelligentdesigner’s picture

You don't actually need to include the render of the extra submit button in your markup -- as long as it's defined in the form array it will still work.

$trash = drupal_render($form['extra_submit_button']);
return drupal_render($form); 
mityok’s picture

I have a little problem with "form building function should create subsequent steps based on the contents of $form_state" part:
when rebuilding the form while processing the ahah callback function my form building function receives no 'values' in $form_state.
here's the code for form building:

function testform_form($form_state) {
      $form['choice'] = array(
		'#title'=>t('test checkboxes'),
		'#type'=>'checkboxes',
		'#options' => array(
		    '1' => '1',
		    '2' => '2',
		    '3' => '3'		    
		),		
	  	'#ahah' => array(
	  			'path'=>'test-form/check-handler-js',
	  			'wrapper'=>'sections-wrapper',	       		
				'method'=>'replace'
			),
		'#default_value'=>isset($form_state['values']['choice']) ? $form_state['values']['choice'] : array()
		);


 $form['sec_wrapper'] = array(		
		    '#prefix' => '<div class="clear-block" id="sections-wrapper">',
		 	'#value' => ' ',		 	
		    '#suffix' => '</div>',
		);
		
		$form['sec_wrapper']['#suffix'].=var_export($form_state,true);


return $form;
}

As you can note, I'm adding the current value of $form_state after my wrapper element. During callback execution I'm getting following $form_state data:

array ( 'storage' => NULL, 'submitted' => false, 'post' => array ( ), )

And here's the code for ahah callback:

function testform_handler_js() {
  // We're starting in step #3, preparing for #4.
  $form_state = array('storage' => NULL, 'submitted' => FALSE);
  
  $form_build_id = $_POST['form_build_id'];
  // Step #4.
  $form = form_get_cache($form_build_id, $form_state);

  // Preparing for #5.
  $args = $form['#parameters'];
  $form_id = array_shift($args);
  $form_state['post'] = $form['#post'] = $_POST;
  $form['#programmed'] = $form['#redirect'] = FALSE;

  // Step #5.
  drupal_process_form($form_id, $form, $form_state);
  // Step #6 and #7 and #8.
  $form = drupal_rebuild_form($form_id, $form_state, $args, $form_build_id);
  
  
  $parts_form = $form['sec_wrapper'];
  unset($parts_form['#prefix'], $parts_form['#suffix']); // Prevent duplicate wrappers.
  
  drupal_json(array(
    'status'   => TRUE,
    'data'     => drupal_render($parts_form),   
    'settings' => call_user_func_array('array_merge_recursive', $javascript['setting'])
  ));
  
}

Also, I've noted that if I simply refresh the page with form (hit F5 in browser or press 'refresh' button in toolbar),
Drupal renders lot's of form validation errors messages (there are some required fields in that form, I've skipped them in example above), sometimes 2-3 times the same message.

perplexed’s picture

Hello, First of all GREAT Tutorial, I am very new to Drupal. I would like to add that I am very impressed with the community. I have a Form using AHAH with a callback that performs a search of an external database table and displays the results in a div wrapper. All this works great when I click the submit button. Once the results are displayed the pagination is at bottom (from pager_query) like expected but when I click any page number, it just displays the single line of code accross the top of browser: "{ "status": true, "data": "\x3cfieldset\x3e\x3cdiv\x3e\n \x3ctable class=..........."

I have done a var_dump of the form values and everything looks ok from the cache, including the form_id and form_build_id.

The resulting line is correct, it just needs to be written to my div wrapper below my form just like when I click submit the first time.

Here is my callback:

function re_simple_search_callback() {
  $form_state = array('storage' => NULL, 'submitted' => FALSE);	 
  $form_build_id = $_POST['form_build_id'];
  $form = form_get_cache($form_build_id, $form_state);
  $args = $form['#parameters']; 
  $form_id = array_shift($args);
  
  $form_state['post'] = $form['#post'] = $_POST;
  $form['#programmed'] = $form['#redirect'] = FALSE;
  
  drupal_process_form($form_id, $form, $form_state);
  $form = drupal_rebuild_form($form_id, $form_state, $args, $form_build_id);

  $parts_form = $form['textfields'];
  unset($parts_form['#prefix'], $parts_form['#suffix']);

 $output = theme('status_messages') . drupal_render($parts_form);

  drupal_json(array('status' => TRUE, 'data' => $output));

}

Here is my submit button in my form:

  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => 'go',
    '#ahah' => array(
        'path' => 're_simple_search/callback',
        'wrapper' => 'search-results-wrapper',
        'method' => 'replace',
        'progress' => array('type' => 'bar', 'message' => t('Searching...')),
      ),
  );

Thank you for any help that anyone can provide and for your time in advance.

asmdec’s picture

What probably happens here is that the submit handler of the AHAH callback is altering the action of the form to his own path. You need to specify the '#action' of your form programatically as explained in this comment http://drupal.org/node/524220#comment-1848070.

The default #action value is the return of request_uri(), which in this AHAH cases is the path of the callback function.

orangeudav’s picture

"#action" not work for me, my solution:

....
$parts_form = $form['textfields'];
unset($parts_form['#prefix'], $parts_form['#suffix']);

$_GET['q'] = url('our/address'); // change as we need  our/addess/js -> our/address

$output = theme('status_messages') . drupal_render($parts_form);
drupal_json(array('status' => TRUE, 'data' => $output));
petreej’s picture

When drupal_process_form is called, what submit handlers will be called? I have a small example (included below), which is a select element with an ahah binding, as well as a few submit buttons. The submit handler that gets called when the select is changed is the first one declared in code. This doesn't seem right. What if I had two ahah select elements. They call the same function! Maybe there is something I don't understand. If I switch the order of the two submit buttons, then submit_b is called. Please examine the code, and let me know what I am overlooking here.

function ahah_test_menu() {
  $items['ahah_test'] = array(
    'title' => 'AHAH Test',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('ahah_test_form'),
    'access callback' => TRUE,
    'type' => MENU_NORMAL_ITEM
  );
  $items['ahah_test/js'] = array(
    'title'=>'Who cares',
    'page callback'=>'ahah_test_something',
    'access callback'=>TRUE,
    'type'=>MENU_CALLBACK
  );
  return $items;
}

function ahah_test_form($form_state) {
//  echo $_SESSION['asdf'];
//  echo $_SESSION['fdsa'];
  $form['country'] = array(
    '#type'=>'select',
    '#title'=>'Country',
    '#options'=>array('Ireland','United States'),
    '#ahah' => array(
      'path'=>'ahah_test/js',
      'wrapper'=>'place_1',
      'event'=>'change',
      'method'=>'replace',
    ),
  );
  $form['wrapper'] = array(
    '#type'=>'item',
    '#prefix'=>'<div id="place_1">',
    '#suffix'=>'</div>'
  );
  $form['a_button'] = array(
    '#type'=>'submit',
    '#value'=>'button a',
    '#submit'=>array('a_submit'),
  );
  $form['b_button'] = array(
    '#type'=>'submit',
    '#value'=>'button b',
    '#submit'=>array('b_submit'),
  );
  return $form;
}

function a_submit($form,&$form_state) {
  $_SESSION['asdf']='asdf';
}

function b_submit($form,&$form_state){
  $_SESSION['fdsa']='fdsa';
}

function ahah_test_something() {
  $form_state = array('storage' => NULL, 'submitted' => FALSE);
  $form_build_id = $_POST['form_build_id'];
  $form = form_get_cache($form_build_id, $form_state);
  $args = $form['#parameters'];
  $form_id = array_shift($args);
  $form['#post'] = $_POST;
  $form['#redirect'] = FALSE;
  $form['#programmed'] = FALSE;
  $form_state['post'] = $_POST;
  drupal_process_form($form_id, $form, $form_state);
  $form = drupal_rebuild_form($form_id, $form_state, $args, $form_build_id);
  $output = 'test';
  drupal_json(array('status' => TRUE, 'data' => $output));
}

Also, looking at the quicktabs module, I see that the submit handlers use these lines near the tops of the functions:

  unset($form_state['submit_handlers']);
  form_execute_handlers('submit', $form, $form_state);  

What is the point of this if not all handlers get called?

Thanks

noobee’s picture

I tried doing what it says, adding it to a moderately complicated form (about 20 fields). But, after the drupal_rebuild_form(), the $form doesn't contain ANY field definitions, just values. Can't, of course, render a non-existent field definition.

Obviously, there's something (probably >1 thing) that I'm completely missing here. I already have some ideas. For example:

What on Earth is the format or contents of $node in the first code example supposed to be? You have already lost me. An actual example, instead of a pseudo-example, would be extremely helpful.

Likewise, what's the deal with including that file in the poll_choice_js() function? What is the purpose of doing that? How does it work to stick a bunch of function definitions in the middle of a function? And, aren't they already defined? Apparently, it's so obvious I can't see it. An example that's not unnecessarily complex would be hugely helpful, but apparently that's not the norm on this site.

This is very discouraging: more and more (5 months now), it seems I will not be productive with Drupal until I have at least studied, or preferably memorized, the entire source code. It seems a prerequisite to "getting" the examples in the doc, at least. (No wonder people say the things they do about trying to learn Drupal!) It would make the doc so much more helpful if you could provide a small, working example available for download, instead of bits and pieces of a solution and a fair amount of "use your imagination" in between.

I have been chasing wild geese in articles and the doc for 3 days now, and still no working AHAH. I'm just trying to make the options in one dropdown reflect the choice selected in the previous one. (The sort of thing one would reasonably expect to be built-in to a framework, actually.) No rocket science at all. Days and days of "it should work" but it doesn't. I'm reasonably sure I'll find I'm doing (or did) something dumb, but there's no way to tell what.

</rant>

I'm very suspicious that there's an issue with my form-building function, but I don't yet have anything concrete enough to be able to tell what.

Any suggestions (or a more complete example) would be exceptionally welcome!

katbailey’s picture

You'll find an example in ahah_example module which is part of the examples package at http://drupal.org/project/examples

Floris246’s picture

Hi there,

When my first dropdown calls the callback function voting_js() it add's a second dropdown form element with the ahah path 'voting/js2'.
When the second dropdown appears and I select an option, the javascript doesn't launch at all.

How can I fix that? Thanks!

noobee’s picture

If you look above at my reply to katbailey's reply, I think you'll recognize that you're running into the same bug in D6 that I hit. They decided it would be an API change, so didn't fix it until D7 (estimated out Sept - Oct).

There are a couple of work-arounds (which you can find with Google). I'll post here again as soon as I get one working (probably a few days; sorry).

iamjon’s picture


"In a nutshell, the code here works perfectly, unless the field type is 'file'"

Do we have to do it the 'old way'?

Jarvis Lukow’s picture

I think this is my fourth attempt to master AHAH. I think this time I am getting pretty close to AHAH zen... but I have a question I was hoping someone could help with.

I have created a form that has a taxonomy select item and two other page elements.

I simply want to add or remove one of two page elements depending on the selected value. I can easily do this with jQuery, but I think this is a good exercise for me to finally figure out AHAH.

My two form elements:

$form['resource_type']['element_one'] = array(
  '#type' => 'textfield',
  '#title' => t('Element One'),
  '#size' => 60,
);

$form['resource_type']['element_two'] = array(
  '#type' => 'textfield',
  '#title' => t('Element Two'),
  '#size' => 60,
);

I am using form_alter to add the AHAH to the taxonomy:

function module_form_alter(&$form, $form_state, $form_id) {
    if ($form_id == 'resource_node_form') {
        $form['taxonomy'][1]['#ahah']['path'] = 'resourceType/js';
        $form['taxonomy'][1]['#ahah']['wrapper'] = 'resource_type';
        $form['taxonomy'][1]['#ahah']['effect'] = 'fade';
    }
}

And my callback so far looks like:

function resource_type_js(){
    $form_state = array('storage' => NULL, 'submitted' => FALSE);
    $form_build_id = $_POST['form_build_id'];
    $form = form_get_cache($form_build_id, $form_state);
    $args = $form['#parameters'];
    $form_id = array_shift($args);
    $form_state['post'] = $form['#post'] = $_POST;
    $form['#programmed'] = $form['#redirect'] = FALSE;
    drupal_process_form($form_id, $form, $form_state);
    $form = drupal_rebuild_form($form_id, $form_state, $args, $form_build_id);
}

I am stumped on how to remove one of the two form elements. Any help would be really appreciated.

Thanks!
Jarvis

visum’s picture

It wasn't immediately apparent to me that the "form generator" is the callback called by drupal_get_form() and returns an array describing the form. I thought the "form generator" was something deep in core that worked on the above mentioned callback.

Another note (probably covered in FAPI somewhere but I needed to know it here): the form generator function can accept arguments passed from drupal_get_form, but they show up as the second argument, because $form_state takes the first slot. When doing so, the form generator declaration should look like:

function mymodule_myform($form_state, $other_args) {
  $form = array();
  // ...
  return $form;
}

When rendering the form, do something like:

  $other_args = array('something' => 'my form needs this');
  print drupal_get_form('mymodule_myform', $other_args);

This is how, when a form rendered using hook_form() in a node module, the second argument takes the $node, as shown in the first snippet above.

Thanks for this page, it's been very helpful!

cladom’s picture

hello,

how can i theme the called field in the path????
i need to give some css do the new generated fields but i do not know how to do it!

Is there any help available for this ?

Thank you


 [#ahah] => Array
        (
            [path] => content/js_add_more/prof-profile/field_cargovalor
            [wrapper] => field-cargovalor-items
            [method] => replace
            [effect] => fade
            [event] => mousedown
            [keypress] => 1
        )
visum’s picture

Since the page doesn't get reloaded, you can't exactly hand back a modified CSS file with the AHAH callback. Your two options would be to:
1) Already have the style you need defined in your theme or module stylesheet, or if that's not possible because you don't know the element's CSS selector beforehand,
2) Send the styles as in-line styles inside of the HTML you returned with your callback. That will most likely involve adding a line to your element declaration in your form generator something like this:
$form['my_element']['#attributes'] = array('style' => 'color: #444444');

lsiden’s picture

I am writing an AHAH form handler that adds another text field to a form when the user hits an "Add Another" button. When function atdwh_profile_add_category_submit($form, &$form_state) is called, $form_state is empty, so the code cannot keep a count of how many text fields have been added so far. What am I doing wrong? The relevant parts of my code can be found at http://pastie.org/1350899. Thanks to all who may help.

vikingjs’s picture

To see if the fields exist, you should be able to look at $form. If the fields have values on submit, those really should be in $form_state, though, so your problem might run deeper. Start by looking at $form to see if the fields are there in any case.

Hope this helps.

shendric’s picture

I am attempting to do something which seems very simple, but I cannot get it to function. I'm about at the end of my rope on this. All I want to do is have a single form element of type text. When I click an "add another field" I would like it to give me another element of the same type. I have followed numerous tutorials, but they all seem to have something like a select menu to determine how many fields to display, or another field to determine this number.

I have tried to use a hidden field that keeps track of how many fields there are, but after the AHAH callback, it gets reset to a default, because the value of the field is not getting stored in the form when it returns. I can provide code, if it helps, but I feel that I'm missing something very basic. Can I not change the value of the hidden field in the callback? If so, what do I use? $form['field_name']['#value']? $form['values']['field_name']? $form['field_name']? I feel like I've tried them all, but gotten no results.

Any help would be greatly appreciated.

shendric’s picture

Most of the code examples I saw neglected to mention that $form_state must be passed to the form building function by reference (&$form_state). Once I did that, all was well. That may be just obvious, but it wasn't obvious to me, and hopefully it will help someone else.

steveoliver’s picture

I wonder (am working on) how this (or, ideally Wim Leer's ahah_helper module) might work to supply AHAH functionality to forms (steps) within a CTools' Form Wizard-powered form... ? So far it's been a pain in the arse, as what's in $_POST is the wizard form, not the form for the current step of the wizard...

stockliasteroid’s picture

I had the same issue... Basically the way this is structured it submits the whole form to the main form_submit function during the ahah callback, which is probably not what you want to do. You only want it to call the submit handler for your "Add More" button, not the main form submit handler. I'm not sure of the best way to handle this with a module using hook_submit for node forms, but if you're building your own forms, I'd check out how QuickTabs does it. It basically checks in the main submit callback to see if the button triggering the save is the actual save button before it proceeds with the submit:

if ($form_state['clicked_button']['#id'] == 'edit-submit-form') {
//do your form submission stuff here
}

Seems kinda icky, but that's the only way I could get it to still fire the submit handler for the Add button, and NOT fire the main submit handler for the form.

shaneforsythe’s picture

You begin explaining how the poll module sets up the form ... then you explain the callback method from poll ... then you explain the submit handler from quicktabs ????

Why not clearly show and link how all 3 are working from ONE module (or other example)

CBurgess’s picture

I agree, this would be better and much clearer if I could see how these three are working together more vividly. Link would help for sure.
Thanks in advance,

Claire,
MKV To AVI Converter developer

kentr’s picture

Can anyone illuminate whether there's some point to the cache beyond the basic labor saving of not regenerating the form, like some security feature?

Why does $form_state need to be set in the submit handler and why not alter the for based on $_POST? Is it b/c other submit handlers need a way to change things along the way, similar to a hook setup?

libruce’s picture

I'm sucked in the critical issue. I added dynamic form elements using AHAH with custom theme, it work great for the displaying and submission. But it lost the custom theme when validate failed. How to deal with the issue?

Here is my ahah callback

function mymodule_change_ahah_js() {

  // We're starting in step #3, preparing for #4.
  $form_state = array(
      'storage' => NULL, 'submitted' => FALSE
  );
  $form_build_id = $_POST ['form_build_id'];
  // Step #4.
  $form = form_get_cache($form_build_id, $form_state);

  // Preparing for #5.
  $args = $form ['#parameters'];
  $form_id = array_shift($args);
  $form_state ['post'] = $form ['#post'] = $_POST;
  $form_state['action'] = $form['#action'];
  $form ['#programmed'] = $form ['#redirect'] = FALSE;
  // Step #5.
  drupal_process_form($form_id, $form, $form_state);
  // Step #6 and #7 and #8.
  $form = drupal_rebuild_form($form_id, $form_state, $args, $form_build_id);
  //change your element here
  $form = mymodule_form_step();

  $form = form_builder('mymodule_form_step', $form, $form_state);
  $i = $_SESSION['step'];
  --$i;


  $ahah_settings = ahah_render($form['milestone_' . arg(2)]['step_' . $i]["step_add_above"]);
  $ahah_settings2 = ahah_render($form['milestone_' . arg(2)]['step_' . $i]["step_add_below"]);
  // Step #9.

  $subform = $form;
  unset($subform ['#prefix'], $subform ['#suffix']);

  $output = theme('status_messages') . drupal_render($subform);
  $output .= '<script type="text/javascript">jQuery.extend(Drupal.settings.ahah, ' . drupal_to_js($ahah_settings) . ');</script>';
  $output .= '<script type="text/javascript">jQuery.extend(Drupal.settings.ahah, ' . drupal_to_js($ahah_settings2) . ');</script>';
  // Final rendering callback.

  drupal_json(array(
      'status' => TRUE, 'data' => $output
  ));
}

function mymodule_form_step() {


  //some code ... 


  $form ['#theme'] = 'mymodule_form_tpl';
  $form ['#cache'] = TRUE;
  return $form;
}


tbrix’s picture

Thanx a lot. I am almost there with ahah in Drupal 6. But what about the field type file ? If I put a file type anywhere in the form, it breaks, returning a js error
"TypeError: j.submit is not a function" in
...+u+'" value="'+g.extraData[u]+'" />').appendTo(j)[0]);t.appendTo("body");t.data(...
jquery.form.js

tbrix’s picture

Hi.

I have a problem with anonymous users and this example D6. In the AHAH Callback, the form is fetched from cache via the $form_build_id:

function quicktabs_ahah() {
$form_state = array('storage' => NULL, 'submitted' => FALSE);
$form_build_id = $_POST['form_build_id'];
$form = form_get_cache($form_build_id, $form_state);

...

This works well for users logged in as they will have there own unique cached form.

But for all users of the form that is NOT logged in, the form will be given the same $form_build_id. This means that fetching the form from cache for one anonymous user and another, will result in the two different users sharing the same form data. How can i prevent this and make sure that all instances generated of the form will be unique ? I have been struggling for hours with this and cant seem to nail it.

* edit update

The reason for the shared form_build_id's turns out to be simple. The site uses Drupal cache. Pages are cached for anonymous users - including the form and therefore also the form_build_id. I installed cacheexclude https://drupal.org/project/cacheexclude and excluded the form paths from caching. Now every instance of the form is unique and anonymous users no longer gets merged data from other form posts via ahah callback.