jeffp-wizardly 0.1.8.4 → 0.1.8.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
|