Projet

Général

Profil

Paste
Télécharger (13,9 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / ctools / help / wizard.html @ 13755f8d

1
<p>Form wizards, or multi-step forms, are a process by which the user goes through or can use an arbitrary number of different forms to create a single object or perform a single task. Traditionally the multi-step form is difficult in Drupal because there is no easy place to put data in between forms. No longer! The form wizard tool allows a single entry point to easily set up a wizard of multiple forms, provide callbacks  to handle data storage and updates between forms and when forms are completed. This tool pairs well with the <a href="&topic:ctools/object-cache&">object cache</a> tool for storage.</p>
2

    
3
<h2>The form info array</h2>
4
<p>The wizard starts with an array of data that describes all of the forms available to the wizard and sets options for how the wizard will present and control the flow. Here is an example of the $form_info array as used in the delegator module:</p>
5

    
6
<pre>
7
  $form_info = array(
8
    'id' => 'delegator_page',
9
    'path' => "admin/structure/pages/edit/$page_name/%step",
10
    'show trail' => TRUE,
11
    'show back' => TRUE,
12
    'show return' => FALSE,
13
    'next callback' => 'delegator_page_add_subtask_next',
14
    'finish callback' => 'delegator_page_add_subtask_finish',
15
    'return callback' => 'delegator_page_add_subtask_finish',
16
    'cancel callback' => 'delegator_page_add_subtask_cancel',
17
    'order' => array(
18
      'basic' => t('Basic settings'),
19
      'argument' => t('Argument settings'),
20
      'access' => t('Access control'),
21
      'menu' => t('Menu settings'),
22
      'multiple' => t('Task handlers'),
23
    ),
24
    'forms' => array(
25
      'basic' => array(
26
        'form id' => 'delegator_page_form_basic'
27
      ),
28
      'access' => array(
29
        'form id' => 'delegator_page_form_access'
30
      ),
31
      'menu' => array(
32
        'form id' => 'delegator_page_form_menu'
33
      ),
34
      'argument' => array(
35
        'form id' => 'delegator_page_form_argument'
36
      ),
37
      'multiple' => array(
38
        'form id' => 'delegator_page_argument_form_multiple'
39
      ),
40
    ),
41
  );
42
</pre>
43

    
44
<p>The above array starts with an <strong>id</strong> which is used to identify the wizard in various places and a <strong>path</strong> which is needed to redirect to the next step between forms. It then creates some <strong>settings</strong> which control which pieces are displayed. In this case, it displays a form trail and a 'back' button, but not the 'return' button. Then there are the <strong>wizard callbacks</strong> which allow the wizard to act appropriately when forms are submitted. Finally it contains a <strong>list of forms</strong> and their <strong>order</strong> so that it knows which forms to use and what order to use them by default. Note that the keys in the order and list of forms match; that key is called the <strong>step</strong> and is used to identify each individual step of the wizard.</p>
45

    
46
<p>Here is a full list of every item that can be in the form info array:</p>
47

    
48
<dl>
49
<dt>id</dt>
50
<dd>An id for wizard. This is used like a hook to automatically name <strong>callbacks</strong>, as well as a form step's form building function. It is also used in trail theming.</dd>
51

    
52
<dt>path</dt>
53
<dd>The path to use when redirecting between forms. <strong>%step</strong> will be replaced with the key for the form.</dd>
54

    
55
<dt>return path</dt>
56
<dd>When a form is complete, this is the path to go to. This is required if the 'return' button is shown and not using AJAX. It is also used for the 'Finish' button. If it is not present and needed, the cancel path will also be checked.</dd>
57

    
58
<dt>cancel path</dt>
59
<dd>When a form is canceled, this is the path to go to. This is required if the 'cancel' is shown and not using AJAX.</dd>
60

    
61
<dt>show trail</dt>
62
<dd>If set to TRUE, the form trail will be shown like a breadcrumb at the top of each form. Defaults to FALSE.</dd>
63

    
64
<dt>show back</dt>
65
<dd>If set to TRUE, show a back button on each form. Defaults to FALSE.</dd>
66

    
67
<dt>show return</dt>
68
<dd>If set to TRUE, show a return button. Defaults to FALSE.</dd>
69

    
70
<dt>show cancel</dt>
71
<dd>If set to TRUE, show a cancel button. Defaults to FALSE.</dd>
72

    
73
<dt>back text</dt>
74
<dd>Set the text of the 'back' button. Defaults to t('Back').</dd>
75

    
76
<dt>next text</dt>
77
<dd>Set the text of the 'next' button. Defaults to t('Continue').</dd>
78

    
79
<dt>return text</dt>
80
<dd>Set the text of the 'return' button. Defaults to t('Update and return').</dd>
81

    
82
<dt>finish text</dt>
83
<dd>Set the text of the 'finish' button. Defaults to t('Finish').</dd>
84

    
85
<dt>cancel text</dt>
86
<dd>Set the text of the 'cancel' button. Defaults to t('Cancel').</dd>
87

    
88
<dt>ajax</dt>
89
<dd>Turn on AJAX capabilities, using CTools' ajax.inc. Defaults to FALSE.</dd>
90

    
91
<dt>modal</dt>
92
<dd>Put the wizard in the modal tool. The modal must already be open and called from an ajax button for this to work, which is easily accomplished using functions provided by the modal tool.</dd>
93

    
94
<dt>ajax render</dt>
95
<dd>A callback to display the rendered form via ajax. This is not required if using the modal tool, but is required otherwise since ajax by itself does not know how to render the results. Params: &$form_state, $output.</dd>
96

    
97
<dt>finish callback</dt>
98
<dd>The function to call when a form is complete and the finish button has been clicked. This function should finalize all data. Params: &$form_state. 
99
Defaults to $form_info['id']._finish if function exists.
100
</dd>
101

    
102
<dt>cancel callback</dt>
103
<dd>The function to call when a form is canceled by the user. This function should clean up any data that is cached. Params: &$form_state. 
104
Defaults to $form_info['id']._cancel if function exists.</dd>
105

    
106
<dt>return callback</dt>
107
<dd>The function to call when a form is complete and the return button has been clicked. This is often the same as the finish callback. Params: &$form_state. 
108
Defaults to $form_info['id']._return if function exists.</dd>
109

    
110
<dt>next callback</dt>
111
<dd>The function to call when the next button has been clicked. This function should take the submitted data and cache it for later use by the finish callback. Params: &$form_state. 
112
Defaults to $form_info['id']._next if function exists.</dd>
113

    
114
<dt>order</dt>
115
<dd>An optional array of forms, keyed by the step, which represents the default order the forms will be displayed in. If not set, the forms array will control the order. Note that submit callbacks can override the order so that branching logic can be used.</dd>
116

    
117
<dt>forms</dt>
118
<dd>An array of form info arrays, keyed by step, describing every form available to the wizard. If order array isn't set, the wizard will use this to set the default order. Each array contains:
119
  <dl>
120
  <dt>form id</dt>
121
  <dd>
122
    The id of the form, as used in the Drupal form system. This is also the name of the function that represents the form builder. 
123
    Defaults to $form_info['id']._.$step._form.
124
  </dd>
125
  
126
  <dt>include</dt>
127
  <dd>The name of a file to include which contains the code for this form. This makes it easy to include the form wizard in another file or set of files. This must be the full path of the file, so be sure to use drupal_get_path() when setting this. This can also be an array of files if multiple files need to be included.</dd>
128
  
129
  <dt>title</dt>
130
  <dd>The title of the form, to be optionally set via drupal_get_title. This is required when using the modal if $form_state['title'] is not set.</dd>
131
  </dl>
132
</dd>
133
</dl>
134

    
135
<h2>Invoking the form wizard</h2>
136
<p>Your module should create a page callback via hook_menu, and this callback should contain an argument for the step. The path that leads to this page callback should be the same as the 'path' set in the $form_info array.</p>
137

    
138
<p>The page callback should set up the $form_info, and figure out what the default step should be if no step is provided (note that the wizard does not do this for you; you MUST specify a step). Then invoke the form wizard:</p>
139

    
140
<pre>
141
  $form_state = array();
142
  ctools_include('wizard');
143
  $output = ctools_wizard_multistep_form($form_info, $step, $form_state);
144
</pre>
145

    
146
<p>If using AJAX or the modal, This part is actually done! If not, you have one more small step:</p>
147

    
148
<pre>
149
  return $output;
150
</pre>
151

    
152
<h2>Forms and their callbacks</h2>
153
<p>Each form within the wizard is a complete, independent form using Drupal's Form API system. It has a form builder callback as well as submit and validate callbacks and can be form altered. The primary difference between these forms and a normal Drupal form is that the submit handler should not save any data. Instead, it should make any changes to a cached object (usually placed on the $form_state) and only the _finish or _return handler should actually save any real data.</p>
154

    
155
<p>How you handle this is completely up to you. The recommended best practice is to use the CTools Object cache, and a good way to do this is to write a couple of wrapper functions around the cache that look like these example functions:</p>
156

    
157
<pre>
158
/**
159
 * Get the cached changes to a given task handler.
160
 */
161
function delegator_page_get_page_cache($name) {
162
  ctools_include('object-cache');
163
  $cache = ctools_object_cache_get('delegator_page', $name);
164
  if (!$cache) {
165
    $cache = delegator_page_load($name);
166
    $cache->locked = ctools_object_cache_test('delegator_page', $name);
167
  }
168

    
169
  return $cache;
170
}
171

    
172
/**
173
 * Store changes to a task handler in the object cache.
174
 */
175
function delegator_page_set_page_cache($name, $page) {
176
  ctools_include('object-cache');
177
  $cache = ctools_object_cache_set('delegator_page', $name, $page);
178
}
179

    
180
/**
181
 * Remove an item from the object cache.
182
 */
183
function delegator_page_clear_page_cache($name) {
184
  ctools_include('object-cache');
185
  ctools_object_cache_clear('delegator_page', $name);
186
}
187
</pre>
188

    
189
<p>Using these wrappers, when performing a get_cache operation, it defaults to loading the real object. It then checks to see if another user has this object cached using the ctools_object_cache_test() function, which automatically sets a lock (which can be used to prevent writes later on).</p>
190

    
191
<p>With this set up, the _next, _finish and _cancel callbacks are quite simple:</p>
192

    
193
<pre>
194
/**
195
 * Callback generated when the add page process is finished.
196
 */
197
function delegator_page_add_subtask_finish(&$form_state) {
198
  $page = &$form_state['page'];
199

    
200
  // Create a real object from the cache
201
  delegator_page_save($page);
202

    
203
  // Clear the cache
204
  delegator_page_clear_page_cache($form_state['cache name']);
205
}
206

    
207
/**
208
 * Callback generated when the 'next' button is clicked.
209
 *
210
 * All we do here is store the cache.
211
 */
212
function delegator_page_add_subtask_next(&$form_state) {
213
  // Update the cache with changes.
214
  delegator_page_set_page_cache($form_state['cache name'], $form_state['page']);
215
}
216

    
217
/**
218
 * Callback generated when the 'cancel' button is clicked.
219
 *
220
 * All we do here is clear the cache.
221
 */
222
function delegator_page_add_subtask_cancel(&$form_state) {
223
  // Update the cache with changes.
224
  delegator_page_clear_page_cache($form_state['cache name']);
225
}
226
</pre>
227

    
228
<p>All that's needed to tie this together is to understand how the changes made it into the cache in the first place. This happened in the various form _submit handlers, which made changes to $form_state['page'] based upon the values set in the form:</p>
229

    
230
<pre>
231
/**
232
 * Store the values from the basic settings form.
233
 */
234
function delegator_page_form_basic_submit($form, &$form_state) {
235
  if (!isset($form_state['page']->pid) && empty($form_state['page']->import)) {
236
    $form_state['page']->name = $form_state['values']['name'];
237
  }
238

    
239
  $form_state['page']->admin_title = $form_state['values']['admin_title'];
240
  $form_state['page']->path = $form_state['values']['path'];
241

    
242
  return $form;
243
}
244
</pre>
245

    
246
<p>No database operations were made during this _submit, and that's a very important distinction about this system.</p>
247

    
248
<h3>Proper handling of back button</h3>
249
<p>When using <strong>'show back' => TRUE</strong> the cached data should be assigned to the <em>#default_value</em> form property. Otherwise when the user goes back to the previous step the forms default values instead of his (cached) input is used.</p>
250

    
251
<pre>
252
/**
253
 * Form builder function for wizard.
254
 */
255
function wizardid_step2_form($form, &$form_state) {
256
  $form_state['my data'] = my_module_get_cache($form_state['cache name']);
257
  $form['example'] = array(
258
    '#type' => 'radios',
259
    '#title' => t('Title'),
260
    '#default_value' => $form_state['my data']->example ? $form_state['my data']->example : default,
261
    '#options' => array(
262
      'default' => t('Default'),
263
      'setting1' => t('Setting1'),
264
    ),
265
  );
266

    
267
  return $form;
268
}
269

    
270
/**
271
 * Submit handler to prepare needed values for storing in cache.
272
 */
273
function wizardid_step2_form_submit($form, &$form_state) {
274
  $form_state['my data']->example = $form_state['values']['example'];
275
}
276
</pre>
277

    
278
<p>The data is stored in the <em>my data</em> object on submitting. If the user goes back to this step the cached <em>my data</em> is used as the default form value. The function <em>my_module_get_cache()</em> is like the cache functions explained above.</p>
279

    
280
<h2>Required fields, cancel and back buttons</h2>
281
<p>If you have required fields in your forms, the back and cancel buttons will not work as expected since validation of the form will fail. You can add the following code to the top of your form validation to avoid this problem:</p>
282
<pre>
283
/**
284
 * Validation handler for step2 form
285
 */
286
function wizardid_step2_form_validate(&$form, &$form_state) {
287
  // if the clicked button is anything but the normal flow
288
  if ($form_state['clicked_button']['#next'] != $form_state['next']) {
289
    drupal_get_messages('error');
290
    form_set_error(NULL, '', TRUE);
291
    return;
292
  }
293
  // you form validation goes here
294
  // ...
295
}
296
</pre>
297

    
298
<h2>Wizard for anonymous users</h2>
299
<p>If you are creating a wizard which is be used by anonymous users, you might run into some issues with drupal's caching for anonymous users. You can circumvent this by using hook_init and telling drupal to not cache your wizard pages:</p>
300
<pre>
301
/**
302
 * Implementation of hook init
303
 */
304
function mymodule_init() {
305
  // if the path leads to the wizard
306
  if (drupal_match_path($_GET['q'], 'path/to/your/wizard/*')) {
307
    // set cache to false
308
    $GLOBALS['conf']['cache'] = FALSE;   
309
  }
310
}
311
</pre>