Tuesday, April 27, 2010

Enable Ajax Mode in Drupal

I have been trying to work with Ajax in Drupal and I must say it was a cumbersome experience. First of all, even though I could find some online example (source codes), most of them were sort of advanced intermediate level codes and they were really difficult to understand what was actually going on. Moreover, the example codes I based off of never really worked without having to tweak them a little. I found that there was lack of good example Ajax code for Drupal; hence, I decided to provide my solution (that worked) here.

First of all you will need to define what you want in your forms. Basically with Ajax, you would want something that dynamically changes the contents of your form without having to reload the entire page. In my example, I placed a checkbox which will, after it's been clicked, display additional fields titled 'question' and 'answer'.

Here is basically what you will need to add in your hook_form function:

function activitymanager_form(&$node, $form_state) {
function activitymanager_form(&$node, $form_state) {


$form['textfield'] = array(
'#type' => 'checkbox',
'#title' => t('Textfield'),
'#default_value' => $form_state['values']['textfield'],
'#ahah' => array(
'path' => 'activitymanager/autotextfields/callback',
'wrapper' => 'textfields',
'effect' => 'fade',
)
);

$form['textfields'] = array(
'#title' => t("Generated text fields for the verification types"),
'#prefix' => '
"textfields">',
'#suffix' => '
'
,
'#type' => 'fieldset',
);

if ($form_state['values']['textfield']) {
$form['textfields']['question'] = array(
'#type' => 'textfield',
'#title' => t('Activity Question'),
);
$form['textfields']['answer'] = array(
'#type' => 'textfield',
'#title' => t('Activity Answer'),
);
}
return $form;

}

The Textfield form displays the checkbox itself. The '#ahah' field defines the Ajax behavior. '#path' directory should map to your callback function, which will be called everytime a user clicks on the checkbox. In my case, the callback function is called activitymanager_autoindex_callback. Therefore, the path to the function should be 'activitymanager/autoindex/callback. You must also set the path to your callback function in your hook_menu function as follows:

define(TEST_PATH,'activitymanager/autotextfields');


function activitymanager_menu() {

$items = array();

// Setup the initial menu option.
// yoursite/?q=test
// if you use clear URLs
// yoursite/test
$items['test'] = array(
'title' => 'Test setup',
'description' => 'A simple test form.',
'page callback' => 'drupal_get_form',
'page arguments' => array ('activitymanager_form'),
'access arguments' => array('access content'),
'type' => MENU_NORMAL_ITEM
);

/**
* This is the path in your site to the ajax/ahah callback function.
*/
$items[TEST_PATH] = array(
'title' => 'Test setup',
'description' => 'A simple test AHAH callback.',
'page callback' => 'drupal_get_form',
'page arguments' => array (
// first argument is the callback function.
'activitymanager_autotextfields_callback'),
//'test_ahah_cb'),
'access arguments' => array('access content'),
'type' => MENU_NORMAL_ITEM
);

return $items;
}
The above code is necessary to tell Drupal how it can find the callback function. It is quite cumbersome to add a new entry here everytime you create a new callback function, but it is a necessary step.

Finally here is my callback function:

function activitymanager_autotextfields_callback() {

// Get form from cache.
$form_state = array('storage' => NULL, 'submitted' => NULL);
$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);

$textfields = $form['textfields'];
$output = drupal_render($textfields);

// Final rendering callback.
print drupal_json(array('status' => TRUE, 'data' => $output));
exit();
}
there were some things in this code that I had to tweak to get it to work properly. When I first ran this code, Drupal would for some reason submit the form whenever I clicked on the checkbox without notifying me of it. So if I clicked on the checkbox 5 times, I would end up with 5 duplicate entries in the database. This is obviously not what I wanted, so I began researching what was actually happening. I found out soon that the drupal_process_form function inside my callback function is causing the above behavior. I am yet to understand why Drupal decides to submit the form here, luckily though I figured out a way to work around this issue. I found that the some of the values in $form_state array can be used to change the behavior of drupal_process_form function. I inserted the following code just above my call to drupal_process_form fucntion:
$form_state['rebuild'] = 'notempty';
This eliminated the submit problem I was having. My callback function now looks like this:

function activitymanager_autotextfields_callback() {

// Get form from cache.
$form_state = array('storage' => NULL, 'submitted' => NULL);
$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['rebuild'] = 'notempty';

drupal_process_form($form_id, $form, $form_state);
$form = drupal_rebuild_form($form_id, $form_state, $args, $form_build_id);

$textfields = $form['textfields'];
$output = drupal_render($textfields);

// Final rendering callback.
print drupal_json(array('status' => TRUE, 'data' => $output));
exit();
}
I hope that somebody in the future finds this posting helpful, as adding an Ajax function in Drupal was not an easy task (at least for me).

No comments:

Post a Comment