Wizard Component Tutorial
2 : Basic Usage and Examples
A tutorial on using my Wizard Component which automates several aspects of multi-page forms including data persistence, form preparation, wizard resetting (manual and automatic), and wizard navigation (including jumping between steps) while maintaining flexibility with custom validation and completion callbacks.
How to use it.
Public variables:
- $autoReset (default: true) - If true, wizard resets after invalid navigation. (ie. Users skipping ahead by changing url or forged form.) If false, wizard navigates to "expected step."
- $receiptAction (default: 'receipt') - View to be rendered after the wizard is complete.
- $steps - Array containing the names of the steps. (Names correspond to views and what is passed through url.)
- $sessionKey (default: ControllerName.Wizard) - Session key that all data from the Wizard is stored in.
- $wizardAction (default: 'wizard') - Controller action that handles the form submission and passing of step variable to the component. (makes urls /controller/wizardaction/step)
Public methods:
- process($step) - Main component method. $step is the step that was submitted that needs to be processed.
- resetWizard() - Resets the wizard. This is usually called only by the component, but there may be cases (I can think of at least one) where it might need to be called manually.
Usage
Let me first say that this component is extremely easy to use but I am definitely not the best at explanations. So if this component is something you are wanting to use but you're having problems using it because I've explained something poorly, please feel free to post a question or a comment on how I could improve this tutorial.
As I said before, the method of passing the "current step" to the component is as a variable in the url and then into the WizardComponent::process() function. While there are obviously more ways to pass around the step variable before passing it into process(), the component internally redirects the application to /controller/wizardaction/(next or previous step) - so even if you were to pass the step by another method, say in a hidden form value for example, the component would still redirect the wizard as if you were using the url method of passing the variable. (Hopefully, in the future, I will update this component to include other methods of passing the variable.) So...
Step One: Set up a Route (optional)
If you want your controller's default action to point to the wizard action, you need to set up a route. This is nice, for example, if you would like your "sign-up" wizard's url to be example.com/signup which would then redirect to /signup/wizardaction/first_step.
Router::connect('/your_controller', array('controller' => 'your_controller', 'action' => 'wizard'));
Step Two: Controller Setup
Obviously, include the component in the controller first (also don't forget to put the actual component in your components folder.) But in your beforeFilter(), you need to setup the steps of the wizard. The step names actually correspond directly to the view names. So if your steps were $this->Wizard->steps = array('account','shipping'); then you would need account.ctp and shipping.ctp in your views folder. The last thing to do is to setup your default action - the code here is actually all you need in a basic setup for your default action.
var $components = array('Wizard');
function beforeFilter() {
$this->Wizard->steps = array('your','steps','here');
}
function wizard($step=null) {
$this->Wizard->process($step);
}
Step Three: Create your views
There's only a few special things about this. 1. Your form action needs to include the name of the step. I think the easiest way is in the example. 2. For pages that need a "previous" button: the 'name' of the button must be 'Previous' for it to work correctly. 3. As I said before, the views need to be saved with the same name as the step setup in the $steps array.
<?php echo $form->create('YourModel',array('id'=>'YourModelForm','url'=>$this->here));?>
# Your form inputs here
<div class="submit">
<?php echo $form->submit('Previous',array('div'=>false,'name'=>'Previous'));?>
<?php echo $form->submit('Continue',array('div'=>false));?>
</div>
<?php echo $form->end();?>
Step Four: Create "process callbacks" in controller
Every step has to have a "process callback" in your controller. This callback occurs when data is present and directly before continuing through the wizard. So this would be a good place for your data validation. The nice thing about having a process callback for each step is that you can validate data against different models for different steps. The process callbacks must return true (to continue) or false (to render the view again). The name of the callback for each step is "processStepName". So, from the previous example, you would need: processAccount() and processShipping().
function processAccount() {
# do some validation stuff here
return true (or false);
}
Step Five: The "receipt"
The receipt is the "landing page" of the wizard. It is the view that is rendered after the last step of the wizard is complete. It does not need to be included in your $steps array because it is automatic. It defaults to 'receipt' but can be changed to another view through the Wizard->receiptAction variable. If you would rather not show a receipt, there are actually two optional callbacks in between the final step of the wizard and the rendering of the receipt.
Optional Stuff
Dedicated Controller
There may be a situation in which you would like your urls to look like '/controller/step'. To do so, you need to dedicate your controller to the wizard action. There is two steps to doing this: 1. Create a route. 2. Set the $wizardAction to null.
Step One: Adding a Route
The action doesn't have to be wizard but just remember to change your controller action to match. (ie. if you set 'action' => 'index', use index() as your action, not wizard())
Router::connect('/[your_controller]/*', array('controller' => '[your_controller]', 'action' => 'wizard'));
Step Two: Setting $wizardAction
Include this in your controller's beforeFilter():
$this->Wizard->wizardAction = null;
Wizard::autoReset
$autoReset is a neat little feature that was easier to program than I had expected. It defaults to true, but can be set to false in your beforeFilter(). Basically, if a user tries to "skip ahead" in your wizard by typing in the url manually, or if a user types in an incorrect step in the url, or if someone tries to use a forged form to skip ahead in the wizard, then - if autoReset is set to true - the wizard will be reset and the user will be sent back to the first step. However, if autoReset is false, the wizard will not be reset, the user will simply be sent to the "expected step" which is the step after the last step completed and saved in the session.
Prepare Callbacks
Sometimes views might need data to be "prepared" before the page can be rendered. For example, if you wanted to set data into an array for use in a select box, you would need access to the view before it was rendered. So each step can also have an optional prepare callback alongside their process callback. They are named similarly: prepareStepName. They do not need to return anything.
function prepareAccount() {
$this->set('options',array('one','two','three'));
}
afterComplete() and beforeReceipt() callbacks
There are two optional controller callbacks that occur between the completion of the wizard and the rendering of the receipt. I'm not sure if both are needed but I figured someone might be able to use them for something. The wizard actually does two things after the wizard is complete - redirect to the receipt page, then render it.
1. afterComplete() occurs directly after the completion of the wizard but before the redirect. I thought this would be the best place for saving all your data (which is accessible in your controller with $this->Session->read($this->Wizard->sessionKey)) to the database, capturing payments after they've been authorized, etc. You could also redirect here if you didn't want the receipt page at all.
One warning: The wizard is reset directly after this callback (in order to protect people from resubmitting data). So if you choose to redirect away from the wizard AND you have $autoReset=false, then you need to reset the wizard yourself before you redirect (ie. this is where Wizard->resetWizard() could be used manually.) If $autoReset=true, then you don't have to worry about it because it will get reset on the next form submission.
2. beforeReceipt() occurs after the redirect but before the view is rendered. I thought someone might use this for preparing a saved receipt or something - I don't know.
That's about it, if anyone has any suggestions, comments, or questions - or if you need a full example - please feel free to let me know.
Latest Comments