? poll_ahah.patch
Index: poll.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/poll/poll.module,v
retrieving revision 1.233
diff -u -p -r1.233 poll.module
--- poll.module	30 Jun 2007 19:46:57 -0000	1.233
+++ poll.module	30 Jun 2007 21:22:54 -0000
@@ -101,12 +101,9 @@ function poll_delete($node) {
  */
 function poll_node_form_submit(&$form, &$form_state) {
   // Renumber fields
-  $form_state['values']['choice'] = array_values($form_state['values']['choice']);
+  $form_state['values']['choices'] = array_values($form_state['values']['choices']);
   $form_state['values']['teaser'] = poll_teaser((object)$form_state['values']);
   $form_state['choices'] = $form_state['values']['choices'];
-  if ($form_state['values']['morechoices']) {
-    $form_state['choices'] *= 2;
-  }
 }
 
 /**
@@ -117,18 +114,18 @@ function poll_validate($node) {
     // Check for at least two options and validate amount of votes:
     $realchoices = 0;
     // Renumber fields
-    $node->choice = array_values($node->choice);
-    foreach ($node->choice as $i => $choice) {
+    $node->choices = array_values($node->choices);
+    foreach ($node->choices as $i => $choice) {
       if ($choice['chtext'] != '') {
         $realchoices++;
       }
       if ($choice['chvotes'] < 0) {
-        form_set_error("choice][$i][chvotes", t('Negative values are not allowed.'));
+        form_set_error("choices][$i][chvotes", t('Negative values are not allowed.'));
       }
     }
 
     if ($realchoices < 2) {
-      form_set_error("choice][$realchoices][chtext", t('You must fill in at least two choices.'));
+      form_set_error("choices][$realchoices][chtext", t('You must fill in at least two choices.'));
     }
   }
 }
@@ -139,6 +136,11 @@ function poll_validate($node) {
 function poll_form(&$node, $form_state) {
   $admin = user_access('administer nodes');
   $type = node_get_types('type', $node);
+
+  $form = array(
+    '#cache' => TRUE,
+  );
+
   $form['title'] = array(
     '#type' => 'textfield',
     '#title' => check_plain($type->title_label),
@@ -147,56 +149,48 @@ function poll_form(&$node, $form_state) 
     '#weight' => -1
   );
 
-  if (isset($form_state['choices'])) {
-    $choices = $form_state['choices'];
+  if (isset($form_state['choice_count'])) {
+    $choice_count = $form_state['choice_count'];
   }
   else {
-    $choices = max(2, empty($node->choice) ? 5 : count($node->choice));
+    $choice_count = max(2, empty($node->choices) ? 5 : count($node->choices));
   }
 
-  $form['choices'] = array(
-    '#type' => 'hidden',
-    '#value' => $choices,
-  );
-
-  // Poll choices
-  $form['choice'] = array(
+  // Add a wrapper for the choices and more button.
+  $form['choice_wrapper'] = array(
     '#type' => 'fieldset',
     '#title' => t('Choices'),
-    '#prefix' => '<div class="poll-form">',
-    '#suffix' => '</div>',
-    '#tree' => TRUE
+    '#tree' => FALSE,
   );
 
-  // We'll manually set the #parents property of this checkbox so that
-  // it appears in the fieldset visually, but its value won't pollute
-  // the $form_state['values']['choice'] array.
-  $form['choice']['morechoices'] = array(
-    '#type' => 'checkbox',
-    '#parents' => array('morechoices'),
-    '#title' => t('Need more choices'),
-    '#default_value' => 0,
-    '#description' => t("If the amount of boxes above isn't enough, check this box and click the Preview button below to add some more."),
-    '#weight' => 1,
+  // Container for just the poll choices.
+  $form['choice_wrapper']['choices'] = array(
+    '#prefix' => '<div class="poll-form" id="poll-choices">',
+    '#suffix' => '</div>',
   );
 
-  for ($a = 0; $a < $choices; $a++) {
-    $form['choice'][$a]['chtext'] = array(
-      '#type' => 'textfield',
-      '#title' => t('Choice @n', array('@n' => ($a + 1))),
-      '#default_value' => isset($node->choice[$a]) ? $node->choice[$a]['chtext'] : '',
-    );
-
-    if ($admin) {
-      $form['choice'][$a]['chvotes'] = array(
-        '#type' => 'textfield',
-        '#title' => t('Votes for choice @n', array('@n' => ($a + 1))),
-        '#default_value' => isset($node->choice[$a]) ? (int)$node->choice[$a]['chvotes'] : 0,
-        '#size' => 5, '#maxlength' => 7
-      );
-    }
+  // Add the current choices to the form.
+  for ($delta = 0; $delta < $choice_count; $delta++) {
+    $text = isset($node->choices[$delta]['chtext']) ? $node->choices[$delta]['chtext'] : '';
+    $votes = isset($node->choices[$delta]['chvotes']) ? $node->choices[$delta]['chvotes'] : '';
+
+    $form['choice_wrapper']['choices'][$delta] = _poll_choice_form($delta, $text, $votes);
   }
 
+  // We name our button 'poll_more' to avoid conflicts with other modules with
+  // buttons with the id 'more'.
+  $form['choice_wrapper']['poll_more'] = array(
+    '#type' => 'submit',
+    '#value' => t('More choices'),
+    '#description' => t("If the amount of boxes above isn't enough, click here to add more choices."),
+    '#weight' => 1,
+    '#submit' => array('poll_more_choices_submit'), // If no javascript action.
+    '#ahah_path' => 'poll/js',
+    '#ahah_wrapper' => 'poll-choices',
+    '#ahah_method' => 'append',
+    '#ahah_effect' => 'slide',
+  );
+
   // Poll 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'));
@@ -223,18 +217,95 @@ function poll_form(&$node, $form_state) 
   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 poll_more_choices_submit($form, &$form_state) {
+  // Set rebuild to true to make Drupal re-render the form with our changes.
+  $form_state['rebuild'] = TRUE;
+
+  // Make the changes we want to the form state.
+  $form_state['node'] = $form_state['values'];
+  if ($form_state['values']['poll_more']) {
+    $form_state['choice_count'] = count($form_state['values']['choices']) + 5;
+  }
+}
+
+function _poll_choice_form($delta, $value = NULL, $votes = NULL) {
+  $admin = user_access('administer nodes');
+
+  $form = array(
+    '#tree' => TRUE,
+  );
+  
+  // We'll manually set the #parents property of these fields so that
+  // their values appear in the $form_state['values']['choices'] array.
+  $form['chtext'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Choice @n', array('@n' => ($delta + 1))),
+    '#default_value' => isset($value) ? $value : '',
+    '#parents' => array('choices', $delta,'chtext'),
+  );
+
+  if ($admin) {
+    $form['chvotes'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Votes for choice @n', array('@n' => ($delta + 1))),
+      '#default_value' => $votes ? $votes : 0,
+      '#size' => 5,
+      '#maxlength' => 7,
+      '#parents' => array('choices', $delta, 'chvotes'),
+    );
+  }
+  
+  return $form;
+}
+
+/**
+ * Menu callback for AHAH additions.
+ */
+function poll_choice_js() {
+  $delta = count($_POST['choices']);
+
+  // Build our new form element.
+  $form_element = _poll_choice_form($delta);
+  drupal_alter('form', $form_element, array(), 'poll_choice_js');
+
+  // Add the new element to the stored form state. Without adding the element
+  // to the form, Drupal is not aware of this new elements existence and will
+  // not process it. We retreive the cached form, add the element, and resave.
+  $form_cache = cache_get('form_'. $_POST['form_build_id'], 'cache_form');
+  $form_cache->data['choice_wrapper']['choices'][$delta] = $form_element;
+  cache_set('form_'. $_POST['form_build_id'], $form_cache->data, 'cache_form', $form_cache->expire);
+
+  // Render the new output.
+  $form_state = array('submitted' => FALSE);
+  $form_element += array(
+    '#post' => $_POST,
+    '#programmed' => FALSE,
+  );
+  $form_element = form_builder('poll_choice_js', $form_element, $form_state);
+  $output = theme('status_messages') . drupal_render($form_element);
+
+  // Return the new output.
+  print drupal_to_js(array('status' => TRUE, 'data' => $output));
+  exit;
+}
+
 function poll_insert($node) {
   if (!user_access('administer nodes')) {
     // Make sure all votes are 0 initially
-    foreach ($node->choice as $i => $choice) {
-      $node->choice[$i]['chvotes'] = 0;
+    foreach ($node->choices as $i => $choice) {
+      $node->choices[$i]['chvotes'] = 0;
     }
     $node->active = 1;
   }
 
   db_query("INSERT INTO {poll} (nid, runtime, active) VALUES (%d, %d, %d)", $node->nid, $node->runtime, $node->active);
 
-  foreach ($node->choice as $choice) {
+  foreach ($node->choices as $choice) {
     if ($choice['chtext'] != '') {
       db_query("INSERT INTO {poll_choices} (nid, chtext, chvotes, chorder) VALUES (%d, '%s', %d, %d)", $node->nid, $choice['chtext'], $choice['chvotes'], $i++);
     }
@@ -256,6 +327,13 @@ function poll_menu() {
     'type' => MENU_SUGGESTED_ITEM,
   );
 
+  $items['poll/js'] = array(
+    'title' => 'Javascript Choice Form',
+    'page callback' => 'poll_choice_js',
+    'access arguments' => array('access content'),
+    'type' => MENU_CALLBACK,
+  );
+
   $items['poll/cancel/%node'] = array(
     'title' => 'Cancel',
     'page callback' => 'poll_cancel',
@@ -297,7 +375,7 @@ function poll_load($node) {
 
   $result = db_query("SELECT chtext, chvotes, chorder FROM {poll_choices} WHERE nid = %d ORDER BY chorder", $node->nid);
   while ($choice = db_fetch_array($result)) {
-    $poll->choice[$choice['chorder']] = $choice;
+    $poll->choices[$choice['chorder']] = $choice;
   }
 
   // Determine whether or not this user is allowed to vote
@@ -361,8 +439,8 @@ function poll_perm() {
  */
 function poll_teaser($node) {
   $teaser = NULL;
-  if (is_array($node->choice)) {
-    foreach ($node->choice as $k => $choice) {
+  if (is_array($node->choices)) {
+    foreach ($node->choices as $k => $choice) {
       $teaser .= '* '. $choice['chtext'] .'\n';
     }
   }
@@ -372,10 +450,10 @@ function poll_teaser($node) {
 /**
  * Generates the voting form for a poll.
  */
-function poll_view_voting($node, $block) {
-  if ($node->choice) {
+function poll_view_voting($form_state, $node, $block) {
+  if ($node->choices) {
     $list = array();
-    foreach ($node->choice as $i => $choice) {
+    foreach ($node->choices as $i => $choice) {
       $list[$i] = check_plain($choice['chtext']);
     }
     $form['choice'] = array(
@@ -395,6 +473,7 @@ function poll_view_voting($node, $block)
  * Themes the voting form for a poll.
  */
 function theme_poll_view_voting($form) {
+  $output = '';
   $output .= '<div class="poll">';
   $output .= '  <div class="vote-form">';
   $output .= '    <div class="choices">';
@@ -415,13 +494,13 @@ function poll_view_results(&$node, $teas
   // Count the votes and find the maximum
   $total_votes = 0;
   $max_votes = 0;
-  foreach ($node->choice as $choice) {
+  foreach ($node->choices as $choice) {
     $total_votes += $choice['chvotes'];
     $max_votes = max($max_votes, $choice['chvotes']);
   }
 
   $poll_results = '';
-  foreach ($node->choice as $i => $choice) {
+  foreach ($node->choices as $i => $choice) {
     if ($choice['chtext'] != '') {
       $poll_results .= theme('poll_bar', check_plain($choice['chtext']), round($choice['chvotes'] * 100 / max($total_votes, 1)), format_plural($choice['chvotes'], '1 vote', '@count votes'), $block);
     }
@@ -503,7 +582,7 @@ function poll_votes() {
     while ($vote = db_fetch_object($result)) {
       $rows[] = array(
         $vote->name ? theme('username', $vote) : check_plain($vote->hostname),
-        check_plain($node->choice[$vote->chorder]['chtext']));
+        check_plain($node->choices[$vote->chorder]['chtext']));
     }
     $output .= theme('table', $header, $rows);
     $output .= theme('pager', NULL, 20, 0);
@@ -524,9 +603,8 @@ function poll_vote(&$node) {
   if ($node = node_load($nid)) {
     $edit = $_POST;
     $choice = $edit['choice'];
-    $vote = $_POST['vote'];
 
-    if (isset($choice) && isset($node->choice[$choice])) {
+    if (isset($choice) && isset($node->choices[$choice])) {
       if ($node->allowvotes) {
         // Record the vote by this user or host.
         if ($user->uid) {
@@ -540,7 +618,7 @@ function poll_vote(&$node) {
         db_query("UPDATE {poll_choices} SET chvotes = chvotes + 1 WHERE nid = %d AND chorder = %d", $node->nid, $choice);
 
         $node->allowvotes = FALSE;
-        $node->choice[$choice]['chvotes']++;
+        $node->choices[$choice]['chvotes']++;
         cache_clear_all();
         drupal_set_message(t('Your vote was recorded.'));
       }
@@ -578,7 +656,7 @@ function poll_cancel(&$node) {
       // Subtract from the votes.
       db_query("UPDATE {poll_choices} SET chvotes = chvotes - 1 WHERE nid = %d AND chorder = %d", $node->nid, $node->vote);
       $node->allowvotes = TRUE;
-      $node->choice[$node->vote]['chvotes']--;
+      $node->choices[$node->vote]['chvotes']--;
       drupal_set_message(t('Your vote was canceled.'));
     }
     else {
@@ -617,7 +695,7 @@ function poll_view($node, $teaser = FALS
   }
 
   if (!empty($node->allowvotes) && ($block || arg(2) != 'results')) {
-    if ($_POST['op'] == t('Vote')) {
+    if (isset($_POST['op']) && $_POST['op'] == t('Vote')) {
       poll_vote($node);
     }
     $node->content['body'] = array(
@@ -642,7 +720,7 @@ function poll_update($node) {
   db_query('DELETE FROM {poll_votes} WHERE nid = %d', $node->nid);
 
   $i = 0;
-  foreach ($node->choice as $choice) {
+  foreach ($node->choices as $choice) {
     $chvotes = (int)$choice['chvotes'];
     $chtext = $choice['chtext'];
 
