/[drupal]/contributions/modules/ahah_helper/ahah_helper.module
ViewVC logotype

Diff of /contributions/modules/ahah_helper/ahah_helper.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph | View Patch Patch

revision 1.2, Wed Nov 19 16:25:29 2008 UTC revision 1.3, Sat Feb 14 15:48:36 2009 UTC
# Line 1  Line 1 
1  <?php  <?php
2  // $Id: ahah_helper.module,v 1.1 2008/08/29 18:58:21 wimleers Exp $  // $Id: ahah_helper.module,v 1.2 2008/11/19 16:25:29 wimleers Exp $
3    
4  define('AHAH_HELPER_CALLBACK', 'ahah_helper/');  
5    define('AHAH_HELPER_CALLBACK_PATH', 'ahah_helper/');
6    define('AHAH_HELPER_PARENTS_ENTIRE_FORM', '<form>');
7    
8    
9    // REMARK: drupal_rebuild_form prevents $form_state['values'] from being
10    // available to the form. Which means everything will have to be set in and
11    // retrieved from $form_state['storage']!
12    // If you switch the code in ahah_helper_render() to use the alternatively
13    // provided drupal_rebuild_form_and_apply_form_values(), you can continue
14    // using $form_state['values']. But then any #default_values you set using
15    // $form_state['storage'] will be overridden: if no value existed for a form
16    // item in $_POST, then it will be set to the empty string or zero. Unless you
17    // set your #values to $form_state['storage'], but then $form_state['values']
18    // has no effect.
19    // As you can see, there currently is no sane way for this to work. So … stick
20    // to storing everything in $form_state['storage'] … until FAPI gets better.
21    
22    
23  //----------------------------------------------------------------------------  //----------------------------------------------------------------------------
# Line 11  define('AHAH_HELPER_CALLBACK', 'ahah_hel Line 27  define('AHAH_HELPER_CALLBACK', 'ahah_hel
27   * Implementation of hook_menu().   * Implementation of hook_menu().
28   */   */
29  function ahah_helper_menu() {  function ahah_helper_menu() {
30    $items[AHAH_HELPER_CALLBACK . '%ahah_helper_form_item'] = array(    $items[AHAH_HELPER_CALLBACK_PATH . '%ahah_helper_form_item'] = array(
31      'page callback'    => 'ahah_helper_render',      'page callback'    => 'ahah_helper_render',
32      'page arguments'   => array(1),      'page arguments'   => array(1),
33      'access callback'  => TRUE,      'access callback'  => TRUE,
34      'type'             => MENU_CALLBACK,      'type'             => MENU_CALLBACK,
35    );    );
   
36    return $items;    return $items;
37  }  }
38    
39    
40  //----------------------------------------------------------------------------  //----------------------------------------------------------------------------
41  // Menu system callbacks.  // Menu system callbacks.
42    
# Line 36  function ahah_helper_form_item_load($for Line 52  function ahah_helper_form_item_load($for
52  // Exposed functions.  // Exposed functions.
53    
54  /**  /**
55   * Builds the path to render a certain form item.   * Helper function to generate a path that corresponds to a tree of parents in
56   *   * the form structure, or the special "entire form" wildcard.
  * @param $parents  
  *   An array of parrents by which the wanted form item is identified.  
57   */   */
58  function ahah_helper_path($parents = FALSE) {  function ahah_helper_path($form_item = FALSE) {
59    if (is_array($parents)) {    $path = AHAH_HELPER_CALLBACK_PATH;
60      $path = AHAH_HELPER_CALLBACK . implode('][', $parents);    $path .= (is_array($form_item)) ? implode('][', $form_item) : AHAH_HELPER_PARENTS_ENTIRE_FORM;
   }  
   else {  
     $path = AHAH_HELPER_CALLBACK . '<form>';  
   }  
   
61    return $path;    return $path;
62  }  }
63    
# Line 56  function ahah_helper_path($parents = FAL Line 65  function ahah_helper_path($parents = FAL
65   * This function records in which file your form definition lives and which   * This function records in which file your form definition lives and which
66   * file therefor should be loaded in case of an AHAH callback.   * file therefor should be loaded in case of an AHAH callback.
67   *   *
68     * @param $form
69     *   The form structure.
70   * @param $form_state   * @param $form_state
71   *   The form state variable.   *   The form state variable.
72   */   */
73  function ahah_helper_register(&$form, &$form_state) {  function ahah_helper_register(&$form, &$form_state) {
74      static $js_file_added;
75    
76    // This is an AHAH-driven form, so enable caching.    // This is an AHAH-driven form, so enable caching.
77    $form['#cache'] = TRUE;    $form['#cache'] = TRUE;
78    
79    // Register the file.    // Register the file.
80    if (!isset($form_state['storage']['#file'])) {    if (!isset($form_state['storage']['#ahah_helper']['file'])) {
81      $menu_item = menu_get_item();      $menu_item = menu_get_item();
82      if ($menu_item['file']) {      if ($menu_item['file']) {
83        $form_state['storage']['#file'] = $menu_item['file'];        $form_state['storage']['#ahah_helper']['file'] = $menu_item['file'];
84      }      }
85    }    }
86    
87      // We remember all values the user last entered, even for fields that are
88      // currently invisible. That way, we can restore the last entered value
89      // when a user switched to Personal usage and back to Company usage.
90      if (isset($form_state['values'])) {
91        // $form_state['storage'] must be initialized for array_smart_merge() to
92        // work.
93        if (!isset($form_state['storage'])) {
94          $form_state['storage'] = array();
95        }
96        $form_state['storage'] = array_smart_merge($form_state['storage'], $form_state['values']);
97      }
98    
99    // Add our JS file, which has some Drupal core JS overrides.    // Add our JS file, which has some Drupal core JS overrides.
100    drupal_add_js(drupal_get_path('module', 'ahah_helper') . '/ahah_helper.js', 'footer');    if (!isset($js_file_added)) {
101        drupal_add_js(drupal_get_path('module', 'ahah_helper') . '/ahah_helper.js', 'footer');
102        $js_file_added = TRUE;
103      }
104  }  }
105    
106  /**  /**
# Line 84  function ahah_helper_generic_submit($for Line 112  function ahah_helper_generic_submit($for
112  }  }
113    
114  /**  /**
115   * Given a POST of a Drupal form (with both a form_id and a form_build_id   * Given a POST of a Drupal form (with both a form_build_id set), this
116   * defined), this function rebuilds the form and then only renders the given   * function rebuilds the form and then only renders the given form item. If no
117   * form item. If no form item is given, the entire form is rendered.   * form item is given, the entire form is rendered.
118   *   *
119   * Is used directly in a menu callback in ahah_helper's sole menu item. Can   * Is used directly in a menu callback in ahah_helper's sole menu item. Can
120   * also be used in more advanced menu callbacks, for example to render   * also be used in more advanced menu callbacks, for example to render
# Line 95  function ahah_helper_generic_submit($for Line 123  function ahah_helper_generic_submit($for
123   * @param $parents   * @param $parents
124   */   */
125  function ahah_helper_render($form_item_to_render = FALSE) {  function ahah_helper_render($form_item_to_render = FALSE) {
126    $form_state = array('submitted' => FALSE, 'values' => $_POST);    $form_state = array('storage' => NULL, 'submitted' => FALSE);
   $form_id = $_POST['form_id'];  
127    $form_build_id = $_POST['form_build_id'];    $form_build_id = $_POST['form_build_id'];
128    
129    // We use the form cache, but solely to retrieve $form_state['storage'] and    // Get the form from the cache.
130    // $form['#parameters'].    $form = form_get_cache($form_build_id, $form_state);
131    // The ahah_helper module has set $form_state['storage']['#file'] to know    $args = $form['#parameters'];
132      $form_id = array_shift($args);
133      // We will run some of the submit handlers so we need to disable redirecting.
134      $form['#redirect'] = FALSE;
135      // We need to process the form, prepare for that by setting a few internals
136      // variables.
137      $form['#post'] = $_POST;
138      $form['#programmed'] = FALSE;
139      $form_state['post'] = $_POST;
140    
141      // $form_state['storage']['#ahah_helper']['file'] has been set, to know
142    // which file should be loaded. This is necessary because we'll use the form    // which file should be loaded. This is necessary because we'll use the form
143    // definition itself rather than the cached $form.    // definition itself rather than the cached $form.
144    $form = form_get_cache($form_build_id, $form_state);    if (isset($form_state['storage']['#ahah_helper']['file'])) {
145    if (isset($form_state['storage']['#file'])) {      require_once($form_state['storage']['#ahah_helper']['#file']);
     require_once($form_state['storage']['#file']);  
146    }    }
147    
148    // Detect which form items already exist, so we can detect form items that    // If the form is being rebuilt due to something else than a pressed button,
149    // will be displayed to the user for the first time and disable validation    // e.g. a select that was changed, then $_POST['op'] will be empty. As a
150    // for them.    // result, Forms API won't be able to detect any pressed buttons. Eventually
151    $existing_form_items = (isset($form_state['storage']['#existing_form_items'])) ? $form_state['storage']['#existing_form_items'] : array();    // it will call _form_builder_ie_cleanup(), which will automatically, yet
152    _ahah_helper_detect_form_items($form, $existing_form_items);    // inappropriately assign the first in the form as the clicked button. The
153    $form_state['storage']['#existing_form_items'] = $existing_form_items;    // reasoning is that since the form has been submitted, a button surely must
154      // have been clicked. This is of course an invalid reasoning in the context
155    // Rebuild the form.    // of AHAH forms.
156    $args = $form['#parameters'];    // To work around this, we *always* set $form_state['submitted'] to true,
157    array_shift($args); // We don't have to pass the form id again.    // this will prevent _form_builder_ie_cleanup() from assigning a wrong
158    $form = drupal_update_form($form_id, $form_state, $args, $form_build_id, $form_item_to_render, $existing_form_items);    // button. When a button is pressed (thus $_POST['op'] is set), then this
159    $form['#programmed'] = FALSE;    // button will still set $form_state['submitted'],
160      // $form_state['submit_handlers'] and $form_state['validate_handlers'].
161      // This problem does not exist when AHAH is disabled, because then the
162      // assumption is true, and then you generally provide a button as an
163      // alternative to the AHAH behavior.
164      $form_state['submitted'] = TRUE;
165      // Continued from the above: when an AHAH update of the form is triggered
166      // without using a button, you generally don't want any validation to kick
167      // in. A typical example is adding new fields, possibly even required ones.
168      // You don't want errors to be thrown at the user until they actually submit
169      // their values. (Well, actually you want to be smart about this: sometimes
170      // you do want instant validation, but that's an even bigger pain to solve
171      // here so I'll leave that for later…)
172      if (!isset($_POST['op'])) {
173        // For the default "{$form_id}_validate" and "{$form_id}_submit" handlers.
174        $form['#validate'] = NULL;
175        $form['#submit'] = NULL;
176        // For customly set #validate and #submit handlers.
177        $form_state['submit_handlers'] = NULL;
178        $form_state['validate_handlers'] = NULL;
179        // Disable #required and #element_validate validation.
180        _ahah_helper_disable_validation($form);
181      }
182    
183      // Build, validate and if possible, submit the form.
184      drupal_process_form($form_id, $form, $form_state);
185      // This call recreates the form relying solely on the form_state that the
186      // drupal_process_form set up.
187      //$form = drupal_rebuild_form($form_id, $form_state, $args, $form_build_id);
188      $form = drupal_rebuild_form($form_id, $form_state, $args, $form_build_id);
189    
190    // We *do* save the form to the cache, so modules that use "D6 core style"    // Get the form item we want to render.
191    // AHAH updates still work.    $form_item = _ahah_helper_get_form_item($form, $form_item_to_render);
   form_set_cache($form_build_id, $form, $form_state);  
192    
193    // Get the JS settings so we can merge them.    // Get the JS settings so we can merge them.
194    $javascript = drupal_add_js(NULL, NULL, 'header');    $javascript = drupal_add_js(NULL, NULL, 'header');
195      $settings = call_user_func_array('array_merge_recursive', $javascript['setting']);
   // Get the form item that we will render.  
   $form_item = _ahah_helper_get_form_item($form, $form_item_to_render);  
   
   // Only keep relevant errors, if any.  
   $errors = array_values(_ahah_helper_collect_relevant_errors($form_item));  
   $_SESSION['messages']['error'] = $errors;  
   if (count($errors) == 0) {  
     unset($_SESSION['messages']['error']);  
   }  
196    
197    drupal_json(array(    drupal_json(array(
198      'status'   => TRUE,      'status'   => TRUE,
199      'data'     => theme('status_messages') . drupal_render($form_item),      'data'     => theme('status_messages') . drupal_render($form_item),
200      'settings' => call_user_func_array('array_merge_recursive', $javascript['setting']),      'settings' => array('ahah' => $settings['ahah']),
201    ));    ));
202  }  }
203    
 /**  
  * Retrieves a form, caches it and builds the form.  
  *  
  * Almost a clone of drupal_rebuild_form, but not entirely: we don't process  
  * the form, we only rebuild it (thereby NOT invoking validate and submit  
  * handlers)  
  */  
 function drupal_update_form($form_id, &$form_state, $args, $form_build_id, $form_item_to_render = FALSE, $existing_form_items = array()) {  
   // Remove the first argument. This is $form_id.when called from  
   // drupal_get_form and the original $form_state when called from some AHAH  
   // callback. Neither is needed. After that, put in the current state.  
   $args[0] = &$form_state;  
   // And the form_id.  
   array_unshift($args, $form_id);  
   $form = call_user_func_array('drupal_retrieve_form', $args);  
   
   $form['#build_id'] = $form_build_id;  
   drupal_prepare_form($form_id, $form, $form_state);  
   
   // Now, we cache the form structure so it can be retrieved later for  
   // validation. If $form_state['storage'] is populated, we'll also cache  
   // it so that it can be used to resume complex multi-step processes.  
   form_set_cache($form_build_id, $form, $form_state);  
   
   // Set POST data.  
   $form['#post'] = $_POST;  
   $form['#programmed'] = TRUE;  
   
   // Build the form, so we can render it, or continue processing it (call  
   // validate and/or submit handlers)  
   $form = form_builder($form_id, $form, $form_state);  
   
   // Disable validation for form items that are being displayed for the first  
   // time.  
   _ahah_helper_new_form_items_disable_validation($form, $existing_form_items);  
   
   // Validate the form.  
   drupal_validate_form($form_id, $form, $form_state);  
   
   // We validated the form, but we'll only prevent submit handlers from being  
   // executed if there were validation errors relevant to the form item that  
   // will be rendered!  
   // Call submit handlers. Only designed to be used for submit handlers that  
   // trigger partial updates of the form, not for an actual submit.  
   $errors = _ahah_helper_collect_relevant_errors(_ahah_helper_get_form_item($form, $form_item_to_render));  
   if (!$errors && count($form_state['submit_handlers'])) {  
     form_execute_handlers('submit', $form, $form_state);  
   
     $form = call_user_func_array('drupal_retrieve_form', $args);  
   
     $form['#build_id'] = $form_build_id;  
     drupal_prepare_form($form_id, $form, $form_state);  
   
     // Now, we cache the form structure so it can be retrieved later for  
     // validation. If $form_state['storage'] is populated, we'll also cache  
     // it so that it can be used to resume complex multi-step processes.  
     form_set_cache($form_build_id, $form, $form_state);  
   
     // Set POST data.  
     $form['#post'] = $_POST;  
     $form['#programmed'] = TRUE;  
   
     // Build the form.  
     $form = form_builder($form_id, $form, $form_state);  
   }  
   
   return $form;  
 }  
   
