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

Contents of /contributions/modules/ahah_helper/ahah_helper.module

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


Revision 1.4 - (show annotations) (download) (as text)
Fri Sep 18 15:09:07 2009 UTC (2 months, 1 week ago) by wimleers
Branch: MAIN
CVS Tags: HEAD
Changes since 1.3: +2 -2 lines
File MIME type: text/x-php
Fixed #415560 by cha0s: ['#ahah_helper']['#file'] is non-existent!
1 <?php
2 // $Id: ahah_helper.module,v 1.3 2009/02/14 15:48:36 wimleers Exp $
3
4
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 //----------------------------------------------------------------------------
24 // Drupal core hooks.
25
26 /**
27 * Implementation of hook_menu().
28 */
29 function ahah_helper_menu() {
30 $items[AHAH_HELPER_CALLBACK_PATH . '%ahah_helper_form_item'] = array(
31 'page callback' => 'ahah_helper_render',
32 'page arguments' => array(1),
33 'access callback' => TRUE,
34 'type' => MENU_CALLBACK,
35 );
36 return $items;
37 }
38
39
40 //----------------------------------------------------------------------------
41 // Menu system callbacks.
42
43 /**
44 * Wildcard loader for form items; assumes that a passed form item also exists.
45 */
46 function ahah_helper_form_item_load($form_item) {
47 return $form_item;
48 }
49
50
51 //----------------------------------------------------------------------------
52 // Exposed functions.
53
54 /**
55 * Helper function to generate a path that corresponds to a tree of parents in
56 * the form structure, or the special "entire form" wildcard.
57 */
58 function ahah_helper_path($form_item = FALSE) {
59 $path = AHAH_HELPER_CALLBACK_PATH;
60 $path .= (is_array($form_item)) ? implode('][', $form_item) : AHAH_HELPER_PARENTS_ENTIRE_FORM;
61 return $path;
62 }
63
64 /**
65 * This function records in which file your form definition lives and which
66 * file therefor should be loaded in case of an AHAH callback.
67 *
68 * @param $form
69 * The form structure.
70 * @param $form_state
71 * The form state variable.
72 */
73 function ahah_helper_register(&$form, &$form_state) {
74 static $js_file_added;
75
76 // This is an AHAH-driven form, so enable caching.
77 $form['#cache'] = TRUE;
78
79 // Register the file.
80 if (!isset($form_state['storage']['#ahah_helper']['file'])) {
81 $menu_item = menu_get_item();
82 if ($menu_item['file']) {
83 $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.
100 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 /**
107 * Submit callback; for any button that's used when JS is disabled (i.e. to
108 * trigger an update of the displayed fields: add/remove fields).
109 */
110 function ahah_helper_generic_submit($form, &$form_state) {
111 $form_state['rebuild'] = TRUE;
112 }
113
114 /**
115 * Given a POST of a Drupal form (with both a form_build_id set), this
116 * function rebuilds the form and then only renders the given form item. If no
117 * 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
120 * also be used in more advanced menu callbacks, for example to render
121 * multiple form items of the same form and return them separately.
122 *
123 * @param $parents
124 */
125 function ahah_helper_render($form_item_to_render = FALSE) {
126 $form_state = array('storage' => NULL, 'submitted' => FALSE);
127 $form_build_id = $_POST['form_build_id'];
128
129 // Get the form from the cache.
130 $form = form_get_cache($form_build_id, $form_state);
131 $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
143 // definition itself rather than the cached $form.
144 if (isset($form_state['storage']['#ahah_helper']['file'])) {
145 require_once($form_state['storage']['#ahah_helper']['file']);
146 }
147
148 // If the form is being rebuilt due to something else than a pressed button,
149 // e.g. a select that was changed, then $_POST['op'] will be empty. As a
150 // result, Forms API won't be able to detect any pressed buttons. Eventually
151 // it will call _form_builder_ie_cleanup(), which will automatically, yet
152 // inappropriately assign the first in the form as the clicked button. The
153 // 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 // of AHAH forms.
156 // To work around this, we *always* set $form_state['submitted'] to true,
157 // this will prevent _form_builder_ie_cleanup() from assigning a wrong
158 // button. When a button is pressed (thus $_POST['op'] is set), then this
159 // 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 // Get the form item we want to render.
191 $form_item = _ahah_helper_get_form_item($form, $form_item_to_render);
192
193 // Get the JS settings so we can merge them.
194 $javascript = drupal_add_js(NULL, NULL, 'header');
195 $settings = call_user_func_array('array_merge_recursive', $javascript['setting']);
196
197 drupal_json(array(
198 'status' => TRUE,
199 'data' => theme('status_messages') . drupal_render($form_item),
200 'settings' => array('ahah' => $settings['ahah']),
201 ));
202 }
203
204
205 //----------------------------------------------------------------------------
206 // Private functions.
207
208 /**
209 * Given a form by reference and an array of parents, return the corresponding
210 * form item by reference. If $parents is FALSE or '<form>', the entire $form
211 * array will be returned. If it doesn't exist, return NULL.
212 *
213 * @param $form
214 * A form.
215 * @param $parents
216 * An array of parrents by which the wanted form item is identified, or
217 * imploded with ']['.
218 * @return
219 * The requested form item.
220 */
221 function &_ahah_helper_get_form_item(&$form, $parents = FALSE) {
222 if ($parents == AHAH_HELPER_PARENTS_ENTIRE_FORM || !$parents) {
223 return $form;
224 }
225
226 // Allow $parents to be either an array of the element's parents or the name
227 // of an element.
228 if (is_string($parents)) {
229 if (strpos($parents, ']') !== FALSE) {
230 $parents = explode('][', $parents);
231 }
232 else {
233 $parents = array($parents);
234 }
235 }
236
237 // Recursively seek the form element.
238 if (count($parents)) {
239 $parent = array_shift($parents);
240 if (isset($form[$parent])) {
241 return _ahah_helper_get_form_item($form[$parent], $parents);
242 }
243 else {
244 return NULL;
245 }
246 }
247 else {
248 return $form;
249 }
250 }
251
252 function _ahah_helper_disable_validation(&$form) {
253 foreach (element_children($form) as $child) {
254 $form[$child]['#validated'] = TRUE;
255 _ahah_helper_disable_validation(&$form[$child]);
256 }
257 }
258
259
260 //----------------------------------------------------------------------------
261 // Additional PHP functions.
262
263 /**
264 * Smarter version of array_merge_recursive: overwrites scalar values.
265 *
266 * From: http://www.php.net/manual/en/function.array-merge-recursive.php#82976.
267 */
268 function array_smart_merge($array, $override) {
269 if (is_array($array) && is_array($override)) {
270 foreach ($override as $k => $v) {
271 if (isset($array[$k]) && is_array($v) && is_array($array[$k])) {
272 $array[$k] = array_smart_merge($array[$k], $v);
273 }
274 else {
275 $array[$k] = $v;
276 }
277 }
278 }
279 return $array;
280 }
281
282
283 //----------------------------------------------------------------------------
284 // Demonstrative functions.
285
286 function drupal_rebuild_form_and_apply_form_values($form_id, &$form_state, $args, $form_build_id = NULL) {
287 // Remove the first argument. This is $form_id.when called from
288 // 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 $args[0] = &$form_state;
291 // 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 $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 }

  ViewVC Help
Powered by ViewVC 1.1.2