jeffp-wizardly 0.1.8.4 → 0.1.8.5
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +362 -136
- data/lib/wizardly/wizard/button.rb +16 -5
- data/lib/wizardly/wizard/configuration.rb +12 -12
- data/lib/wizardly/wizard/configuration/methods.rb +50 -33
- data/lib/wizardly/wizard/page.rb +8 -4
- data/lib/wizardly/wizard/text_helpers.rb +6 -3
- metadata +2 -2
data/README.rdoc
CHANGED
@@ -4,10 +4,6 @@
|
|
4
4
|
|
5
5
|
== Resources
|
6
6
|
|
7
|
-
Examples
|
8
|
-
|
9
|
-
* http://github.com/jeffp/wizardly-examples
|
10
|
-
|
11
7
|
Source
|
12
8
|
|
13
9
|
* git://github.com/jeffp/wizardly.git
|
@@ -15,6 +11,10 @@ Source
|
|
15
11
|
Install
|
16
12
|
|
17
13
|
* sudo gem install jeffp-wizardly --source=http://http://gems.github.com
|
14
|
+
|
15
|
+
Examples
|
16
|
+
|
17
|
+
* http://github.com/jeffp/wizardly-examples
|
18
18
|
|
19
19
|
== Description
|
20
20
|
|
@@ -25,26 +25,52 @@ produces the scaffolding and controller of a multi-step wizard.
|
|
25
25
|
Features include:
|
26
26
|
* Model-based definition
|
27
27
|
* Wizard controller macro
|
28
|
-
* Wizard
|
29
|
-
*
|
30
|
-
*
|
31
|
-
* Page and
|
28
|
+
* Wizard scaffold generator
|
29
|
+
* Simple wizard page helpers
|
30
|
+
* Default and customizable wizard buttons
|
31
|
+
* Page and wizard callbacks
|
32
|
+
* High in Convention
|
33
|
+
* Rich in Configuration
|
34
|
+
|
35
|
+
== Example
|
36
|
+
|
37
|
+
Create a working controller wizard for any model in 3 steps. Here's how:
|
38
|
+
|
39
|
+
Step 1: Define validation groups for your model.
|
40
|
+
|
41
|
+
class User < ActiveRecord::Base
|
42
|
+
validation_group :step1, :fields=>[:first_name, :last_name]
|
43
|
+
validation_group :step2, :fields=>[:age, :gender]
|
44
|
+
...
|
45
|
+
end
|
46
|
+
|
47
|
+
Step 2: Tell your controller to act 'wizardly'.
|
48
|
+
|
49
|
+
class SignupController < ApplicationController
|
50
|
+
act_wizardly_for :user
|
51
|
+
end
|
52
|
+
|
53
|
+
Step 3: Generate a 'wizardly' scaffold for your controller.
|
54
|
+
|
55
|
+
./script/generate wizardly_scaffold signup
|
56
|
+
|
57
|
+
You are ready to go: start your servers.
|
58
|
+
|
59
|
+
General usage and configuration of wizardly follows. See the examples at
|
60
|
+
|
61
|
+
http://github.com/jeffp/wizardly-examples
|
32
62
|
|
33
63
|
== Setup
|
34
64
|
|
35
65
|
Put the following in your application's config block in config/environment.rb
|
36
66
|
|
37
|
-
config.gem 'jeffp-wizardly'
|
67
|
+
config.gem 'jeffp-wizardly'
|
38
68
|
|
39
69
|
and run the install gems rake task on your application
|
40
70
|
|
41
71
|
rake gems:install
|
42
72
|
|
43
|
-
|
44
|
-
|
45
|
-
./script/generate wizardly_app
|
46
|
-
|
47
|
-
== Recommendations
|
73
|
+
=== Setup Recommendations
|
48
74
|
|
49
75
|
Wizardly uses sessions. It is recommended you use a session store other than the
|
50
76
|
default cookies store to eliminate the 4KB size restriction. To use the active
|
@@ -59,83 +85,103 @@ And set up the sessions table in the database
|
|
59
85
|
|
60
86
|
Use the MemCached session store for higher performance requirements.
|
61
87
|
|
62
|
-
==
|
63
|
-
|
64
|
-
Create a working controller wizard for any model in 3 steps. Here's how:
|
65
|
-
|
66
|
-
Step 1: Define validation groups for your model.
|
88
|
+
== Inspecting A Wizard Controller
|
67
89
|
|
68
|
-
|
69
|
-
|
70
|
-
validation_group :step2, :fields=>[:age, :gender]
|
71
|
-
...
|
72
|
-
end
|
90
|
+
This is optional, but for any rails app, you can install wizardly rake tasks
|
91
|
+
by running
|
73
92
|
|
74
|
-
|
93
|
+
./script/generate wizardly_app
|
75
94
|
|
76
|
-
|
77
|
-
act_wizardly_for :user
|
78
|
-
end
|
95
|
+
Once installed, you can run the config rake task to inspect a wizardly controller
|
79
96
|
|
80
|
-
|
97
|
+
rake wizardly:config name=controller_name
|
81
98
|
|
82
|
-
|
99
|
+
The controller_name you give it must have a +act_wizardly_for+ declaration for
|
100
|
+
an existing active record model and database table. (Don't forget to migrate
|
101
|
+
your database) The rake task will display the wizard's buttons, pages and fields
|
102
|
+
along with other configuration information.
|
83
103
|
|
84
|
-
|
104
|
+
== The Basic Wizard
|
85
105
|
|
86
|
-
|
106
|
+
Wizardly creates a controller action for each validation group
|
107
|
+
in the model. Likewise, the wizardly scaffold generates a view named for each
|
108
|
+
corresponding action.
|
87
109
|
|
88
|
-
|
110
|
+
=== Action Instance Variables
|
89
111
|
|
90
|
-
|
112
|
+
The actions automatically instantiate a few instance variables: @step, @wizard,
|
113
|
+
@title and @{model_name}. @step contains the symbol corresponding to
|
114
|
+
the action name and is used in the wizard view helpers. @wizard references
|
115
|
+
the wizard configuration object and is primarily used by the view helpers.
|
116
|
+
@title contains the string name of the action by default and is used in the scaffolding. Finally and
|
117
|
+
most importantly of all, the action instantiates an instance of the model. For
|
118
|
+
instance, if the model is :account_user for AccountUser class, the action instantiates
|
119
|
+
an @account_user instance.
|
91
120
|
|
92
|
-
===
|
121
|
+
=== Flow
|
93
122
|
|
94
|
-
The
|
95
|
-
declared in the model.
|
96
|
-
the
|
97
|
-
the
|
123
|
+
The page-to-page flow of the wizard is assumed to occur in the order that the
|
124
|
+
validation groups were declared in the model. A 'next' button progresses to the
|
125
|
+
next page in the order; a 'back' button progresses to the previous page in the order.
|
126
|
+
The first page in the order will have no 'back' button. The final page in the order
|
127
|
+
will no 'next' button, but instead have a 'finish' button which completes the wizard
|
128
|
+
by saving the model instance to the database. Every page will have a 'cancel' button.
|
98
129
|
|
99
|
-
|
100
|
-
rake task. First you'll need to install it if you already have not
|
130
|
+
=== Automatic Page Progression Tracking
|
101
131
|
|
102
|
-
|
132
|
+
The default progression is linear, progressing from one page to the next in the defined
|
133
|
+
order. In the default case, the 'back' button simply jumps back to the previous page.
|
134
|
+
However, the developer can create more complex flows by jumping and skipping pages in
|
135
|
+
the action callback methods. Wizardly tracks the forward progression
|
136
|
+
through a wizard and back tracks accordingly for the 'back' button.
|
103
137
|
|
104
|
-
|
105
|
-
'act_wizardly_for' macro
|
138
|
+
=== Wizard Entrance Guarding
|
106
139
|
|
107
|
-
|
140
|
+
Since there is an assumed order for the wizard, entering the wizard at a later page
|
141
|
+
makes no sense. Wizardly, however, guards entry to the wizard. If the user has
|
142
|
+
never entered the wizard, then entry is always redirected to the first page. If the
|
143
|
+
user has partially completed the wizard and is allowed to re-enter the wizard to
|
144
|
+
complete it, wizardly only allows entry at or before the page previously completed.
|
108
145
|
|
109
|
-
|
146
|
+
The guarding feature can be disabled for testing or other purposes.
|
110
147
|
|
148
|
+
== Basic Usage
|
111
149
|
|
112
150
|
=== Completing and Canceling
|
113
151
|
|
114
152
|
Once a user has entered a wizard, there are two ways they can leave, either by
|
115
|
-
completing or canceling the wizard.
|
153
|
+
completing or canceling the wizard. The redirects for these two cases need to
|
154
|
+
be defined. If neither are defined, then the HTTP_REFERER on first entry of the
|
155
|
+
wizard is used as the redirect for both cases.
|
116
156
|
|
117
|
-
The
|
118
|
-
completing or canceling the wizard
|
119
|
-
|
120
|
-
raises a RedirectNotDefined error. Let's remedy this problem
|
121
|
-
|
157
|
+
The 3-step example given above is very simple. In fact, no redirects have been defined for
|
158
|
+
completing or canceling the wizard so the wizardly controller
|
159
|
+
will use the HTTP_REFERER for both cases. If there is no HTTP_REFERER, the controller
|
160
|
+
raises a RedirectNotDefined error. Let's remedy this problem by adding redirect options
|
161
|
+
to the macro.
|
122
162
|
|
123
163
|
class SignupController < ApplicationController
|
124
|
-
act_wizardly_for :user,
|
164
|
+
act_wizardly_for :user,
|
165
|
+
:completed=>'/main/finished',
|
125
166
|
:canceled=>{:controller=>:main, :action=>:canceled}
|
126
167
|
end
|
127
168
|
|
128
169
|
Now whether the user completes or cancels the wizard, the controller knows
|
129
|
-
how to redirect.
|
130
|
-
|
170
|
+
how to redirect. If both canceling and completing the wizard redirect to the
|
171
|
+
same place, the following option takes care of both
|
131
172
|
|
132
173
|
class SignupController < ApplicationController
|
133
174
|
act_wizardly_for :user, :redirect=>'/main'
|
134
175
|
end
|
135
176
|
|
136
|
-
|
177
|
+
These redirects are static, and may not suffice for all cases. In the event
|
178
|
+
that the redirect needs to be determined at run-time, the developer can use a
|
179
|
+
number of page callback macros to intercede in the wizard logic and redirect
|
180
|
+
as needed on canceling or completion. See Callbacks below.
|
181
|
+
|
182
|
+
==== Controller Options
|
137
183
|
|
138
|
-
Here's a list of options
|
184
|
+
Here's a list of options for the +act_wizardly_for+ controller macro
|
139
185
|
|
140
186
|
:completed => '/main/finished'
|
141
187
|
:canceled => {:controller=>'main', :action=>'canceled'}
|
@@ -145,26 +191,26 @@ Here's a list of options you can use in the macro
|
|
145
191
|
:persist_model => {:once|:per_page}
|
146
192
|
:form_data => {:sandbox|:session}
|
147
193
|
|
148
|
-
Setting the :skip option to +true+ tells the scaffold helpers to include
|
194
|
+
Setting the :skip option to +true+ tells the scaffold helpers to include a skip button on each page.
|
149
195
|
The :mask_fields options tells the scaffold generator which fields to generate as 'type=password' fields.
|
150
|
-
|
151
|
-
|
196
|
+
Remaining options are explained below.
|
152
197
|
|
153
198
|
==== Preserving Form Field Data
|
154
199
|
|
155
|
-
The :form_data option controls how the form data is preserved
|
156
|
-
|
157
|
-
:session,
|
200
|
+
The :form_data option controls how the form data is preserved when a user
|
201
|
+
leaves or navigates to a page outside of the wizard before completing the wizard.
|
202
|
+
The default setting, :session, maintains the form data until the wizard is complete regardless of
|
158
203
|
whether the user leaves the wizard and returns later. The form
|
159
204
|
data is preserved for the life of the session or until the user completes the wizard.
|
160
205
|
|
161
|
-
The other
|
206
|
+
The other setting, :sandbox, clears the form data whenever
|
162
207
|
the user leaves the wizard before the wizard is complete. This includes pressing
|
163
208
|
a :cancel button, a hyperlink or plainly navigating somewhere else.
|
164
|
-
Upon returning to the wizard, the form is reset and the user starts fresh
|
209
|
+
Upon returning to the wizard, the form is reset and the user starts fresh from the
|
210
|
+
first page.
|
165
211
|
|
166
212
|
The form data is always cleared once the user has completed the wizard and the
|
167
|
-
|
213
|
+
record has been committed.
|
168
214
|
|
169
215
|
==== Guarding Wizard Entry
|
170
216
|
|
@@ -182,7 +228,7 @@ contrary, if :form_data is set to :sandbox, entry is always guarded, and once th
|
|
182
228
|
leaves the wizard, entry may only occur at the initial page (as the form data has
|
183
229
|
been reset).
|
184
230
|
|
185
|
-
====
|
231
|
+
==== Persisting The Model
|
186
232
|
|
187
233
|
The :persist_model option controls how the model is saved, either :once or :per_page.
|
188
234
|
The default option :once, only saves the model when the wizard is completed, by the
|
@@ -205,110 +251,182 @@ can add :skip functionality to all pages by adding an option to the macro
|
|
205
251
|
end
|
206
252
|
|
207
253
|
You can create, customize and change buttons for any controller and any page.
|
208
|
-
See the
|
254
|
+
See the Button Customization section.
|
209
255
|
|
210
256
|
|
211
257
|
=== Callbacks
|
212
258
|
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
259
|
+
There are two kinds of callbacks -- general and action. Action callbacks occur
|
260
|
+
based on a controller action. General callbacks occur based on a general wizard event.
|
261
|
+
|
262
|
+
==== Action Callback Macros
|
263
|
+
|
264
|
+
Action callback macros are available for injecting code and control into the wizard
|
265
|
+
logic for each page. For instance, say our model
|
266
|
+
declares the following validation group
|
267
|
+
|
268
|
+
validation_group :step4, :fields=>[:username, :password, :password_confirmation]
|
269
|
+
|
270
|
+
In the case that there's a validation error, we'd like to clear the password fields before re-rendering
|
271
|
+
when one of the fields is invalid. For this purpose, we can use the on_errors action callback macro.
|
218
272
|
|
219
273
|
class SignupController < ApplicationController
|
220
274
|
act_wizardly_for :user, :redirect=>'/main'
|
221
275
|
|
222
276
|
on_errors(:step4) do
|
223
|
-
#clear the password field if errors
|
224
277
|
@user[:password] = ''
|
225
278
|
@user[:password_confirmation] = ''
|
226
279
|
end
|
227
280
|
end
|
228
281
|
|
229
|
-
|
230
|
-
validation_group.
|
231
|
-
|
232
|
-
Every page has a set of callback macros. Here's the list.
|
282
|
+
Here's the list of action callbacks macros that the developer can use for any action
|
233
283
|
|
234
|
-
on_get(:step) # called just after creating model instance and before rendering a GET request
|
235
|
-
on_post(:step) # called just after creating the model instance for a POST request
|
236
|
-
on_errors(:step) # called after on_post callback if the form is invalid according to validation group
|
237
|
-
on_next(:step) # called on a valid form when :next button is pressed
|
238
284
|
on_back(:step) # called when the :back button is pressed
|
239
|
-
on_cancel(:step) # called when the :cancel button is pressed
|
240
|
-
on_finish(:step) # called on a valid form when the :finish button is pressed
|
241
285
|
on_skip(:step) # called when the skip button is pressed
|
286
|
+
on_cancel(:step) # called when the :cancel button is pressed
|
287
|
+
on_next(:step) # called when the :next button is pressed for a valid form (post only)
|
288
|
+
on_finish(:step) # called when the :finish button is pressed for a valid form (post only)
|
242
289
|
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
for instance if our model is :user, the instance variable is '@user'. Each
|
247
|
-
wizard page action also defines a @title, @description and @step instance variable.
|
248
|
-
These are primarily used for scaffolding. The @step holds the name of the current
|
249
|
-
validation group, ie. :step1, step2, ... in our examples.
|
290
|
+
on_post(:step) # called at the beginning of a POST request
|
291
|
+
on_get(:step) # called before rendering a GET request
|
292
|
+
on_errors(:step) # called before re-rendering the form after form invalidation (on a POST request)
|
250
293
|
|
251
|
-
|
294
|
+
The first five callbacks are related to the wizard buttons. Each
|
252
295
|
callback gives the developer a chance to intervene before the impending render or
|
253
|
-
redirect caused by a button click
|
296
|
+
redirect caused by a button click.
|
297
|
+
|
298
|
+
Each callback macro consists of a list of actions or the symbol :all as the argument
|
299
|
+
and a block defining the code. For example
|
300
|
+
|
301
|
+
on_post(:step1) do
|
302
|
+
...
|
303
|
+
end
|
304
|
+
on_back(:step2, :step3, :step4) do
|
305
|
+
...
|
306
|
+
end
|
307
|
+
on_get(:all) do
|
308
|
+
...
|
309
|
+
end
|
310
|
+
|
311
|
+
Passing a non-existing action name raises a CallbackError.
|
312
|
+
|
313
|
+
The block in a callback is called in the controller context, thus giving it access to all
|
314
|
+
controller variables and methods including the model instance, controller methods
|
315
|
+
like +redirect_to+, and controller variables like params, request, response and session.
|
316
|
+
The model instance variable is available for all action callback macros.
|
317
|
+
|
318
|
+
==== The Wizard's Action Request Cycle
|
254
319
|
|
255
|
-
|
256
|
-
|
320
|
+
The wizard page is first requested through a GET to an action. In this GET request,
|
321
|
+
the wizard action creates the instance variables @step, @title and @wizard, and
|
322
|
+
builds the model instance variable @{model_name}. Action callbacks may occur in the
|
323
|
+
following order.
|
257
324
|
|
258
|
-
|
325
|
+
#GET request callback order
|
259
326
|
|
260
|
-
on_back
|
261
|
-
on_skip
|
262
|
-
on_cancel
|
263
|
-
...on_'custom_button'...
|
327
|
+
on_back, on_skip, on_cancel
|
264
328
|
on_get
|
265
329
|
render_wizard_form
|
266
330
|
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
is
|
272
|
-
|
273
|
-
|
331
|
+
If the wizard detects that a back, skip or cancel button has been pressed, the
|
332
|
+
corresponding callback is called if implemented. If the developer does nothing
|
333
|
+
in the callbacks, default handlers will redirect accordingly and the on_get and
|
334
|
+
render_wizard_form callbacks will not be called (Note: render_wizard_form is a
|
335
|
+
general callback and is included for completeness) Once rendered, the page is
|
336
|
+
presented to the user with a selection of fields and
|
337
|
+
wizard buttons for posting the form.
|
338
|
+
|
339
|
+
When the form data is returned by a POST request, the action creates the instance
|
340
|
+
variables and builds the model instance using the form data. The on_post callback
|
341
|
+
is called at the beginning of the post, then the wizard checks for back, skip and
|
342
|
+
cancel buttons. If neither of those buttons were pressed, it proceeds to validate
|
343
|
+
the form, calling the on_errors callback if form validation fails, re-rendering and
|
344
|
+
sending the page with errors. If validation succeeds, the action determines whether
|
345
|
+
the POST request signifies a 'next' or a 'finish' request and calls the corresponding
|
346
|
+
callback if implemented. The callback order for a POST request is as indicated below.
|
347
|
+
(The on_completed callback is a general callback called once the wizard is completed and
|
348
|
+
the model has been committed to the database)
|
349
|
+
|
350
|
+
#POST request callback order
|
274
351
|
|
275
352
|
on_post
|
276
|
-
on_back
|
277
|
-
on_skip
|
278
|
-
on_cancel
|
279
|
-
...on_'custom_button'...
|
353
|
+
on_back, on_skip, on_cancel
|
280
354
|
on_errors
|
281
|
-
render_wizard_form
|
355
|
+
render_wizard_form # only if errors
|
282
356
|
on_next
|
283
357
|
on_finish
|
284
|
-
|
285
|
-
In contrast to the on_get callback, the on_post callback occurs first. It is an
|
286
|
-
opportunity to modify the model instance values (the model instance has already been
|
287
|
-
created from the post parameters). The on_post method does not permit any rendering
|
288
|
-
or redirecting since the back, skip and cancel buttons have not been evaluated and
|
289
|
-
may require precedence over flow control. on_errors is called only for an invalid
|
290
|
-
form and the render callback (see below) is called only if there are errors. Either
|
291
|
-
on_next or on_finish are called if the form is valid. on_next is the default if a
|
292
|
-
'Finish' button is not pressed. on_next by default moves to the next page, while
|
293
|
-
on_finish saves the model and redirects to the :completed redirect.
|
294
|
-
|
295
|
-
The argument of the callback macro indicates for which pages the block should be
|
296
|
-
called. You can indicate as many pages or :all as shown.
|
358
|
+
on_completed # only if completed
|
297
359
|
|
298
|
-
|
299
|
-
|
360
|
+
==== Rendering with on_get and on_errors
|
361
|
+
|
362
|
+
The on_get and on_errors callbacks are called just before rendering the form. These
|
363
|
+
callbacks are a good place to declare extra variables needed to render the form.
|
364
|
+
|
365
|
+
on_get(:step2) do
|
366
|
+
setup_step2_form
|
300
367
|
end
|
301
|
-
|
302
|
-
|
368
|
+
|
369
|
+
on_errors(:step2) do
|
370
|
+
setup_step2_form
|
303
371
|
end
|
304
|
-
|
305
|
-
|
372
|
+
|
373
|
+
def setup_step2_form
|
374
|
+
@contact_options = [%w(phone 1), %w(email 2), %w(mail, 3)]
|
375
|
+
end
|
376
|
+
|
377
|
+
If you have a variable that goes in every page, render_wizard_form is called
|
378
|
+
for every page.
|
379
|
+
|
380
|
+
==== Modifying form data with on_post
|
381
|
+
|
382
|
+
The on_post callback is the first callback in the chain of a POST request and
|
383
|
+
is a good place to modify form input such as adding capitalization to a form.
|
384
|
+
Modification should happen through the model instance variable and not the
|
385
|
+
controller's params variable.
|
386
|
+
|
387
|
+
Redirecting and rendering are not allowed in the on_post callback. Doing so will
|
388
|
+
raise an error.
|
389
|
+
|
390
|
+
==== Modifying Flow with on_next
|
391
|
+
|
392
|
+
on_next is called when a form has posted validly and the wizard is ready to move
|
393
|
+
to the next page. This is a good opportunity to modify form flow for more complex
|
394
|
+
forms by redirecting and skipping pages. See the STI Model example in
|
395
|
+
http://github.com/jeffp/wizardly-examples for an example of a wizard with two paths
|
396
|
+
based on user input.
|
397
|
+
|
398
|
+
on_next(:step1) do
|
399
|
+
redirect_to(:action=>:step3) if @contributor.is_volunteer?
|
400
|
+
end
|
401
|
+
on_next(:step2) do
|
402
|
+
redirect_to(:action=>:step4)
|
306
403
|
end
|
307
404
|
|
308
|
-
|
405
|
+
In the above example, :step 3 is a page for a volunteer, and :step2 is a page for
|
406
|
+
a non-volunteer.
|
407
|
+
|
408
|
+
==== Completing the wizard with on_next
|
309
409
|
|
410
|
+
Sometimes you may want to complete the wizard based on user input rather than a
|
411
|
+
'finish' button. You can call the +complete_wizard+ method. See the completing
|
412
|
+
wizard programmatically example below.
|
310
413
|
|
311
|
-
|
414
|
+
==== Final modifications with on_finish
|
415
|
+
|
416
|
+
on_finish callback is called when the user presses a 'finish' button and form
|
417
|
+
validation is successful (for the validation_group). on_finish is a good place
|
418
|
+
to make any final modifications before the model instance is committed to the
|
419
|
+
database.
|
420
|
+
|
421
|
+
Alternatively, if you want to stop the completion process, you can call the
|
422
|
+
+do_not_complete+ method in the on_finish callback.
|
423
|
+
|
424
|
+
=== General Callback Macros
|
425
|
+
|
426
|
+
There are two general callback macros: render_wizard_form and on_completed. These
|
427
|
+
are not tied to any action or set of actions.
|
428
|
+
|
429
|
+
==== render_wizard_form
|
312
430
|
|
313
431
|
For anyone needing to handle rendering in a special way, wizardly provides a render
|
314
432
|
call back for this.
|
@@ -328,7 +446,22 @@ call back for this.
|
|
328
446
|
end
|
329
447
|
end
|
330
448
|
|
331
|
-
|
449
|
+
==== Dynamic redirecting with on_completed
|
450
|
+
|
451
|
+
The on_completed callback is called once the model instance has been committed to
|
452
|
+
the database. If you need any fields generated from committing the model, such
|
453
|
+
as an ID, to redirect on completion, the
|
454
|
+
on_completed callback is the place to do this.
|
455
|
+
|
456
|
+
on_completed do
|
457
|
+
redirect_to post_path(@post)
|
458
|
+
end
|
459
|
+
|
460
|
+
=== Helper methods
|
461
|
+
|
462
|
+
Wizardly provides some helper methods which are useful in callbacks.
|
463
|
+
|
464
|
+
==== Completing the Wizard Programmatically
|
332
465
|
|
333
466
|
Perhaps you want to complete a wizard based off of a test instead of a button
|
334
467
|
click. You can do this in your callbacks by calling the +complete_wizard+ method.
|
@@ -344,6 +477,18 @@ You can change the redirect dynamically by passing it to the method.
|
|
344
477
|
|
345
478
|
complete_wizard(some_model_path)
|
346
479
|
|
480
|
+
==== Rerendering from a callback
|
481
|
+
|
482
|
+
Sometimes it is useful to re-render the form and send the response to the user immediately.
|
483
|
+
Wizardly provides a +render_and_return+ method for this purpose. If a callback is
|
484
|
+
triggered from a POST request, and the callback needs to re-render, this is the method.
|
485
|
+
|
486
|
+
on_back(:step2) do
|
487
|
+
if (something_mandatory_not_selected)
|
488
|
+
flash[:notice] = 'Please make a selection before going back'
|
489
|
+
render_and_return
|
490
|
+
end
|
491
|
+
end
|
347
492
|
|
348
493
|
=== Creating Scaffolds
|
349
494
|
|
@@ -368,9 +513,90 @@ You can create a scaffold using image_submit_tag by doing the following:
|
|
368
513
|
|
369
514
|
Default button images are provided under the public/images/wizardly/ directory.
|
370
515
|
|
371
|
-
==
|
516
|
+
== Button Customization
|
517
|
+
|
518
|
+
The buttons used by the wizard and the view helpers can be customized as follows.
|
519
|
+
|
520
|
+
=== Changing Names of Default Wizard Buttons
|
521
|
+
|
522
|
+
The wizard supports five default button actions-- :next, :back, :cancel, :skip and
|
523
|
+
:finish. The default names for the buttons are the corresponding capitalized
|
524
|
+
string -- 'Next', 'Back', 'Cancel', 'Skip' and 'Finish'.
|
525
|
+
|
526
|
+
The default button names can be customized in the +act_wizardly_for+ code block
|
527
|
+
|
528
|
+
class UserSignupController < ApplicationController
|
529
|
+
act_wizardly_for :user, :redirect=>'/main/index' do
|
530
|
+
change_button(:back).name_to('Previous Page')
|
531
|
+
change_button(:finish).name_to('Save and Return')
|
532
|
+
end
|
533
|
+
end
|
534
|
+
|
535
|
+
With the above code, the 'Back' button will now be named 'Previous Page' and the 'Finish'
|
536
|
+
button is renamed to the longer name 'Save and Return'. Notice that symbols are used
|
537
|
+
to determine the default button, but the customized name is passed. A new symbol
|
538
|
+
representing the change is created for each change, hence, for our buttons above :back
|
539
|
+
is now referred to as :previous_page and :finish is now referred to as :save_and_return.
|
540
|
+
|
541
|
+
=== Action Callbacks for Customized Buttons
|
542
|
+
|
543
|
+
Changing the name of a default button also changes the name of its callback macro.
|
544
|
+
For instance, the on_back() and on_finish() callback macros are replaced by the following
|
545
|
+
for the above example
|
546
|
+
|
547
|
+
on_previous_page(:step3) do
|
548
|
+
...
|
549
|
+
end
|
550
|
+
on_save_and_return(:step3) do
|
551
|
+
...
|
552
|
+
end
|
553
|
+
|
554
|
+
=== Creating New Buttons
|
555
|
+
|
556
|
+
Completely new buttons can be added to the wizard by passing a button name to the +create_button+ method
|
557
|
+
in the +act_wizardly_for+ code block
|
558
|
+
|
559
|
+
act_wizardly_for :user, :redirect=>'/main/index' do
|
560
|
+
change_button(:back).name_to('Previous Page')
|
561
|
+
change_button(:finish).name_to('Save and Return')
|
562
|
+
...
|
563
|
+
create_button('Help')
|
564
|
+
end
|
565
|
+
|
566
|
+
This creates a new button names 'Help' represented by the :help symbol. Actions for this
|
567
|
+
button must be defined by the on_help() callback macros for each and every page.
|
568
|
+
|
569
|
+
on_help(:all) do
|
570
|
+
case @step
|
571
|
+
when :step1 then ...
|
572
|
+
when :step2 then ...
|
573
|
+
end
|
574
|
+
end
|
575
|
+
|
576
|
+
=== Setting Buttons for a Wizard Page
|
577
|
+
|
578
|
+
Any new button needs to be explicitly added to every page it will show up on. Each
|
579
|
+
pages button set can be set in the +act_wizardly_for+ code as follows
|
580
|
+
|
581
|
+
act_wizardly_for :user, :redirect=>'/main/index' do
|
582
|
+
change_button(:back).name_to('Previous Page')
|
583
|
+
change_button(:finish).name_to('Save and Return')
|
584
|
+
...
|
585
|
+
create_button('Help')
|
586
|
+
...
|
587
|
+
set_page(:step1).buttons_to :next, :cancel, :help
|
588
|
+
set_page(:step2).buttons_to :next, :previous_page, :cancel, :help
|
589
|
+
set_page(:step3).buttons_to :save_and_return, :previous_page, :cancel, :help
|
590
|
+
end
|
591
|
+
|
592
|
+
=== Viewing the Configuration
|
593
|
+
|
594
|
+
Use the wizardly rake tools to view the configuration of any wizard changes you make.
|
595
|
+
|
596
|
+
./script/generate wizardly_app # if not already called for the project
|
597
|
+
rake wizardly:config name=controller_name
|
372
598
|
|
373
|
-
|
599
|
+
See the section 'Inspecting a Wizard Controller' above.
|
374
600
|
|
375
601
|
== Testing
|
376
602
|
|
@@ -9,18 +9,29 @@ module Wizardly
|
|
9
9
|
|
10
10
|
def initialize(id, name=nil)
|
11
11
|
@id = id
|
12
|
-
@name = name ||
|
12
|
+
@name = name || symbol_to_button_name(id)
|
13
|
+
@user_defined = false
|
13
14
|
end
|
15
|
+
|
16
|
+
def user_defined?; @user_defined; end
|
14
17
|
|
15
|
-
|
18
|
+
#used in the dsl
|
19
|
+
def name_to(name)
|
16
20
|
if name.is_a?(String)
|
17
21
|
@name = name.strip.squeeze(' ')
|
18
|
-
@id =
|
22
|
+
@id = button_name_to_symbol(name)
|
19
23
|
elsif name.is_a?(Symbol)
|
20
24
|
@id = name
|
21
|
-
@name =
|
25
|
+
@name = symbol_to_button_name(name)
|
22
26
|
end
|
23
27
|
end
|
24
|
-
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class UserDefinedButton < Button
|
31
|
+
def initialize(id, name=nil)
|
32
|
+
super
|
33
|
+
@user_defined = true
|
34
|
+
end
|
35
|
+
end
|
25
36
|
end
|
26
37
|
end
|
@@ -7,6 +7,7 @@ require 'wizardly/wizard/configuration/methods'
|
|
7
7
|
module Wizardly
|
8
8
|
module Wizard
|
9
9
|
class Configuration
|
10
|
+
include TextHelpers
|
10
11
|
attr_reader :pages, :completed_redirect, :canceled_redirect, :controller_path, :controller_class_name, :controller_name, :page_order
|
11
12
|
|
12
13
|
#enum_attr :persistance, %w(sandbox session database)
|
@@ -28,10 +29,7 @@ module Wizardly
|
|
28
29
|
@page_order = []
|
29
30
|
@pages = {}
|
30
31
|
@buttons = nil
|
31
|
-
@default_buttons = {}
|
32
|
-
[:next, :back, :skip, :cancel, :finish].each do |default|
|
33
|
-
@default_buttons[default] = Button.new(default)
|
34
|
-
end
|
32
|
+
@default_buttons = Hash[*[:next, :back, :cancel, :finish, :skip].collect {|default| [default, Button.new(default)] }.flatten]
|
35
33
|
end
|
36
34
|
|
37
35
|
def guard?; @guard_entry; end
|
@@ -105,7 +103,7 @@ module Wizardly
|
|
105
103
|
end
|
106
104
|
PageField.new(f, type)
|
107
105
|
end
|
108
|
-
page = Page.new(p, fields)
|
106
|
+
page = Page.new(self, p, fields)
|
109
107
|
|
110
108
|
# default button settings based on order, can be altered by
|
111
109
|
# set_page(@id).buttons_to []
|
@@ -128,14 +126,15 @@ module Wizardly
|
|
128
126
|
def _when_completed_redirect_to(redir); @completed_redirect = redir; end
|
129
127
|
def _when_canceled_redirect_to(redir); @canceled_redirect = redir; end
|
130
128
|
def _change_button(name)
|
131
|
-
|
129
|
+
raise(WizardConfigurationError, "Button :#{name} in _change_button() call does not exist", caller) unless @default_buttons.key?(name)
|
130
|
+
@buttons = nil
|
132
131
|
@default_buttons[name]
|
133
132
|
end
|
134
133
|
def _create_button(name)
|
135
|
-
id =
|
136
|
-
raise(
|
134
|
+
id = button_name_to_symbol(name)
|
135
|
+
raise(WizardConfigurationError, "Button '#{name}' with id :#{id} cannot be created. The ID already exists.", caller) if @default_buttons.key?(id)
|
137
136
|
@buttons=nil
|
138
|
-
@default_buttons[id] =
|
137
|
+
@default_buttons[id] = UserDefinedButton.new(id, name)
|
139
138
|
end
|
140
139
|
def _set_page(name); @pages[name]; end
|
141
140
|
def _mask_passwords(passwords)
|
@@ -177,10 +176,11 @@ module Wizardly
|
|
177
176
|
self.buttons.each do |k, b|
|
178
177
|
bs = StringIO.new
|
179
178
|
bs << " #{b.name} (:#{b.id}) "
|
180
|
-
if (
|
181
|
-
bs << "--
|
179
|
+
if (b.user_defined?)
|
180
|
+
bs << "-- user defined button and function"
|
182
181
|
else
|
183
|
-
|
182
|
+
dk = @default_buttons.index(b)
|
183
|
+
bs << "-- used for internal <#{dk}> functionality"
|
184
184
|
end
|
185
185
|
io.puts bs.string
|
186
186
|
end
|
@@ -4,17 +4,23 @@ module Wizardly
|
|
4
4
|
|
5
5
|
def print_callback_macros
|
6
6
|
macros = [
|
7
|
-
%w(on_post
|
8
|
-
%w(on_get
|
9
|
-
%w(on_errors
|
7
|
+
%w(on_post _on_post_%s_form),
|
8
|
+
%w(on_get _on_get_%s_form),
|
9
|
+
%w(on_errors _on_invalid_%s_form)
|
10
10
|
]
|
11
11
|
self.buttons.each do |id, button|
|
12
|
-
macros << ['on_'+ id.to_s, '
|
12
|
+
macros << ['on_'+ id.to_s, '_on_%s_form_'+ id.to_s ]
|
13
13
|
end
|
14
14
|
mb = StringIO.new
|
15
15
|
macros.each do |macro|
|
16
16
|
mb << <<-MACRO
|
17
17
|
def self.#{macro.first}(*args, &block)
|
18
|
+
self._define_action_callback_macro('#{macro.first}', '#{macro.last}', *args, &block)
|
19
|
+
end
|
20
|
+
MACRO
|
21
|
+
end
|
22
|
+
mb << <<-DEFMAC
|
23
|
+
def self._define_action_callback_macro(macro_first, macro_last, *args, &block)
|
18
24
|
return if args.empty?
|
19
25
|
all_forms = #{page_order.inspect}
|
20
26
|
if args.include?(:all)
|
@@ -22,28 +28,27 @@ module Wizardly
|
|
22
28
|
else
|
23
29
|
forms = args.map do |fa|
|
24
30
|
unless all_forms.include?(fa)
|
25
|
-
raise(ArgumentError, ":"+fa.to_s+" in callback
|
31
|
+
raise(ArgumentError, ":"+fa.to_s+" in callback '" + macro_first + "' is not a form defined for the wizard", caller)
|
26
32
|
end
|
27
33
|
fa
|
28
34
|
end
|
29
35
|
end
|
30
36
|
forms.each do |form|
|
31
|
-
self.send(:define_method, sprintf(
|
37
|
+
self.send(:define_method, sprintf(macro_last, form.to_s), &block )
|
38
|
+
hide_action macro_last.to_sym
|
32
39
|
end
|
33
40
|
end
|
34
41
|
|
35
|
-
|
36
|
-
end
|
42
|
+
DEFMAC
|
37
43
|
|
38
|
-
|
39
|
-
%w(on_completed
|
40
|
-
]
|
41
|
-
|
42
|
-
mb << <<-GLOBAL
|
44
|
+
[
|
45
|
+
%w(on_completed _after_wizard_save)
|
46
|
+
].each do |macro|
|
47
|
+
mb << <<-EVENTS
|
43
48
|
def self.#{macro.first}(&block)
|
44
49
|
self.send(:define_method, :#{macro.last}, &block )
|
45
50
|
end
|
46
|
-
|
51
|
+
EVENTS
|
47
52
|
end
|
48
53
|
mb.string
|
49
54
|
end
|
@@ -91,20 +96,20 @@ GLOBAL
|
|
91
96
|
@description = '#{page.description}'
|
92
97
|
h = (self.wizard_form_data||{}).merge(params[:#{self.model}] || {})
|
93
98
|
@#{self.model} = build_wizard_model(h)
|
94
|
-
if request.post? && callback_performs_action?(:
|
95
|
-
raise CallbackError, "render or redirect not allowed in :
|
99
|
+
if request.post? && callback_performs_action?(:_on_post_#{id}_form)
|
100
|
+
raise CallbackError, "render or redirect not allowed in :on_post(:#{id}) callback", caller
|
96
101
|
end
|
97
102
|
button_id = check_action_for_button
|
98
103
|
return if performed?
|
99
104
|
if request.get?
|
100
|
-
return if callback_performs_action?(:
|
105
|
+
return if callback_performs_action?(:_on_get_#{id}_form)
|
101
106
|
render_wizard_form
|
102
107
|
return
|
103
108
|
end
|
104
109
|
|
105
110
|
# @#{self.model}.enable_validation_group :#{id}
|
106
111
|
unless @#{self.model}.valid?(:#{id})
|
107
|
-
return if callback_performs_action?(:
|
112
|
+
return if callback_performs_action?(:_on_invalid_#{id}_form)
|
108
113
|
render_wizard_form
|
109
114
|
return
|
110
115
|
end
|
@@ -113,29 +118,29 @@ GLOBAL
|
|
113
118
|
ONE
|
114
119
|
if self.last_page?(id)
|
115
120
|
mb << <<-TWO
|
116
|
-
callback_performs_action?(:
|
121
|
+
callback_performs_action?(:_on_#{id}_form_#{finish_button})
|
117
122
|
complete_wizard unless @do_not_complete
|
118
123
|
TWO
|
119
124
|
elsif self.first_page?(id)
|
120
125
|
mb << <<-THREE
|
121
126
|
if button_id == :#{finish_button}
|
122
|
-
callback_performs_action?(:
|
127
|
+
callback_performs_action?(:_on_#{id}_form_#{finish_button})
|
123
128
|
complete_wizard unless @do_not_complete
|
124
129
|
return
|
125
130
|
end
|
126
131
|
#{model_persist_line}
|
127
|
-
return if callback_performs_action?(:
|
132
|
+
return if callback_performs_action?(:_on_#{id}_form_#{next_button})
|
128
133
|
redirect_to :action=>:#{self.next_page(id)}
|
129
134
|
THREE
|
130
135
|
else
|
131
136
|
mb << <<-FOUR
|
132
137
|
if button_id == :#{finish_button}
|
133
|
-
callback_performs_action?(:
|
138
|
+
callback_performs_action?(:_on_#{id}_form_#{finish_button})
|
134
139
|
complete_wizard unless @do_not_complete
|
135
140
|
return
|
136
141
|
end
|
137
142
|
#{model_persist_line}
|
138
|
-
return if callback_performs_action?(:
|
143
|
+
return if callback_performs_action?(:_on_#{id}_form_#{next_button})
|
139
144
|
redirect_to :action=>:#{self.next_page(id)}
|
140
145
|
FOUR
|
141
146
|
end
|
@@ -157,6 +162,7 @@ ENSURE
|
|
157
162
|
<<-CALLBACKS
|
158
163
|
protected
|
159
164
|
def _on_wizard_#{finish}
|
165
|
+
return if @wizard_completed_flag
|
160
166
|
@#{self.model}.save_without_validation! if @#{self.model}.changed?
|
161
167
|
@wizard_completed_flag = true
|
162
168
|
reset_wizard_form_data
|
@@ -259,10 +265,19 @@ SESSION
|
|
259
265
|
SANDBOX
|
260
266
|
end
|
261
267
|
mb << <<-HELPERS
|
268
|
+
def render_and_return
|
269
|
+
return if callback_performs_action?('_on_get_'+@step.to_s+'_form')
|
270
|
+
render_wizard_form
|
271
|
+
render unless self.performed?
|
272
|
+
end
|
273
|
+
|
262
274
|
def complete_wizard(redirect = nil)
|
263
|
-
|
264
|
-
|
275
|
+
unless @wizard_completed_flag
|
276
|
+
@#{self.model}.save_without_validation!
|
277
|
+
callback_performs_action?(:_after_wizard_save)
|
278
|
+
end
|
265
279
|
redirect_to redirect if (redirect && !self.performed?)
|
280
|
+
return if @wizard_completed_flag
|
266
281
|
_on_wizard_#{finish_button}
|
267
282
|
redirect_to #{Utils.formatted_redirect(self.completed_redirect)} unless self.performed?
|
268
283
|
end
|
@@ -291,7 +306,7 @@ SANDBOX
|
|
291
306
|
def progression
|
292
307
|
session[:#{self.progression_key}]||[]
|
293
308
|
end
|
294
|
-
hide_action :progression, :progression=
|
309
|
+
hide_action :progression, :progression=, :initial_referer, :initial_referer=
|
295
310
|
|
296
311
|
def wizard_form_data=(hash)
|
297
312
|
if wizard_config.form_data_keep_in_session?
|
@@ -336,14 +351,16 @@ SANDBOX
|
|
336
351
|
#check if params[:commit] has returned a button from submit_tag
|
337
352
|
unless (params[:commit] == nil)
|
338
353
|
button_name = underscore_button_name(params[:commit])
|
339
|
-
unless [:#{next_id}, :#{finish_id}].include?(button_id = button_name.to_sym)
|
340
|
-
action_method_name = "
|
354
|
+
unless [:#{next_id}, :#{finish_id}].include?(button_id = button_name.to_sym)
|
355
|
+
action_method_name = "_on_" + params[:action].to_s + "_form_" + button_name
|
341
356
|
callback_performs_action?(action_method_name)
|
342
|
-
|
343
|
-
|
344
|
-
self.
|
345
|
-
|
346
|
-
|
357
|
+
unless ((btn_obj = self.wizard_config.buttons[button_id]) == nil || btn_obj.user_defined?)
|
358
|
+
method_name = "_on_wizard_" + button_name
|
359
|
+
if (self.method(method_name))
|
360
|
+
self.__send__(method_name)
|
361
|
+
else
|
362
|
+
raise MissingCallbackError, "Callback method either '" + action_method_name + "' or '" + method_name + "' not defined", caller
|
363
|
+
end
|
347
364
|
end
|
348
365
|
end
|
349
366
|
end
|
data/lib/wizardly/wizard/page.rb
CHANGED
@@ -8,19 +8,23 @@ module Wizardly
|
|
8
8
|
attr_reader :id, :title, :description
|
9
9
|
attr_accessor :buttons, :fields
|
10
10
|
|
11
|
-
def initialize(id, fields)
|
11
|
+
def initialize(config, id, fields)
|
12
12
|
@buttons = []
|
13
|
-
@title =
|
13
|
+
@title = symbol_to_button_name(id)
|
14
14
|
@id = id
|
15
15
|
@description = ''
|
16
16
|
@fields = fields
|
17
|
+
@config = config
|
17
18
|
end
|
18
19
|
|
19
20
|
def name; id.to_s; end
|
20
21
|
|
21
22
|
def buttons_to(*args)
|
22
|
-
|
23
|
-
@buttons = args
|
23
|
+
buttons = @config.buttons
|
24
|
+
@buttons = args.map do |button_id|
|
25
|
+
raise(WizardConfigurationError, ":#{button_id} not defined as a button id in :button_to() call", caller) unless buttons.key?(button_id)
|
26
|
+
buttons[button_id]
|
27
|
+
end
|
24
28
|
end
|
25
29
|
def title_to(name)
|
26
30
|
@title = name.strip.squeeze(' ')
|
@@ -2,10 +2,13 @@ module Wizardly
|
|
2
2
|
module Wizard
|
3
3
|
module TextHelpers
|
4
4
|
private
|
5
|
-
def
|
6
|
-
|
5
|
+
def underscore_button_name(name)
|
6
|
+
name.to_s.strip.squeeze(' ').gsub(/ /, '_').downcase
|
7
7
|
end
|
8
|
-
def
|
8
|
+
def button_name_to_symbol(str)
|
9
|
+
underscore_button_name(str).to_sym
|
10
|
+
end
|
11
|
+
def symbol_to_button_name(sym)
|
9
12
|
sym.to_s.gsub(/_/, ' ').squeeze(' ').titleize
|
10
13
|
end
|
11
14
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jeffp-wizardly
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.8.
|
4
|
+
version: 0.1.8.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeff Patmon
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-08-
|
12
|
+
date: 2009-08-21 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|