... that's a pity hardcore job ;) Trust me or not: due to developing this pieces of code, before I found a solution for the inproper AHAH behavior on AHAH-loaded forms in a multistep AHAH forms I pulled a half of hair from my head and perform a facepalm in the end;) I've read dozens of articles about AHAH forms to find a solution. No positive results. So this is it :) The shortest possible multistep AHAH form example :)
Imagine: we have to develop a multiform page with AJAX behavior like a common Onepage Multistep Form. The easiest to meet is Onepage Checkout page in the online stores. As we know, these have either one such a big form with all fields required to fill-in at once or a single steps to fill-in data in a separate pages. Both have pros and cons. But what if we could deliver a possibility to have separate form steps and let user fill them in without page refresh?
Here comes AHAH. Let me show you example with three form steps - first form AHAH-ready, second - AHAH-loaded, third - page result based on first and second form data. In addition, we do not need to use AHAH Helper by Wim Leers here.
Firstly, we declare hook_menu() for module page and AHAH callbacks:
function ahah_test_menu() {
$items['ahah_test/onepage'] = array(
'page callback' => 'ahah_test_form_page',
'access arguments' => array('access content'),
'type' => MENU_CALLBACK,
);
$items['ajax/onepage/%/%'] = array(
'page callback' => 'onepage_checkout_forms_js',
'page arguments' => array(2,3),
'access arguments' => array('access content'),
'type' => MENU_CALLBACK,
);
return $items;
}
Next: two forms and submit callbacks:
function ahah_test_step_first_form($form, &$form_state) {
$form = array();
$form['firstname'] = array(
'#type' => 'textfield',
'#title' => 'Firstname',
'#default_value' => $_SESSION['ahah_test_onepage']['firstname'],
'#required' => TRUE,
);
$form['submit'] = array(
'#type' => 'submit',
'#id' => 'first_step_submit', // Ad. 1 (explanation below)
'#value' => 'Submit',
'#ahah' => array(
'event' => 'click',
'path' => 'ajax/onepage/first_step_submit/0',
'wrapper' => 'ahah-output', // Ad. 2 (explanation below)
'method' => 'replace',
'effect' => 'fade',
),
);
return $form;
}
function ahah_test_step_second_form($form, &$form_state) {
$form = array();
$form['lastname'] = array(
'#type' => 'textfield',
'#title' => 'Lastname',
'#default_value' => $_SESSION['ahah_test_onepage']['lastname'],
'#required' => TRUE,
);
$form['submit'] = array(
'#type' => 'submit',
'#id' => 'second_step_submit', // Ad. 1 (explanation below)
'#value' => 'Submit',
'#ahah' => array(
'event' => 'click',
'path' => 'ajax/onepage/second_step_submit/0',
'wrapper' => 'ahah-output', // Ad. 2 (explanation below)
'method' => 'replace',
'effect' => 'fade',
),
);
return $form;
}
function ahah_test_step_first_form_submit($form, &$form_state) {
$_SESSION['ahah_test_onepage']['firstname'] = $form_state['values']['firstname'];
}
function ahah_test_step_second_form_submit($form, &$form_state) {
$_SESSION['ahah_test_onepage']['lastname'] = $form_state['values']['lastname'];
}
Ad. 1 THIS IS THE MOST IMPORTANT ATTRIBUTE TO MAKE AHAH-LOADED FORMS AHAH-READY! If we do not set up this attribute to the submit form field as an absolute unique value (instead of ID rendered dynamically by Drupal Form API) - our AHAH-loaded form will fail with AHAH behavior, because AHAH binding after loading each form step will be processed on a first enabled form in a page ever (ie. login form, register form, search form).
Ad. 2 Output (messages and other JSON data) generated in an AHAH callbacks will be inserted to the HTML element with such an ID value.
function ahah_test_form_page() {
$output = '<div id="onepage-wrapper">';
$output .= '<div id="ahah-output"></div>'; // wrapper for AHAH callback output
$output .= '<div id="ahah_test-step-first-form">' . drupal_get_form('ahah_test_step_first_form') . '</div>';
$output .= '<div id="ahah_test-step-second-form">' . ($_SESSION['ahah_test_onepage']['step_first_completed'] ? drupal_get_form('ahah_test_step_second_form') : '') . '</div>';
$output .= '<div id="ahah-forms-result"></div>'; // final result output after second form
$output .= '</div>';
return $output;
}
We declare AHAH callback to process each available form step:
function onepage_checkout_forms_js($op = 'list', $arg = 0) {
switch ($op) {
case 'first_step_submit':
$form = ahah_test_form_handler($arg); // we process current form by custom developed form handler
$output = theme('status_messages');
$second_step_form = drupal_get_form('ahah_test_step_second_form');
if (empty($output)) { // if there is no messages like errors ($output is empty)
$output .= '<script type="text/javascript">
$(document).ready(function() {
$("#ahah_test-step-second-form").empty().html(' . drupal_to_js($second_step_form) . ');
});
</script>';
}
break;
case 'second_step_submit':
$form = ahah_test_form_handler($arg);
$output = theme('status_messages');
$forms_result = ahah_test_forms_result(); // final page results
if (empty($output)) {
$output .= '<script type="text/javascript">
$(document).ready(function() {
$("#ahah-forms-result").empty().html(' . drupal_to_js($forms_result) . ');
});
</script>';
}
break;
}
// In order to make AHAH-loaded forms AHAH-ready, we need to send new Drupal.settings
// with updated AHAH-form bindings ready to use by javascript callout
$javascript = drupal_add_js(NULL, NULL, 'header');
$settings = call_user_func_array('array_merge_recursive', $javascript['setting']);
// we bind form elements using a new Drupal.settings for AHAH fields
$output .= '<script type="text/javascript">
$(document).ready(function() {
// Yeap, this is from misc/ahah.js :) We need such a thing to make AHAH-loaded forms AHAH-ready
var buttons = ' . drupal_to_js($settings['ahah']) . ';
for (var base in buttons) {
if(!$("#" + base + ".ahah-processed").length > 0) {
var element_settings = buttons[base];
$(element_settings.selector).each(function() {
element_settings.element = this;
var ahah = new Drupal.ahah(base, element_settings);
});
$("#" + base).addClass("ahah-processed");
}
}
});
</script>';
drupal_json(array(
'status' => TRUE,
'data' => $output,
'settings' => array('ahah' => $settings['ahah']),
));
}
Final results, based on the content inserted in both previous steps:
function ahah_test_forms_result() {
return 'Username: ' . $_SESSION['ahah_test_onepage']['firstname'] . ' ' . $_SESSION['ahah_test_onepage']['lastname'];
}
The last thing is to create form handler to process and rebuild form steps:
function ahah_test_form_handler($delta = 0) {
include_once 'modules/node/node.pages.inc';
$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;
$form_state['remove_delta'] = $delta;
drupal_process_form($form_id, $form, $form_state);
if (form_get_errors ()) {
form_execute_handlers('submit', $form, $form_state);
}
$form = drupal_rebuild_form($form_id, $form_state, $args, $form_build_id);
return $form;
}
DEMONSTRATION, a little bit more complex: availabe on my test online store based on Drupal: www.dis-cart.designend.net (in Polish, you can use GoogleTranslate if needed:)), just add something to the cart, goto order, select quick order as a guest and fill-in all onepage checkout steps by AJAX. If the order process will fail in the end, don't remind me. This does have a big changes in modules right now;) Orders won't be processed;)
And there is something more for these ones, that wan't a ready module to check out: DOWNLOAD :)
Comments
This entry has no comments yet
Leave a comment