Index: modules/system/system.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.module,v
retrieving revision 1.659
diff -u -r1.659 system.module
--- modules/system/system.module	14 Jan 2009 21:13:41 -0000	1.659
+++ modules/system/system.module	20 Jan 2009 19:21:45 -0000
@@ -388,6 +388,12 @@
     'access callback' => TRUE,
     'type' => MENU_CALLBACK,
   );
+  $items['system/ahah'] = array(
+    'title' => 'AHAH callback',
+    'page callback' => 'form_ahah_callback',
+    'access callback' => TRUE,
+    'type' => MENU_CALLBACK,
+  );
   $items['system/timezone'] = array(
     'title' => 'Time zone',
     'page callback' => 'system_timezone',
Index: modules/poll/poll.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/poll/poll.module,v
retrieving revision 1.284
diff -u -r1.284 poll.module
--- modules/poll/poll.module	8 Jan 2009 08:42:12 -0000	1.284
+++ modules/poll/poll.module	20 Jan 2009 19:21:44 -0000
@@ -118,13 +118,6 @@
     'type' => MENU_LOCAL_TASK,
   );
 
-  $items['poll/js'] = array(
-    'title' => 'Javascript Choice Form',
-    'page callback' => 'poll_choice_js',
-    'access arguments' => array('access content'),
-    'type' => MENU_CALLBACK,
-  );
-
   return $items;
 }
 
@@ -252,9 +245,11 @@
 
   // Add initial or additional choices.
   $existing_delta = $delta;
+  $form['choice_wrapper']['choice']['#poll_new_choices'] = array();
   for ($delta; $delta < $choice_count; $delta++) {
     $key = 'new:'. ($delta - $existing_delta);
     $form['choice_wrapper']['choice'][$key] = _poll_choice_form($key, NULL, '', 0, $weight, $choice_count);
+    $form['choice_wrapper']['choice']['#poll_new_choices'][] = $key;
   }
 
   // We name our button 'poll_more' to avoid conflicts with other modules using
@@ -266,7 +261,7 @@
     '#weight' => 1,
     '#submit' => array('poll_more_choices_submit'), // If no javascript action.
     '#ahah' => array(
-      'path' => 'poll/js',
+      'callback' => 'poll_choice_js',
       'wrapper' => 'poll-choices',
       'method' => 'replace',
       'effect' => 'fade',
@@ -315,7 +310,7 @@
 
   // Make the changes we want to the form state.
   if ($form_state['values']['poll_more']) {
-    $n = $_GET['q'] == 'poll/js' ? 1 : 5;
+    $n = $_GET['q'] == 'system/ahah' ? 1 : 5;
     $form_state['choice_count'] = count($form_state['values']['choice']) + $n;
   }
 }
@@ -361,36 +356,23 @@
 }
 
 /**
- * Menu callback for AHAH additions.
+ * Menu callback for AHAH additions. Render the new poll choices.
  */
-function poll_choice_js() {
-  $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.
+function poll_choice_js($form, $form_state) {
   $choice_form = $form['choice_wrapper']['choice'];
-  unset($choice_form['#prefix'], $choice_form['#suffix']); // Prevent duplicate wrappers.
+
+  // Add a class to new rows to have them fade in separately.
+  foreach ($choice_form['#poll_new_choices'] as $key) {
+    if (isset($choice_form[$key]['#attributes']['class'])) {
+      $choice_form[$key]['#attributes']['class'] .= ' ahah-new-content';
+    }
+    else {
+      $choice_form[$key]['#attributes']['class'] = 'ahah-new-content';
+    }
+  }
+
+  // Prevent duplicate wrappers.
+  unset($choice_form['#prefix'], $choice_form['#suffix']);
   $output = theme('status_messages') . drupal_render($choice_form);
 
   drupal_json(array('status' => TRUE, 'data' => $output));
Index: modules/poll/poll.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/poll/poll.test,v
retrieving revision 1.14
diff -u -r1.14 poll.test
--- modules/poll/poll.test	18 Dec 2008 14:38:37 -0000	1.14
+++ modules/poll/poll.test	20 Jan 2009 19:21:44 -0000
@@ -187,7 +187,7 @@
     // @TODO: the framework should make it possible to submit a form to a
     // different URL than its action or the current. For now, we can just force
     // it.
-    $this->additionalCurlOptions[CURLOPT_URL] = url('poll/js', array('absolute' => TRUE));
+    $this->additionalCurlOptions[CURLOPT_URL] = url('system/ahah', array('absolute' => TRUE));
     $this->drupalPost(NULL, $edit, t('More choices'));
     unset($this->additionalCurlOptions[CURLOPT_URL]);
 
Index: includes/form.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/form.inc,v
retrieving revision 1.314
diff -u -r1.314 form.inc
--- includes/form.inc	19 Jan 2009 10:46:50 -0000	1.314
+++ includes/form.inc	20 Jan 2009 19:21:44 -0000
@@ -1755,6 +1755,39 @@
 }
 
 /**
+ * Menu callback for AHAH callbacks through the #ahah['callback'] FAPI property.
+ */
+function form_ahah_callback() {
+  $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);
+
+  // Get the callback function from the clicked button.
+  $callback = $form_state['clicked_button']['#ahah']['callback'];
+  $callback($form, $form_state);
+}
+
+/**
  * Roll out a single radios element to a list of radios,
  * using the options array as index.
  */
@@ -1871,7 +1904,7 @@
 function form_process_ahah($element) {
   static $js_added = array();
   // Add a reasonable default event handler if none specified.
-  if (isset($element['#ahah']['path']) && !isset($element['#ahah']['event'])) {
+  if (isset($element['#ahah']) && !isset($element['#ahah']['event'])) {
     switch ($element['#type']) {
       case 'submit':
       case 'button':
@@ -1901,12 +1934,12 @@
 
   // Adding the same javascript settings twice will cause a recursion error,
   // we avoid the problem by checking if the javascript has already been added.
-  if (isset($element['#ahah']['path']) && isset($element['#ahah']['event']) && !isset($js_added[$element['#id']])) {
+  if ((isset($element['#ahah']['callback']) || isset($element['#ahah']['path'])) && isset($element['#ahah']['event']) && !isset($js_added[$element['#id']])) {
     drupal_add_js('misc/jquery.form.js', array('weight' => JS_LIBRARY));
     drupal_add_js('misc/ahah.js');
 
     $ahah_binding = array(
-      'url'      => url($element['#ahah']['path']),
+      'url'      => isset($element['#ahah']['callback']) ? url('system/ahah') : url($element['#ahah']['path']),
       'event'    => $element['#ahah']['event'],
       'keypress' => empty($element['#ahah']['keypress']) ? NULL : $element['#ahah']['keypress'],
       'wrapper'  => empty($element['#ahah']['wrapper']) ? NULL : $element['#ahah']['wrapper'],
