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.
@@ -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 scaffolding generator
29
- * Default wizard buttons
30
- * Custom button creation
31
- * Page and button callbacks
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', :lib=>'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
- For any rails app, run the following to install wizardly rake tasks (optional)
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
- == Example
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
- class User < ActiveRecord::Base
69
- validation_group :step1, :fields=>[:first_name, :last_name]
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
- Step 2: Tell your controller to act 'wizardly'.
93
+ ./script/generate wizardly_app
75
94
 
76
- class SignupController < ApplicationController
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
- Step 3: Generate a 'wizardly' scaffold for your controller.
97
+ rake wizardly:config name=controller_name
81
98
 
82
- ./script/generate wizardly_scaffold signup
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
- You are ready to go.
104
+ == The Basic Wizard
85
105
 
86
- General usage and configuration of wizardly follows. See the examples at
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
- http://github.com/jeffp/wizardly-examples
110
+ === Action Instance Variables
89
111
 
90
- == Usage
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
- === Default Behavior and Scaffolding Configuration
121
+ === Flow
93
122
 
94
- The wizardly_scaffold generator produces an html view for each validation_group
95
- declared in the model. The wizard progresses to each page in the same order of
96
- the validation_group(s) in the model. Buttons for navigating
97
- the wizard are included on each page.
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
- You can view a wizardly controller's configuration by using the wizardly:config
100
- rake task. First you'll need to install it if you already have not
130
+ === Automatic Page Progression Tracking
101
131
 
102
- ./script/generate wizardly_app
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
- Then you can call the rake task for any controller configured with the
105
- 'act_wizardly_for' macro
138
+ === Wizard Entrance Guarding
106
139
 
107
- rake wizardly:config name=signup
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
- Substitute the name of any wizardly controller for 'signup'.
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 above example is pretty simple. In fact, no redirects have been defined for
118
- completing or canceling the wizard in the above example so the wizardly controller
119
- uses the HTTP_REFERER for both cases. What if there is no referer? The controller
120
- raises a RedirectNotDefined error. Let's remedy this problem with some options
121
- in the macro.
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, :completed=>'/main/finished',
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. Alternately, if you want to redirect to the same page
130
- for both cases
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
- ==== Options For act_wizardly_for
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 you can use in the macro
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 or exclude a skip button on each page.
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
- :persist_model and :form_data are explained below.
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 between
156
- page requests that call outside the wizard controller. The default option setting,
157
- :session, keeps the form data until the wizard is complete regardless of
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 option setting, :sandbox, clears the form data whenever
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
- database record has been created.
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
- ==== Saving The Model
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 Advanced Configuration section.
254
+ See the Button Customization section.
209
255
 
210
256
 
211
257
  === Callbacks
212
258
 
213
- The wizard macro 'act_wizardly_for' creates controller actions for each page.
214
- The processing is standard for a wizard. Hooks or callback macros are available
215
- for changing or interrupting the processing. Here's an example. Say our model
216
- declares a :step4 validation_group with :username, :password, and
217
- :password_confirmation fields. We'd like to handle this step specifically.
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
- The above hook macro is only called if :step4 does not validate according to its
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
- The block for each hook is called in the controller context, thus giving it access to all
244
- controller variables and methods just as any controller action method. Each callback
245
- has access to the model through an instance variable using the model's name. So
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
- Notice the last five callbacks are related to the default wizard buttons. Each
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 in the view.
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
- Important: Method names for button callbacks will change if you change the name
256
- of the button. For general use, this is not an issue. See the Advanced Configuration section.
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
- The order of callbacks for a GET request is as follows:
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
- Of course, only one of the button callbacks (back, skip or cancel) may occur for any single request, and
268
- any one of them may keep the on_get from being called if they render or redirect.
269
- :next and :finish button callbacks only occur for POST requests. The on_get callback
270
- occurs just before rendering, and may itself render or redirect. The on_get callback
271
- is an opportunity to modify or check the model fields before rendering.
272
-
273
- The order of callbacks for a POST request is as follows:
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 # only if errors
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
- on_post(:step1) do
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
- on_back(:step2, :step3, :step4) do
302
- ...
368
+
369
+ on_errors(:step2) do
370
+ setup_step2_form
303
371
  end
304
- on_get(:all) do
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
- Indicating a form that does not exist raises a CallbackError.
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
- === Rendering Callback
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
- === Completing the Wizard Programmatically
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
- == Advanced Configuration
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
- To be provided
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 || sym_to_string(id)
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
- def to(name)
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 = string_to_sym(name)
22
+ @id = button_name_to_symbol(name)
19
23
  elsif name.is_a?(Symbol)
20
24
  @id = name
21
- @name = sym_to_string(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
- @buttons = nil
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 = string_to_sym(name)
136
- raise(WizardlyConfigurationError, "Button '#{name}' cannot be created. It already exists.", caller) if @default_buttons.key?(id)
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] = Button.new(id, name)
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 (dk = @default_buttons.index(b))
181
- bs << "-- used for internal <#{dk}> functionality"
179
+ if (b.user_defined?)
180
+ bs << "-- user defined button and function"
182
181
  else
183
- bs << "-- custom defined button and function"
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 on_post_%s_form),
8
- %w(on_get on_get_%s_form),
9
- %w(on_errors on_invalid_%s_form)
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, 'on_%s_form_'+ 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 #{macro.first} is not a form defined for the wizard", caller)
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("#{macro.last}", form.to_s), &block )
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
- MACRO
36
- end
42
+ DEFMAC
37
43
 
38
- global_macros = [
39
- %w(on_completed after_wizard_save)
40
- ]
41
- global_macros.each do |macro|
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
- GLOBAL
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?(:on_post_#{id}_form)
95
- raise CallbackError, "render or redirect not allowed in :on_post_#{id}_form callback", caller
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?(:on_get_#{id}_form)
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?(:on_invalid_#{id}_form)
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?(:on_#{id}_form_#{finish_button})
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?(:on_#{id}_form_#{finish_button})
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?(:on_#{id}_form_#{next_button})
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?(:on_#{id}_form_#{finish_button})
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?(:on_#{id}_form_#{next_button})
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
- @#{self.model}.save_without_validation!
264
- callback_performs_action?(:after_wizard_save)
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 = "on_" + params[:action].to_s + "_form_" + button_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
- method_name = "_on_wizard_" + button_name
343
- if (self.method(method_name))
344
- self.__send__(method_name)
345
- else
346
- raise MissingCallbackError, "Callback method either '" + action_method_name + "' or '" + method_name + "' not defined", caller
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
@@ -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 = sym_to_string(id)
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
- # #must analyze buttons
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 string_to_sym(str)
6
- str.downcase.strip.squeeze(' ').gsub(/ /, '_').to_sym
5
+ def underscore_button_name(name)
6
+ name.to_s.strip.squeeze(' ').gsub(/ /, '_').downcase
7
7
  end
8
- def sym_to_string(sym)
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
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-19 00:00:00 -07:00
12
+ date: 2009-08-21 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies: []
15
15