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

Jay Matwichuk - October 21, 2009 - 02:04

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.

 
 

Drupal is a registered trademark of Dries Buytaert.