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


/**
 * @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'));
				}
			}
    }
  }
}


Comments

jaypan’s picture

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.

Contact me to contract me for D7 -> D10/11 migrations.