Adding dynamic form elements using AHAH
For an understanding about what AHAH is, and more specifically, what AHAH in Drupal is, please read the "Introduction to AHAH in Drupal (http://drupal.org/node/348475)" before reading this section. Here we will launch straight into the problem of adding dynamic form elements in Drupal 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 what needs to be in place:
- Your main function for building the form should react to $form_state, meaning that its elements and their values will be defined based on user-submitted data or data already existing in the database
- You need an AHAH callback function which is called by your #ahah binding
- Your AHAH callback retrieves the form from the cache and goes through the process of rebuilding it, which includes calling submit handlers, the first of which will be the element-specific submit handler for the element your #ahah behaviour is bound to
- Your submit handler needs to retain user-submitted information in $form_state which will then be used in the rebuilding of the form
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 it 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". 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 main tricks beyond simply creating your form that you must follow for AHAH to work smoothly and soundly (and following these can also reduce 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 subsequent steps 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 $_POST or similar.
As an example, here is a small excerpt from the top of the function node_form in node.pages.inc:
<?php
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:
- The form generator creates the form.
- It gets cached, and rendered.
- The user submits the AHAH callback, which goes to your callback.
- You retrieve the form from the cache.
- You process it with
drupal_process_form. Process calls the submit handlers, which put whatever was worthy of keeping into form_state. - You call
drupal_rebuild_formwhich first destroys$_POST. - The form generator function is called and creates the form again but since it knows to react to form_state, the form will be different.
- The new form gets cached and processed again, but because
$_POSTis destroyed, it knows not to call the submit handlers again. - 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 an example from poll.module* of AHAH done right.
<?php
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 until 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. There are three #ahah elements in the quicktabs admin form (this form allows you to create blocks of tabbed content, choosing either an existing block or a view for each tab): 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:
<?php
/**
* 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 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:<?php
// 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 you can see how $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:
<?php
/**
* 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 one shown above, which is also the callback used for the remove button, it just has a different submit handler, which decreases the number of tabs by one.
One other thing worth mentioning about the ahah callback is the use of the $javascript array. 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.
The problem is detailed much more explicitly here:
http://drupal.org/node/399676
In a nutshell, the code here works perfectly, unless the field type is 'file'

Cannot submit more than once
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.
Resolved
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.
Really?
I tried this but now it doesn't change the form through the callback. Looks like I'm stuck with
Any ideas?
Modifying a CCK node add form with AHAH
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!
Hi burningdog - unfortunately
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.
Any other solution?
@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?
Dealing with AHAH and cck select field
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
Not possible?
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).
Hi Roger, thanks for this
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?
Nope
No, I haven't - see the problems that such a solution will need to solve: http://drupal.org/node/331941#comment-1971870
AHAH Submit
.
Inconsistency ?
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 ?
+1
I hit on the exact same issue on the exact same day! Value doesn't change in
$form_valueswhen 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. =)--
http://www.drupaler.co.uk/
AHAH and Select
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.
http://www.netsensei.nl
http://www.colada.be
Interesting
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. =)--
http://www.drupaler.co.uk/
The button doesn't really get
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.
http://www.netsensei.nl
http://www.colada.be
You don't actually need to
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);
I have a little problem with
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:
<?php
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:
<?php
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.
--
My blog (in russian) www.veldv.info
Problem with array_shift
Thanks for this great tutorial, it works really great and it helped me building very nice features.
Just one problem that only occures rarely and I don't know why that happens while 99% of the time the very same code works without an error at all. When preparing for #5 there are the following two lines of code:
$args = $form['#parameters'];$form_id = array_shift($args);
and the second fires an error message that a wrong parameter has been used. Is suspect, that in that case
$form['#parameters']hasn't been an array. However, on the site where this happaned once or twice, there is only one single form which makes use of ahah and it usually works without that error. In other words, that variable seems to be an array but not always.I know that sounds crazy but that's what happened. Any idea?
IT services as individual as the requirements.
AHAH Callback and pager_query Problem
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:
<?php
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:
<?php$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.
What probably happens here is
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.
Executing submit handlers
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