'); // REMARK: drupal_rebuild_form prevents $form_state['values'] from being // available to the form. Which means everything will have to be set in and // retrieved from $form_state['storage']! // If you switch the code in ahah_helper_render() to use the alternatively // provided drupal_rebuild_form_and_apply_form_values(), you can continue // using $form_state['values']. But then any #default_values you set using // $form_state['storage'] will be overridden: if no value existed for a form // item in $_POST, then it will be set to the empty string or zero. Unless you // set your #values to $form_state['storage'], but then $form_state['values'] // has no effect. // As you can see, there currently is no sane way for this to work. So … stick // to storing everything in $form_state['storage'] … until FAPI gets better. //---------------------------------------------------------------------------- // Drupal core hooks. /** * Implementation of hook_menu(). */ function ahah_helper_menu() { $items[AHAH_HELPER_CALLBACK_PATH . '%ahah_helper_form_item'] = array( 'page callback' => 'ahah_helper_render', 'page arguments' => array(1), 'access callback' => TRUE, 'type' => MENU_CALLBACK, ); return $items; } //---------------------------------------------------------------------------- // Menu system callbacks. /** * Wildcard loader for form items; assumes that a passed form item also exists. */ function ahah_helper_form_item_load($form_item) { return $form_item; } //---------------------------------------------------------------------------- // Exposed functions. /** * Helper function to generate a path that corresponds to a tree of parents in * the form structure, or the special "entire form" wildcard. */ function ahah_helper_path($form_item = FALSE) { $path = AHAH_HELPER_CALLBACK_PATH; $path .= (is_array($form_item)) ? implode('][', $form_item) : AHAH_HELPER_PARENTS_ENTIRE_FORM; return $path; } /** * This function records in which file your form definition lives and which * file therefor should be loaded in case of an AHAH callback. * * @param $form * The form structure. * @param $form_state * The form state variable. */ function ahah_helper_register(&$form, &$form_state) { static $js_file_added; // This is an AHAH-driven form, so enable caching. $form['#cache'] = TRUE; // When not set.. try to remember the old action if (!isset($form_state['storage']['old_action'])) { $form_state['storage']['old_action'] = isset($form['#action']) ? $form['#action'] : $_SERVER['REQUEST_URI']; } // If the form action change, restore the old one. if (isset($form_state['storage']['old_action']) && ($form['#action']!=$form_state['storage']['old_action'])) { $form['#action'] = $form_state['storage']['old_action']; } // Register the file. if (!isset($form_state['storage']['#ahah_helper']['file'])) { $menu_item = menu_get_item(); if ($menu_item['file']) { $form_state['storage']['#ahah_helper']['file'] = $menu_item['file']; } } // We remember all values the user last entered, even for fields that are // currently invisible. That way, we can restore the last entered value // when a user switched to Personal usage and back to Company usage. if (isset($form_state['values'])) { // $form_state['storage'] must be initialized for array_smart_merge() to // work. if (!isset($form_state['storage'])) { $form_state['storage'] = array(); } $form_state['storage'] = array_smart_merge($form_state['storage'], $form_state['values']); } // Add our JS file, which has some Drupal core JS overrides. if (!isset($js_file_added)) { drupal_add_js(drupal_get_path('module', 'ahah_helper') . '/ahah_helper.js', 'footer'); $js_file_added = TRUE; } } /** * Submit callback; for any button that's used when JS is disabled (i.e. to * trigger an update of the displayed fields: add/remove fields). */ function ahah_helper_generic_submit($form, &$form_state) { $form_state['rebuild'] = TRUE; } /** * Given a POST of a Drupal form (with both a form_build_id set), this * function rebuilds the form and then only renders the given form item. If no * form item is given, the entire form is rendered. * * Is used directly in a menu callback in ahah_helper's sole menu item. Can * also be used in more advanced menu callbacks, for example to render * multiple form items of the same form and return them separately. * * @param $parents */ function ahah_helper_render($form_item_to_render = FALSE) { $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; // $form_state['storage']['#ahah_helper']['file'] has been set, to know // which file should be loaded. This is necessary because we'll use the form // definition itself rather than the cached $form. if (isset($form_state['storage']['#ahah_helper']['file'])) { require_once($form_state['storage']['#ahah_helper']['file']); } // If the form is being rebuilt due to something else than a pressed button, // e.g. a select that was changed, then $_POST['op'] will be empty. As a // result, Forms API won't be able to detect any pressed buttons. Eventually // it will call _form_builder_ie_cleanup(), which will automatically, yet // inappropriately assign the first in the form as the clicked button. The // reasoning is that since the form has been submitted, a button surely must // have been clicked. This is of course an invalid reasoning in the context // of AHAH forms. // To work around this, we *always* set $form_state['submitted'] to true, // this will prevent _form_builder_ie_cleanup() from assigning a wrong // button. When a button is pressed (thus $_POST['op'] is set), then this // button will still set $form_state['submitted'], // $form_state['submit_handlers'] and $form_state['validate_handlers']. // This problem does not exist when AHAH is disabled, because then the // assumption is true, and then you generally provide a button as an // alternative to the AHAH behavior. $form_state['submitted'] = TRUE; // Continued from the above: when an AHAH update of the form is triggered // without using a button, you generally don't want any validation to kick // in. A typical example is adding new fields, possibly even required ones. // You don't want errors to be thrown at the user until they actually submit // their values. (Well, actually you want to be smart about this: sometimes // you do want instant validation, but that's an even bigger pain to solve // here so I'll leave that for later…) if (!isset($_POST['op'])) { // For the default "{$form_id}_validate" and "{$form_id}_submit" handlers. $form['#validate'] = NULL; $form['#submit'] = NULL; // For customly set #validate and #submit handlers. $form_state['submit_handlers'] = NULL; $form_state['validate_handlers'] = NULL; // Disable #required and #element_validate validation. _ahah_helper_disable_validation($form); } // 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); $form = drupal_rebuild_form($form_id, $form_state, $args, $form_build_id); // Get the form item we want to render. $form_item = _ahah_helper_get_form_item($form, $form_item_to_render); // Get the JS settings so we can merge them. $javascript = drupal_add_js(NULL, NULL, 'header'); $settings = call_user_func_array('array_merge_recursive', $javascript['setting']); drupal_json(array( 'status' => TRUE, 'data' => theme('status_messages') . drupal_render($form_item), 'settings' => array('ahah' => $settings['ahah']), )); } //---------------------------------------------------------------------------- // Private functions. /** * Given a form by reference and an array of parents, return the corresponding * form item by reference. If $parents is FALSE or '