Creating an AHAH element within another AHAH element
8ballsaysiwin - October 20, 2009 - 22:24
I am working on creating a form using ahah that will allow the node creator to create a set of questions and a set of possible answers to each question. I have borrowed heavily from the poll module, but am still having a little difficulty figuring out what I need to fix to get this going correctly. If someone could look through this and provide some guidance on what I still need to work on it would be much appreciated.
(I have looked at the quiz module, but it's unfortunately not what I need for this particular project.)
Cheers,
George
<?php
/**
* @file
* Provides a test node type
*/
/**
* Implementation of hook_node_info().
*/
function test_node_info() {
return array(
'test' => array(
'name' => t('test'),
'module' => 'test',
'description' => t('Set up a campaign test!'),
'has_title' => TRUE,
'title_labal' => t('Title'),
'has_body' => TRUE,
'body_label' => t('Introductory text'),
'locked' => TRUE
)
);
}
/**
* Impletentation of hook_perm().
*/
function test_perm() {
return array('create test', 'edit own test', 'edit any test', 'delete own test', 'delete any test', 'access tests');
}
/**
* Implementation of hook_access().
*/
function test_access($op, $node, $account) {
$is_author = $account->uid == $node->uid;
switch ($op) {
case 'create':
// Allow if user's role has 'create test' permission.
return user_access('create test', $account);
case 'update':
// Allow if user's role has 'edit own test' permission and user is the author; or if the user's role has 'edit any joke' permission.
return user_access('edit own test', $account) && $is_author || user_access('edit any test', $account);
case 'delete':
// Allow if user's role has 'delete own test' permission and user is the author; or if the user's role has 'delete any test' permission.
return user_access('delete own test', $account) && $is_author || user_access('delete any test', $account);
}
}
/**
* Implementation of hook_form().
*/
function test_form($node) {
// Get metadata for this node type
// we use it for labeling the title and body fields
global $user;
$admin = user_access('administer nodes') || user_access('edit any test') || (user_access('edit own test') && $user->uid == $node->uid);
$type = node_get_types('type', $node);
$form['title'] = array(
'#type' => 'textfield',
'#title' => check_plain($type->title_label),
'#required' => TRUE,
'#default_value' => $node->title,
'#weight' => -45,
'#maxlength' => 225
);
$form['body_filter']['body'] = array(
'#type' => 'textarea',
'#title' => check_plain($type->body_label),
'#required' => TRUE,
'#default_value' => $node->body,
'#weight' => -44,
'#rows' => 5
);
if (isset($form_state['question_count'])) {
$question_count = $form_state['question_count'];
}
else {
$question_count = max(2, empty($node->question) ? 2 : count($node->question));
}
// Add a wrapper for the questions and more button.
$form['question_wrapper'] = array(
'#tree' => FALSE,
'#weight' => -4,
'#prefix' => '<div class="clear-block" id="test-question-wrapper">',
'#suffix' => '</div>',
);
// Container for just the questions.
$form['question_wrapper']['question'] = array(
'#prefix' => '<div id="test-question">',
'#suffix' => '</div>',
'#theme' => 'test_question',
);
// Add the current questions to the form.
for ($delta = 0; $delta < $quesion_count; $delta++) {
$text = isset($node->question[$delta]['qtext']) ? $node->question[$delta]['qtext'] : '';
$form['question_wrapper']['question'][$delta] = _test_question_form($delta, $text);
}
// Add current answers to each question set
if (isset($form_state['values']['question']['answer_count'])) {
$answer_count = $form_state['answer_count'];
}
else {
$answer_count = max(2, empty($node->question) ? 2 : count($node->question));
}
// Add a wrapper for the answers and their more button.
$form['question_wrapper']['question']['answer_wrapper'] = array(
'#tree' => FALSE,
'#weight' => -3,
'#prefix' => '<div class="clear-block" id="test-answer-wrapper">',
'#suffix' => '</div>',
);
// Container for just the questions.
$form['question_wrapper']['question']['answer_wrapper']['answer'] = array(
'#prefix' => '<div id="test-answer">',
'#suffix' => '</div>',
'#theme' => 'test_answer',
);
// Add the current answers to the form.
for ($answerDelta = 0; $answerDelta < $answer_count; $answerDelta++) {
$answer = isset($node->question[$answerDelta]['answer']) ? $node->question[$answerDelta]['answer'] : '';
$form['question_wrapper']['question']['answer_wrapper']['answer'][$answerDelta] = _test_answer_form($answerDelta, $answer);
}
// We name our button 'test_answer_more' to avoid conflicts with other modules using
// AHAH-enabled buttons with the id 'more'.
$form['question_wrapper']['question']['answer_wrapper']['test_answer_more'] = array(
'#type' => 'submit',
'#value' => t('Add more answer choices'),
'#description' => t("If the amount of answer options above isn't enough, click here to add more options."),
'#weight' => 1,
'#submit' => array('test_more_answers_submit'), // If no javascript action.
'#ahah' => array(
'path' => 'test/ahah',
'wrapper' => 'test-answer',
'method' => 'replace',
'effect' => 'fade',
),
);
// We name our button 'test_more' to avoid conflicts with other modules using
// AHAH-enabled buttons with the id 'more'.
$form['question_wrapper']['test_more'] = array(
'#type' => 'submit',
'#value' => t('Add more questions'),
'#description' => t("If the amount of question options above isn't enough, click here to add more questions."),
'#weight' => 1,
'#submit' => array('test_more_question_submit'), // If no javascript action.
'#ahah' => array(
'path' => 'test/js',
'wrapper' => 'test-question',
'method' => 'replace',
'effect' => 'fade',
),
);
// test attributes
$_duration = array(0 => t('Unlimited')) + drupal_map_assoc(array(86400, 172800, 345600, 604800, 1209600, 2419200, 4838400, 9676800, 31536000), "format_interval");
$_active = array(0 => t('Closed'), 1 => t('Active'));
if ($admin) {
$form['settings'] = array(
'#type' => 'fieldset',
'#collapsible' => TRUE,
'#title' => t('test settings'),
'#weight' => -3,
);
$form['settings']['active'] = array(
'#type' => 'radios',
'#title' => t('test status'),
'#default_value' => isset($node->active) ? $node->active : 1,
'#options' => $_active,
'#description' => t('When a test is closed, users can no longer use it.')
);
}
$form['settings']['runtime'] = array(
'#type' => 'select',
'#title' => t('test duration'),
'#default_value' => isset($node->runtime) ? $node->runtime : 0,
'#options' => $_duration,
'#description' => t('After this period, the test will be closed automatically.'),
);
return $form;
}
/**
* Submit handler to add more choices to a poll form. This handler is used when
* javascript is not available. It makes changes to the form state and the
* entire form is rebuilt during the page reload.
*/
function test_more_question_submit($form, &$form_state) {
// Set the form to rebuild and run submit handlers.
node_form_submit_build_node($form, $form_state);
// Make the changes we want to the form state.
if ($form_state['values']['test_more']) {
$n = $_GET['q'] == 'test/js' ? 1 : 5;
$form_state['question_count'] = count($form_state['values']['question']) + $n;
}
}
function test_more_answers_submit($form, &$form_state) {
// Set the form to rebuild and run submit handlers.
node_form_submit_build_node($form, $form_state);
// Make the changes we want to the form state.
if ($form_state['values']['question']['answer_count']) {
$n = $_GET['a'] == 'test/ahah' ? 1 : 5;
$form_state['values']['question']['answer_count'] = count($form_state['values']['question']['answer_count']) + $n;
}
}
function _test_question_form($delta, $value = '', $answer_number = 0) {
$admin = user_access('administer nodes');
$form = array(
'#tree' => TRUE,
'#cache' => TRUE,
);
// We'll manually set the #parents property of these fields so that
// their values appear in the $form_state['values']['choice'] array.
$form['qtext'] = array(
'#type' => 'textfield',
'#title' => t('Choice @n', array('@n' => ($delta + 1))),
'#default_value' => $value,
'#parents' => array('question', $delta),
);
return $form;
}
function _test_answer_form($answerDelta, $value = '', $answer_number = 0) {
$admin = user_access('administer nodes');
$form = array(
'#tree' => TRUE,
'#cahce' => TRUE,
);
// We'll manually set the #parents property of these fields
$form['question_wrapper']['question']['answer'] = array(
'#type' => 'textfield',
'#title' => t('Answers for choice @n', array('@n' => ($answerDelta + 1))),
'#default_value' => $answer_number,
'#size' => 5,
'#maxlength' => 7,
'#parents' => array('answer', $answerDelta),
);
return $form;
}
/**
* Menu callback for AHAH additions.
*/
function test_question_js() {
include_once 'modules/node/node.pages.inc';
$form_state = array('storage' => NULL, 'submitted' => FALSE);
$form_build_id = $_POST['form_build_id'];
// Get the form from the cache.
$form = form_get_cache($form_build_id, $form_state);
$args = $form['#parameters'];
$form_id = array_shift($args);
// We will run some of the submit handlers so we need to disable redirecting.
$form['#redirect'] = FALSE;
// We need to process the form, prepare for that by setting a few internals
// variables.
$form['#post'] = $_POST;
$form['#programmed'] = FALSE;
$form_state['post'] = $_POST;
// Build, validate and if possible, submit the form.
drupal_process_form($form_id, $form, $form_state);
// This call recreates the form relying solely on the form_state that the
// drupal_process_form set up.
$form = drupal_rebuild_form($form_id, $form_state, $args, $form_build_id);
// Render the new output.
$question_form = $form['question_wrapper']['question'];
unset($question_form['#prefix'], $question_form['#suffix']); // Prevent duplicate wrappers.
$output = theme('status_messages') . drupal_render($question_form);
drupal_json(array('status' => TRUE, 'data' => $output));
}
/**
* Menu callback for AHAH additions.
*/
function test_answer_ahah() {
include_once 'modules/node/node.pages.inc';
$form_state = array('storage' => NULL, 'submitted' => FALSE);
$form_build_id = $_POST['form_build_id'];
// Get the form from the cache.
$form = form_get_cache($form_build_id, $form_state);
$args = $form['#parameters'];
$form_id = array_shift($args);
// We will run some of the submit handlers so we need to disable redirecting.
$form['#redirect'] = FALSE;
// We need to process the form, prepare for that by setting a few internals
// variables.
$form['#post'] = $_POST;
$form['#programmed'] = FALSE;
$form_state['post'] = $_POST;
// Build, validate and if possible, submit the form.
drupal_process_form($form_id, $form, $form_state);
// This call recreates the form relying solely on the form_state that the
// drupal_process_form set up.
$form = drupal_rebuild_form($form_id, $form_state, $args, $form_build_id);
// Render the new output.
$answer_form = $form['question_wrapper']['question']['answer_wrapper']['answer'];
unset($answer_form['#prefix'], $question_form['#suffix']); // Prevent duplicate wrappers.
$output = theme('status_messages') . drupal_render($answer_form);
drupal_json(array('status' => TRUE, 'data' => $output));
}
/**
* Implementation of hook_submit().
*/
function poll_node_form_submit(&$form, &$form_state) {
// Renumber fields
$form_state['values']['question'] = array_values($form_state['values']['question']);
$form_state['values']['question']['answer'] = array_values($form_state['values']['question']['answer']);
}
/**
* Implementation of hook_validate().
*/
function poll_validate($node) {
if (isset($node->title)) {
// Check for at least two options and validate amount of votes:
$realchoices = 0;
// Renumber fields
$node->question = array_values($node->question);
foreach ($node->question as $i => $question) {
if ($question['qtext'] != '') {
$realchoices++;
}
foreach ($node->question['answer'] as $a => $answer) {
if ($answer['answer'] != '') {
$answerchoices++;
}
if ($answerchoices < 2) {
form_set_error("question][answer][$answerchoices", t('You must give the user at least 2 options to choose from'));
}
}
}
}
}
?>
I strongly recommend using
I strongly recommend using the AHAH Helper module, as it both does what you want, and makes AHAH so much easier to deal with. It's a real headache trying to debug it.