204    
205  //----------------------------------------------------------------------------  //----------------------------------------------------------------------------
206  // Private functions.  // Private functions.
# Line 230  function drupal_update_form($form_id, &$ Line 216  function drupal_update_form($form_id, &$
216   *   An array of parrents by which the wanted form item is identified, or   *   An array of parrents by which the wanted form item is identified, or
217   *   imploded with ']['.   *   imploded with ']['.
218   * @return   * @return
219   *   The wanted form item.   *   The requested form item.
220   */   */
221  function &_ahah_helper_get_form_item(&$form, $parents = FALSE) {  function &_ahah_helper_get_form_item(&$form, $parents = FALSE) {
222    if ($parents == '<form>' || !$parents) {    if ($parents == AHAH_HELPER_PARENTS_ENTIRE_FORM || !$parents) {
223      return $form;      return $form;
224    }    }
225    
# Line 263  function &_ahah_helper_get_form_item(&$f Line 249  function &_ahah_helper_get_form_item(&$f
249    }    }
250  }  }
251    
252  /**  function _ahah_helper_disable_validation(&$form) {
253   * Helper function to detect form items. If you want to check if a form item    foreach (element_children($form) as $child) {
254   * does not exist, you should verify that it doesn't exist in the      $form[$child]['#validated'] = TRUE;
255   * $existing_form_items parameter.      _ahah_helper_disable_validation(&$form[$child]);
  *  
  * @param $form  
  *   A form.  
  * @param $existing_form_items  
  *   An array of existing form items, basically a clone of $form, but without  
  *   the form items' properties and previously existing, but now deleted items  
  *   are persistent.  
  */  
 function _ahah_helper_detect_form_items($form, &$existing_form_items) {  
   foreach(element_children($form) as $child) {  
     $existing_form_items[$child]['exists'] = TRUE;  
     _ahah_helper_detect_form_items($form[$child], $existing_form_items[$child]);  
256    }    }
257  }  }
258    
259    
260    //----------------------------------------------------------------------------
261    // Additional PHP functions.
262    
263  /**  /**
264   * Helper function to disable form items for form items that exist for the   * Smarter version of array_merge_recursive: overwrites scalar values.
  * first time in the form.  
265   *   *
266   * @param $form   * From: http://www.php.net/manual/en/function.array-merge-recursive.php#82976.
  *   A form.  
  * @param $existing_form_items  
  *   An array of existing form items, as generated by  
  *   _ahah_helper_detect_form_items().  
267   */   */
268  function _ahah_helper_new_form_items_disable_validation(&$form, $existing_form_items) {  function array_smart_merge($array, $override) {
269    foreach (element_children($form) as $child) {    if (is_array($array) && is_array($override)) {
270      if (!isset($existing_form_items[$child]['exists'])) {      foreach ($override as $k => $v) {
271        $form[$child]['#validated'] = TRUE;        if (isset($array[$k]) && is_array($v) && is_array($array[$k])) {
272        $form[$child]['#first_time'] = TRUE;          $array[$k] = array_smart_merge($array[$k], $v);
273          }
274          else {
275            $array[$k] = $v;
276          }
277      }      }
     _ahah_helper_new_form_items_disable_validation($form[$child], $existing_form_items[$child]);  
278    }    }
279      return $array;
280  }  }
281    
 /**  
  * Helper function to filter get all form errors that are relevant to the  
  * given form item and its children.  
  *  
  * @param $form_item  
  *   The form item for which to collect the relevant errors.  
  * @return  
  *   An array of errors, if any.  
  */  
 function _ahah_helper_collect_relevant_errors($form_item) {  
   if (empty($form_item)) {  
     return array();  
   }  
282    
283    $errors = array();  //----------------------------------------------------------------------------
284    $error = form_get_error($form_item);  // Demonstrative functions.
285    
286    if ($error) {  function drupal_rebuild_form_and_apply_form_values($form_id, &$form_state, $args, $form_build_id = NULL) {
287      $key = implode('][', $form_item['#parents']);    // Remove the first argument. This is $form_id.when called from
288      $errors[$key] = $error;    // drupal_get_form and the original $form_state when called from some AHAH
289    }    // callback. Neither is needed. After that, put in the current state.
290    foreach (element_children($form_item) as $child) {    $args[0] = &$form_state;
291      $errors = array_merge($errors, _ahah_helper_collect_relevant_errors($form_item[$child]));    // And the form_id.
292      array_unshift($args, $form_id);
293      $form = call_user_func_array('drupal_retrieve_form', $args);
294    
295      if (!isset($form_build_id)) {
296        // We need a new build_id for the new version of the form.
297        $form_build_id = 'form-'. md5(mt_rand());
298    }    }
299    return $errors;    $form['#build_id'] = $form_build_id;
300      drupal_prepare_form($form_id, $form, $form_state);
301    
302      // Now, we cache the form structure so it can be retrieved later for
303      // validation. If $form_state['storage'] is populated, we'll also cache
304      // it so that it can be used to resume complex multi-step processes.
305      form_set_cache($form_build_id, $form, $form_state);
306    
307      /**
308       * UNTIL HERE THIS FUNCTION IS IDENTICAL TO drupal_rebuild_form().
309       */
310    
311      // Clear out all post data, as we don't want the previous step's
312      // data to pollute this one and trigger validate/submit handling,
313      // then process the form for rendering.
314      //$_POST = array();
315      //$form['#post'] = array();
316      //drupal_process_form($form_id, $form, $form_state);
317    
318      // Set POST data.
319      $form['#post'] = $_POST;
320      $form = form_builder($form_id, $form, $form_state);
321    
322      return $form;
323  }  }

Legend:
Removed from v.1.2  
changed lines
  Added in v.1.3

  ViewVC Help
Powered by ViewVC 1.1.2