Repository files navigation

Step-By-Step Wizard Controllers

Build StatusCode Climate

Use wicked to make your Rails controllers into step-by-step wizards. To see Wicked in action check out the example Rails app or watch the screencast.

Why

Many times I'm left wanting a RESTful way to display a step by step process that may or not be associated with a resource. Wicked gives the flexibility to do what I want while hiding all the really nasty stuff you shouldn't do in a controller to make this possible. At its core Wicked is a RESTful(ish) state machine, but you don't need to know that, just use it.

Install

Add this to your Gemfile

gem'wicked'

Then run bundle install and you're ready to start

Quicklinks

How

We are going to build an 'after signup' wizard. If you don't have a current_user then check out how to Build a step-by-step object with Wicked.

First create a controller:

rails g controller after_signup

Add Routes into config/routes.rb:

resources:after_signup

Next include Wicked::Wizard in your controller

classAfterSignupController < ApplicationControllerincludeWicked::Wizardsteps:confirm_password,:confirm_profile,:find_friends# ...

You can also use the old way of inheriting from Wicked::WizardController.

classAfterSignupController < Wicked::WizardControllersteps:confirm_password,:confirm_profile,:find_friends# ...

The wizard is set to call steps in order in the show action, you can specify custom logic in your show using a case statement like below. To send someone to the first step in this wizard we can direct them to after_signup_path(:confirm_password).

classAfterSignupController < ApplicationControllerincludeWicked::Wizardsteps:confirm_password,:confirm_profile,:find_friendsdefshow@user=current_usercasestepwhen:find_friends@friends=@user.find_friendsendrender_wizardendend

Note: Wicked uses the :id parameter to control the flow of steps, if you need to have an id parameter, please use nested routes. See building objects with wicked for an example. It will need to be prefixed, for example a Product's :id would be :product_id

You'll need to call render_wizard at the end of your action to get the correct views to show up.

By default the wizard will render a view with the same name as the step. So for our controller AfterSignupController with a view path of /views/after_signup/ if call the :confirm_password step, our wizard will render /views/after_signup/confirm_password.html.erb

Then in your view you can use the helpers to get to the next step.

<%= link_to 'skip', next_wizard_path %>

You can manually specify which wizard action you want to link to by using the wizard_path helper.

<%= link_to 'skip', wizard_path(:find_friends) %>

In addition to showing sequential views we can update elements in our controller.

classAfterSignupController < ApplicationControllerincludeWicked::Wizardsteps:confirm_password,:confirm_profile,:find_friendsdefupdate@user=current_usercasestepwhen:confirm_password@user.update_attributes(params[:user])endsign_in(@user,bypass: true)# needed for deviserender_wizard@userendend

We're passing render_wizard our @user object here. If you pass an object into render_wizard it will show the next step if the object saves or re-render the previous view if it does not save.

To get to this update action, you simply need to submit a form that PUT's to the same url

<%= form_for @user, url: wizard_path, method: :put do |f| %><%= f.password_field :password %><%= f.password_field :password_confirmation %><%= f.submit "Change Password" %><% end %>

We explicitly tell the form to PUT above. If you forget this, you will get a warning about the create action not existing, or no route found for POST. Don't forget this.

In the controller if you find that you want to skip a step, you can do it simply by calling skip_step

defshow@user=current_usercasestepwhen:find_friendsif@user.has_facebook_access_token?@friends=@user.find_friendselseskip_stependendrender_wizardend

Now you've got a fully functioning AfterSignup controller! If you have questions or if you struggled with something, let me know on twitter, and i'll try to make it better or make the docs better.

Quick Reference

View/URL Helpers

wizard_path# Grabs the current path in the wizardwizard_path(:specific_step)# Url of the :specific_stepnext_wizard_path# Url of the next stepprevious_wizard_path# Url of the previous step# These only work while in a Wizard, and are not absolute paths# You can have multiple wizards in a project with multiple `wizard_path` calls

Controller Tidbits:

steps:first,:second# Sets the order of stepsstep# Gets current stepnext_step# Gets next stepprevious_step# Gets previous stepskip_step# Tells render_wizard to skip to the next logical stepjump_to(:specific_step)# Jump to :specific_steprender_wizard# Renders the current steprender_wizard(@user)# Shows next_step if @user.save, otherwise renders current step

Finally:

Don't forget to create your named views

app/
views/
controller_name/
first.html.erb
second.html.erb
# ...

Finish Wizard Path

You can specify the url that your user goes to by over-riding the finish_wizard_path in your wizard controller.

deffinish_wizard_pathuser_path(current_user)end

Testing with RSpec

# Test find_friends block of show actionget:show,id: :find_friends# Test find_friends block of update actionput:update,{'id'=>'find_friends',"user"=>{"id"=>@user.id.to_s}}

Internationalization of URLS (I18n)

If your site works in multiple languages, or if you just want more control over how your URLs look you can now use I18n with wicked. To do so you need to replace this:

includeWicked::Wizard

With this:

includeWicked::Wizard::Translated

This will allow you to specify translation keys instead of literal step names. Let's say you've got steps that look like this:

steps :first, :second

So the urls would be /after_signup/first and /after_signup/second. But you want them to show up differently for different locales. For example someone coming form a Spanish speaking locale should see /after_signup/uno and after_signup/dos.

To internationalize first you need to create your locales files under config/locales such as config/locales/es.yml for Spanish. You then need to add a first and second key under a wicked key like this:

es:
hello: "hola mundo"wicked:
first: "uno"second: "dos"

It would also be a good idea to create a english version under config/locales/en.yml or your english speaking friends will get errors. If your app already uses I18n you don't need to do anything else, if not you will need to make sure that you set the I18n.locale on each request you could do this somewhere like a before filter in your application_controller.rb

before_filter:set_localeprivatedefset_localeI18n.locale=params[:locale]ifparams[:locale].present?enddefdefault_url_options(options={}){locale: I18n.locale}end

For a screencast on setting up and using I18n check out Railscasts. You can also read the free I18n Rails Guide.

Now when you visit your controller with the proper locale set your URLs should be more readable like /after_signup/uno and after_signup/dos.

Wicked expects your files to be named the same as your keys, so when a user visits after_signup/dos with the es locale it will render the second.html.erb file.

Important: When you do this the value of step as well as next_step and previous_step and all the values within steps will be translated to what locale you are using. To translate them to the "canonical" values that you've have in your controller you'll need so use wizard_value method.

For example, if you had this in your controller, and you converted it to a use Wicked translations, so this will not work:

steps:confirm_password,:confirm_profile,:find_friendsdefshowcasestepwhen:find_friends@friends=current_user.find_friendsendrender_wizardend

Instead you need to use wizard_value to get the "reverse translation" in your controller code like this:

steps:confirm_password,:confirm_profile,:find_friendsdefshowcasewizard_value(step)when:find_friends@friends=current_user.find_friendsendrender_wizardend

The important thing to remember is that step and the values in steps are always going to be in the same language if you're using the Wicked translations. If you need any values to match the values set directly in your controller, or the names of your files (i.e. views/../confirm_password.html.erb, then you need to use wizard_value method.

Custom URLs

Very similar to using I18n from above but instead of making new files for different languages, you can stick with one language. Make sure you are using the right module:

includeWicked::Wizard::Translated

Then you'll need to specify translations in your language file. For me, the language I'm using is english so I can add translations to config/locales/en.yml

en:
hello: "hello world"wicked:
first: "verify_email"second: "if_you_are_popular_add_friends"

Now you can change the values in the URLs to whatever you want without changing your controller or your files, just modify your en.yml. If you're not using English you can set your default_locale to something other than en in your config/application.rb file.

config.i18n.default_locale=:de

Important: Don't forget to use wizard_value() method to make sure you are using the right cannonical values of step, previous_step, next_step, etc. If you are comparing them to non wicked generate values.

Custom crafted wizard urls: just another way Wicked makes your app a little more saintly.

Dynamic Step Names

If you wish to set the order of your steps dynamically you can do this with a prepend_before_filter and self.steps = like this:

includeWicked::Wizardprepend_before_filter:set_steps# ...privatedefset_stepsifparams[:flow] == "twitter"self.steps=[:ask_twitter,:ask_email]elsifparams[:flow] == "facebook"self.steps=[:ask_facebook,:ask_email]endend

Keywords

There are a few "magical" keywords that will take you to the first step, the last step, or the "final" action (the redirect that happens after the last step). Prior to version 0.6.0 these were hardcoded strings. Now they are constants which means you can access them or change them. They are:

Wicked::FIRST_STEPWicked::LAST_STEPWicked::FINISH_STEP

You can build links using these constants after_signup_path(Wicked::LAST_STEP) which will redirect the user to the first step you've specified. This might be useful for redirecting a user to a step when you're not already in a Wicked controller. If you change the step names, they are expected to be strings (not symbols).

About

Please poke around the source code, if you see easier ways to get a Rails controller to do what I want, let me know.

If you have a question file an issue or, find me on the Twitters @schneems.

This project rocks and uses MIT-LICENSE.

Contributing

  1. Fork it ( https://github.com/schneems/wicked/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

About

Use wicked to turn your controller into a wizard

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

Step-By-Step Wizard Controllers

Build StatusCode Climate

Use wicked to make your Rails controllers into step-by-step wizards. To see Wicked in action check out the example Rails app or watch the screencast.

Why

Many times I'm left wanting a RESTful way to display a step by step process that may or not be associated with a resource. Wicked gives the flexibility to do what I want while hiding all the really nasty stuff you shouldn't do in a controller to make this possible. At its core Wicked is a RESTful(ish) state machine, but you don't need to know that, just use it.

Install

Add this to your Gemfile

gem'wicked'

Then run bundle install and you're ready to start

Quicklinks

How

We are going to build an 'after signup' wizard. If you don't have a current_user then check out how to Build a step-by-step object with Wicked.

First create a controller:

rails g controller after_signup

Add Routes into config/routes.rb:

resources:after_signup

Next include Wicked::Wizard in your controller

classAfterSignupController < ApplicationControllerincludeWicked::Wizardsteps:confirm_password,:confirm_profile,:find_friends# ...

You can also use the old way of inheriting from Wicked::WizardController.

classAfterSignupController < Wicked::WizardControllersteps:confirm_password,:confirm_profile,:find_friends# ...

The wizard is set to call steps in order in the show action, you can specify custom logic in your show using a case statement like below. To send someone to the first step in this wizard we can direct them to after_signup_path(:confirm_password).

classAfterSignupController < ApplicationControllerincludeWicked::Wizardsteps:confirm_password,:confirm_profile,:find_friendsdefshow@user=current_usercasestepwhen:find_friends@friends=@user.find_friendsendrender_wizardendend

Note: Wicked uses the :id parameter to control the flow of steps, if you need to have an id parameter, please use nested routes. See building objects with wicked for an example. It will need to be prefixed, for example a Product's :id would be :product_id

You'll need to call render_wizard at the end of your action to get the correct views to show up.

By default the wizard will render a view with the same name as the step. So for our controller AfterSignupController with a view path of /views/after_signup/ if call the :confirm_password step, our wizard will render /views/after_signup/confirm_password.html.erb

Then in your view you can use the helpers to get to the next step.

<%= link_to 'skip', next_wizard_path %>

You can manually specify which wizard action you want to link to by using the wizard_path helper.

<%= link_to 'skip', wizard_path(:find_friends) %>

In addition to showing sequential views we can update elements in our controller.

classAfterSignupController < ApplicationControllerincludeWicked::Wizardsteps:confirm_password,:confirm_profile,:find_friendsdefupdate@user=current_usercasestepwhen:confirm_password@user.update_attributes(params[:user])endsign_in(@user,bypass: true)# needed for deviserender_wizard@userendend

We're passing render_wizard our @user object here. If you pass an object into render_wizard it will show the next step if the object saves or re-render the previous view if it does not save.

To get to this update action, you simply need to submit a form that PUT's to the same url

<%= form_for @user, url: wizard_path, method: :put do |f| %><%= f.password_field :password %><%= f.password_field :password_confirmation %><%= f.submit "Change Password" %><% end %>

We explicitly tell the form to PUT above. If you forget this, you will get a warning about the create action not existing, or no route found for POST. Don't forget this.

In the controller if you find that you want to skip a step, you can do it simply by calling skip_step

defshow@user=current_usercasestepwhen:find_friendsif@user.has_facebook_access_token?@friends=@user.find_friendselseskip_stependendrender_wizardend

Now you've got a fully functioning AfterSignup controller! If you have questions or if you struggled with something, let me know on twitter, and i'll try to make it better or make the docs better.

Quick Reference

View/URL Helpers

wizard_path# Grabs the current path in the wizardwizard_path(:specific_step)# Url of the :specific_stepnext_wizard_path# Url of the next stepprevious_wizard_path# Url of the previous step# These only work while in a Wizard, and are not absolute paths# You can have multiple wizards in a project with multiple `wizard_path` calls

Controller Tidbits:

steps:first,:second# Sets the order of stepsstep# Gets current stepnext_step# Gets next stepprevious_step# Gets previous stepskip_step# Tells render_wizard to skip to the next logical stepjump_to(:specific_step)# Jump to :specific_steprender_wizard# Renders the current steprender_wizard(@user)# Shows next_step if @user.save, otherwise renders current step

Finally:

Don't forget to create your named views

app/
views/
controller_name/
first.html.erb
second.html.erb
# ...

Finish Wizard Path

You can specify the url that your user goes to by over-riding the finish_wizard_path in your wizard controller.

deffinish_wizard_pathuser_path(current_user)end

Testing with RSpec

# Test find_friends block of show actionget:show,id: :find_friends# Test find_friends block of update actionput:update,{'id'=>'find_friends',"user"=>{"id"=>@user.id.to_s}}

Internationalization of URLS (I18n)

If your site works in multiple languages, or if you just want more control over how your URLs look you can now use I18n with wicked. To do so you need to replace this:

includeWicked::Wizard

With this:

includeWicked::Wizard::Translated

This will allow you to specify translation keys instead of literal step names. Let's say you've got steps that look like this:

steps :first, :second

So the urls would be /after_signup/first and /after_signup/second. But you want them to show up differently for different locales. For example someone coming form a Spanish speaking locale should see /after_signup/uno and after_signup/dos.

To internationalize first you need to create your locales files under config/locales such as config/locales/es.yml for Spanish. You then need to add a first and second key under a wicked key like this:

es:
hello: "hola mundo"wicked:
first: "uno"second: "dos"

It would also be a good idea to create a english version under config/locales/en.yml or your english speaking friends will get errors. If your app already uses I18n you don't need to do anything else, if not you will need to make sure that you set the I18n.locale on each request you could do this somewhere like a before filter in your application_controller.rb

before_filter:set_localeprivatedefset_localeI18n.locale=params[:locale]ifparams[:locale].present?enddefdefault_url_options(options={}){locale: I18n.locale}end

For a screencast on setting up and using I18n check out Railscasts. You can also read the free I18n Rails Guide.

Now when you visit your controller with the proper locale set your URLs should be more readable like /after_signup/uno and after_signup/dos.

Wicked expects your files to be named the same as your keys, so when a user visits after_signup/dos with the es locale it will render the second.html.erb file.

Important: When you do this the value of step as well as next_step and previous_step and all the values within steps will be translated to what locale you are using. To translate them to the "canonical" values that you've have in your controller you'll need so use wizard_value method.

For example, if you had this in your controller, and you converted it to a use Wicked translations, so this will not work:

steps:confirm_password,:confirm_profile,:find_friendsdefshowcasestepwhen:find_friends@friends=current_user.find_friendsendrender_wizardend

Instead you need to use wizard_value to get the "reverse translation" in your controller code like this:

steps:confirm_password,:confirm_profile,:find_friendsdefshowcasewizard_value(step)when:find_friends@friends=current_user.find_friendsendrender_wizardend

The important thing to remember is that step and the values in steps are always going to be in the same language if you're using the Wicked translations. If you need any values to match the values set directly in your controller, or the names of your files (i.e. views/../confirm_password.html.erb, then you need to use wizard_value method.

Custom URLs

Very similar to using I18n from above but instead of making new files for different languages, you can stick with one language. Make sure you are using the right module:

includeWicked::Wizard::Translated

Then you'll need to specify translations in your language file. For me, the language I'm using is english so I can add translations to config/locales/en.yml

en:
hello: "hello world"wicked:
first: "verify_email"second: "if_you_are_popular_add_friends"

Now you can change the values in the URLs to whatever you want without changing your controller or your files, just modify your en.yml. If you're not using English you can set your default_locale to something other than en in your config/application.rb file.

config.i18n.default_locale=:de

Important: Don't forget to use wizard_value() method to make sure you are using the right cannonical values of step, previous_step, next_step, etc. If you are comparing them to non wicked generate values.

Custom crafted wizard urls: just another way Wicked makes your app a little more saintly.

Dynamic Step Names

If you wish to set the order of your steps dynamically you can do this with a prepend_before_filter and self.steps = like this:

includeWicked::Wizardprepend_before_filter:set_steps# ...privatedefset_stepsifparams[:flow] == "twitter"self.steps=[:ask_twitter,:ask_email]elsifparams[:flow] == "facebook"self.steps=[:ask_facebook,:ask_email]endend

Keywords

There are a few "magical" keywords that will take you to the first step, the last step, or the "final" action (the redirect that happens after the last step). Prior to version 0.6.0 these were hardcoded strings. Now they are constants which means you can access them or change them. They are:

Wicked::FIRST_STEPWicked::LAST_STEPWicked::FINISH_STEP

You can build links using these constants after_signup_path(Wicked::LAST_STEP) which will redirect the user to the first step you've specified. This might be useful for redirecting a user to a step when you're not already in a Wicked controller. If you change the step names, they are expected to be strings (not symbols).

About

Please poke around the source code, if you see easier ways to get a Rails controller to do what I want, let me know.

If you have a question file an issue or, find me on the Twitters @schneems.

This project rocks and uses MIT-LICENSE.

Contributing

  1. Fork it ( https://github.com/schneems/wicked/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

About

Use wicked to turn your controller into a wizard

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Step-By-Step Wizard Controllers

Build StatusCode Climate

Use wicked to make your Rails controllers into step-by-step wizards. To see Wicked in action check out the example Rails app or watch the screencast.

Why

Many times I'm left wanting a RESTful way to display a step by step process that may or not be associated with a resource. Wicked gives the flexibility to do what I want while hiding all the really nasty stuff you shouldn't do in a controller to make this possible. At its core Wicked is a RESTful(ish) state machine, but you don't need to know that, just use it.

Install

Add this to your Gemfile

gem'wicked'

Then run bundle install and you're ready to start

Quicklinks

How

We are going to build an 'after signup' wizard. If you don't have a current_user then check out how to Build a step-by-step object with Wicked.

First create a controller:

rails g controller after_signup

Add Routes into config/routes.rb:

resources:after_signup

Next include Wicked::Wizard in your controller

classAfterSignupController < ApplicationControllerincludeWicked::Wizardsteps:confirm_password,:confirm_profile,:find_friends# ...

You can also use the old way of inheriting from Wicked::WizardController.

classAfterSignupController < Wicked::WizardControllersteps:confirm_password,:confirm_profile,:find_friends# ...

The wizard is set to call steps in order in the show action, you can specify custom logic in your show using a case statement like below. To send someone to the first step in this wizard we can direct them to after_signup_path(:confirm_password).

classAfterSignupController < ApplicationControllerincludeWicked::Wizardsteps:confirm_password,:confirm_profile,:find_friendsdefshow@user=current_usercasestepwhen:find_friends@friends=@user.find_friendsendrender_wizardendend

Note: Wicked uses the :id parameter to control the flow of steps, if you need to have an id parameter, please use nested routes. See building objects with wicked for an example. It will need to be prefixed, for example a Product's :id would be :product_id

You'll need to call render_wizard at the end of your action to get the correct views to show up.

By default the wizard will render a view with the same name as the step. So for our controller AfterSignupController with a view path of /views/after_signup/ if call the :confirm_password step, our wizard will render /views/after_signup/confirm_password.html.erb

Then in your view you can use the helpers to get to the next step.

<%= link_to 'skip', next_wizard_path %>

You can manually specify which wizard action you want to link to by using the wizard_path helper.

<%= link_to 'skip', wizard_path(:find_friends) %>

In addition to showing sequential views we can update elements in our controller.

classAfterSignupController < ApplicationControllerincludeWicked::Wizardsteps:confirm_password,:confirm_profile,:find_friendsdefupdate@user=current_usercasestepwhen:confirm_password@user.update_attributes(params[:user])endsign_in(@user,bypass: true)# needed for deviserender_wizard@userendend

We're passing render_wizard our @user object here. If you pass an object into render_wizard it will show the next step if the object saves or re-render the previous view if it does not save.

To get to this update action, you simply need to submit a form that PUT's to the same url

<%= form_for @user, url: wizard_path, method: :put do |f| %><%= f.password_field :password %><%= f.password_field :password_confirmation %><%= f.submit "Change Password" %><% end %>

We explicitly tell the form to PUT above. If you forget this, you will get a warning about the create action not existing, or no route found for POST. Don't forget this.

In the controller if you find that you want to skip a step, you can do it simply by calling skip_step

defshow@user=current_usercasestepwhen:find_friendsif@user.has_facebook_access_token?@friends=@user.find_friendselseskip_stependendrender_wizardend

Now you've got a fully functioning AfterSignup controller! If you have questions or if you struggled with something, let me know on twitter, and i'll try to make it better or make the docs better.

Quick Reference

View/URL Helpers

wizard_path# Grabs the current path in the wizardwizard_path(:specific_step)# Url of the :specific_stepnext_wizard_path# Url of the next stepprevious_wizard_path# Url of the previous step# These only work while in a Wizard, and are not absolute paths# You can have multiple wizards in a project with multiple `wizard_path` calls

Controller Tidbits:

steps:first,:second# Sets the order of stepsstep# Gets current stepnext_step# Gets next stepprevious_step# Gets previous stepskip_step# Tells render_wizard to skip to the next logical stepjump_to(:specific_step)# Jump to :specific_steprender_wizard# Renders the current steprender_wizard(@user)# Shows next_step if @user.save, otherwise renders current step

Finally:

Don't forget to create your named views

app/
views/
controller_name/
first.html.erb
second.html.erb
# ...

Finish Wizard Path

You can specify the url that your user goes to by over-riding the finish_wizard_path in your wizard controller.

deffinish_wizard_pathuser_path(current_user)end

Testing with RSpec

# Test find_friends block of show actionget:show,id: :find_friends# Test find_friends block of update actionput:update,{'id'=>'find_friends',"user"=>{"id"=>@user.id.to_s}}

Internationalization of URLS (I18n)

If your site works in multiple languages, or if you just want more control over how your URLs look you can now use I18n with wicked. To do so you need to replace this:

includeWicked::Wizard

With this:

includeWicked::Wizard::Translated

This will allow you to specify translation keys instead of literal step names. Let's say you've got steps that look like this:

steps :first, :second

So the urls would be /after_signup/first and /after_signup/second. But you want them to show up differently for different locales. For example someone coming form a Spanish speaking locale should see /after_signup/uno and after_signup/dos.

To internationalize first you need to create your locales files under config/locales such as config/locales/es.yml for Spanish. You then need to add a first and second key under a wicked key like this:

es:
hello: "hola mundo"wicked:
first: "uno"second: "dos"

It would also be a good idea to create a english version under config/locales/en.yml or your english speaking friends will get errors. If your app already uses I18n you don't need to do anything else, if not you will need to make sure that you set the I18n.locale on each request you could do this somewhere like a before filter in your application_controller.rb

before_filter:set_localeprivatedefset_localeI18n.locale=params[:locale]ifparams[:locale].present?enddefdefault_url_options(options={}){locale: I18n.locale}end

For a screencast on setting up and using I18n check out Railscasts. You can also read the free I18n Rails Guide.

Now when you visit your controller with the proper locale set your URLs should be more readable like /after_signup/uno and after_signup/dos.

Wicked expects your files to be named the same as your keys, so when a user visits after_signup/dos with the es locale it will render the second.html.erb file.

Important: When you do this the value of step as well as next_step and previous_step and all the values within steps will be translated to what locale you are using. To translate them to the "canonical" values that you've have in your controller you'll need so use wizard_value method.

For example, if you had this in your controller, and you converted it to a use Wicked translations, so this will not work:

steps:confirm_password,:confirm_profile,:find_friendsdefshowcasestepwhen:find_friends@friends=current_user.find_friendsendrender_wizardend

Instead you need to use wizard_value to get the "reverse translation" in your controller code like this:

steps:confirm_password,:confirm_profile,:find_friendsdefshowcasewizard_value(step)when:find_friends@friends=current_user.find_friendsendrender_wizardend

The important thing to remember is that step and the values in steps are always going to be in the same language if you're using the Wicked translations. If you need any values to match the values set directly in your controller, or the names of your files (i.e. views/../confirm_password.html.erb, then you need to use wizard_value method.

Custom URLs

Very similar to using I18n from above but instead of making new files for different languages, you can stick with one language. Make sure you are using the right module:

includeWicked::Wizard::Translated

Then you'll need to specify translations in your language file. For me, the language I'm using is english so I can add translations to config/locales/en.yml

en:
hello: "hello world"wicked:
first: "verify_email"second: "if_you_are_popular_add_friends"

Now you can change the values in the URLs to whatever you want without changing your controller or your files, just modify your en.yml. If you're not using English you can set your default_locale to something other than en in your config/application.rb file.

config.i18n.default_locale=:de

Important: Don't forget to use wizard_value() method to make sure you are using the right cannonical values of step, previous_step, next_step, etc. If you are comparing them to non wicked generate values.

Custom crafted wizard urls: just another way Wicked makes your app a little more saintly.

Dynamic Step Names

If you wish to set the order of your steps dynamically you can do this with a prepend_before_filter and self.steps = like this:

includeWicked::Wizardprepend_before_filter:set_steps# ...privatedefset_stepsifparams[:flow] == "twitter"self.steps=[:ask_twitter,:ask_email]elsifparams[:flow] == "facebook"self.steps=[:ask_facebook,:ask_email]endend

Keywords

There are a few "magical" keywords that will take you to the first step, the last step, or the "final" action (the redirect that happens after the last step). Prior to version 0.6.0 these were hardcoded strings. Now they are constants which means you can access them or change them. They are:

Wicked::FIRST_STEPWicked::LAST_STEPWicked::FINISH_STEP

You can build links using these constants after_signup_path(Wicked::LAST_STEP) which will redirect the user to the first step you've specified. This might be useful for redirecting a user to a step when you're not already in a Wicked controller. If you change the step names, they are expected to be strings (not symbols).

About

Please poke around the source code, if you see easier ways to get a Rails controller to do what I want, let me know.

If you have a question file an issue or, find me on the Twitters @schneems.

This project rocks and uses MIT-LICENSE.

Contributing

  1. Fork it ( https://github.com/schneems/wicked/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

About

Use wicked to turn your controller into a wizard

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Step-By-Step Wizard Controllers

Build StatusCode Climate

Use wicked to make your Rails controllers into step-by-step wizards. To see Wicked in action check out the example Rails app or watch the screencast.

Why

Many times I'm left wanting a RESTful way to display a step by step process that may or not be associated with a resource. Wicked gives the flexibility to do what I want while hiding all the really nasty stuff you shouldn't do in a controller to make this possible. At its core Wicked is a RESTful(ish) state machine, but you don't need to know that, just use it.

Install

Add this to your Gemfile

gem'wicked'

Then run bundle install and you're ready to start

Quicklinks

How

We are going to build an 'after signup' wizard. If you don't have a current_user then check out how to Build a step-by-step object with Wicked.

First create a controller:

rails g controller after_signup

Add Routes into config/routes.rb:

resources:after_signup

Next include Wicked::Wizard in your controller

classAfterSignupController < ApplicationControllerincludeWicked::Wizardsteps:confirm_password,:confirm_profile,:find_friends# ...

You can also use the old way of inheriting from Wicked::WizardController.

classAfterSignupController < Wicked::WizardControllersteps:confirm_password,:confirm_profile,:find_friends# ...

The wizard is set to call steps in order in the show action, you can specify custom logic in your show using a case statement like below. To send someone to the first step in this wizard we can direct them to after_signup_path(:confirm_password).

classAfterSignupController < ApplicationControllerincludeWicked::Wizardsteps:confirm_password,:confirm_profile,:find_friendsdefshow@user=current_usercasestepwhen:find_friends@friends=@user.find_friendsendrender_wizardendend

Note: Wicked uses the :id parameter to control the flow of steps, if you need to have an id parameter, please use nested routes. See building objects with wicked for an example. It will need to be prefixed, for example a Product's :id would be :product_id

You'll need to call render_wizard at the end of your action to get the correct views to show up.

By default the wizard will render a view with the same name as the step. So for our controller AfterSignupController with a view path of /views/after_signup/ if call the :confirm_password step, our wizard will render /views/after_signup/confirm_password.html.erb

Then in your view you can use the helpers to get to the next step.

<%= link_to 'skip', next_wizard_path %>

You can manually specify which wizard action you want to link to by using the wizard_path helper.

<%= link_to 'skip', wizard_path(:find_friends) %>

In addition to showing sequential views we can update elements in our controller.

classAfterSignupController < ApplicationControllerincludeWicked::Wizardsteps:confirm_password,:confirm_profile,:find_friendsdefupdate@user=current_usercasestepwhen:confirm_password@user.update_attributes(params[:user])endsign_in(@user,bypass: true)# needed for deviserender_wizard@userendend

We're passing render_wizard our @user object here. If you pass an object into render_wizard it will show the next step if the object saves or re-render the previous view if it does not save.

To get to this update action, you simply need to submit a form that PUT's to the same url

<%= form_for @user, url: wizard_path, method: :put do |f| %><%= f.password_field :password %><%= f.password_field :password_confirmation %><%= f.submit "Change Password" %><% end %>

We explicitly tell the form to PUT above. If you forget this, you will get a warning about the create action not existing, or no route found for POST. Don't forget this.

In the controller if you find that you want to skip a step, you can do it simply by calling skip_step

defshow@user=current_usercasestepwhen:find_friendsif@user.has_facebook_access_token?@friends=@user.find_friendselseskip_stependendrender_wizardend

Now you've got a fully functioning AfterSignup controller! If you have questions or if you struggled with something, let me know on twitter, and i'll try to make it better or make the docs better.

Quick Reference

View/URL Helpers

wizard_path# Grabs the current path in the wizardwizard_path(:specific_step)# Url of the :specific_stepnext_wizard_path# Url of the next stepprevious_wizard_path# Url of the previous step# These only work while in a Wizard, and are not absolute paths# You can have multiple wizards in a project with multiple `wizard_path` calls

Controller Tidbits:

steps:first,:second# Sets the order of stepsstep# Gets current stepnext_step# Gets next stepprevious_step# Gets previous stepskip_step# Tells render_wizard to skip to the next logical stepjump_to(:specific_step)# Jump to :specific_steprender_wizard# Renders the current steprender_wizard(@user)# Shows next_step if @user.save, otherwise renders current step

Finally:

Don't forget to create your named views

app/
views/
controller_name/
first.html.erb
second.html.erb
# ...

Finish Wizard Path

You can specify the url that your user goes to by over-riding the finish_wizard_path in your wizard controller.

deffinish_wizard_pathuser_path(current_user)end

Testing with RSpec

# Test find_friends block of show actionget:show,id: :find_friends# Test find_friends block of update actionput:update,{'id'=>'find_friends',"user"=>{"id"=>@user.id.to_s}}

Internationalization of URLS (I18n)

If your site works in multiple languages, or if you just want more control over how your URLs look you can now use I18n with wicked. To do so you need to replace this:

includeWicked::Wizard

With this:

includeWicked::Wizard::Translated

This will allow you to specify translation keys instead of literal step names. Let's say you've got steps that look like this:

steps :first, :second

So the urls would be /after_signup/first and /after_signup/second. But you want them to show up differently for different locales. For example someone coming form a Spanish speaking locale should see /after_signup/uno and after_signup/dos.

To internationalize first you need to create your locales files under config/locales such as config/locales/es.yml for Spanish. You then need to add a first and second key under a wicked key like this:

es:
hello: "hola mundo"wicked:
first: "uno"second: "dos"

It would also be a good idea to create a english version under config/locales/en.yml or your english speaking friends will get errors. If your app already uses I18n you don't need to do anything else, if not you will need to make sure that you set the I18n.locale on each request you could do this somewhere like a before filter in your application_controller.rb

before_filter:set_localeprivatedefset_localeI18n.locale=params[:locale]ifparams[:locale].present?enddefdefault_url_options(options={}){locale: I18n.locale}end

For a screencast on setting up and using I18n check out Railscasts. You can also read the free I18n Rails Guide.

Now when you visit your controller with the proper locale set your URLs should be more readable like /after_signup/uno and after_signup/dos.

Wicked expects your files to be named the same as your keys, so when a user visits after_signup/dos with the es locale it will render the second.html.erb file.

Important: When you do this the value of step as well as next_step and previous_step and all the values within steps will be translated to what locale you are using. To translate them to the "canonical" values that you've have in your controller you'll need so use wizard_value method.

For example, if you had this in your controller, and you converted it to a use Wicked translations, so this will not work:

steps:confirm_password,:confirm_profile,:find_friendsdefshowcasestepwhen:find_friends@friends=current_user.find_friendsendrender_wizardend

Instead you need to use wizard_value to get the "reverse translation" in your controller code like this:

steps:confirm_password,:confirm_profile,:find_friendsdefshowcasewizard_value(step)when:find_friends@friends=current_user.find_friendsendrender_wizardend

The important thing to remember is that step and the values in steps are always going to be in the same language if you're using the Wicked translations. If you need any values to match the values set directly in your controller, or the names of your files (i.e. views/../confirm_password.html.erb, then you need to use wizard_value method.

Custom URLs

Very similar to using I18n from above but instead of making new files for different languages, you can stick with one language. Make sure you are using the right module:

includeWicked::Wizard::Translated

Then you'll need to specify translations in your language file. For me, the language I'm using is english so I can add translations to config/locales/en.yml

en:
hello: "hello world"wicked:
first: "verify_email"second: "if_you_are_popular_add_friends"

Now you can change the values in the URLs to whatever you want without changing your controller or your files, just modify your en.yml. If you're not using English you can set your default_locale to something other than en in your config/application.rb file.

config.i18n.default_locale=:de

Important: Don't forget to use wizard_value() method to make sure you are using the right cannonical values of step, previous_step, next_step, etc. If you are comparing them to non wicked generate values.

Custom crafted wizard urls: just another way Wicked makes your app a little more saintly.

Dynamic Step Names

If you wish to set the order of your steps dynamically you can do this with a prepend_before_filter and self.steps = like this:

includeWicked::Wizardprepend_before_filter:set_steps# ...privatedefset_stepsifparams[:flow] == "twitter"self.steps=[:ask_twitter,:ask_email]elsifparams[:flow] == "facebook"self.steps=[:ask_facebook,:ask_email]endend

Keywords

There are a few "magical" keywords that will take you to the first step, the last step, or the "final" action (the redirect that happens after the last step). Prior to version 0.6.0 these were hardcoded strings. Now they are constants which means you can access them or change them. They are:

Wicked::FIRST_STEPWicked::LAST_STEPWicked::FINISH_STEP

You can build links using these constants after_signup_path(Wicked::LAST_STEP) which will redirect the user to the first step you've specified. This might be useful for redirecting a user to a step when you're not already in a Wicked controller. If you change the step names, they are expected to be strings (not symbols).

About

Please poke around the source code, if you see easier ways to get a Rails controller to do what I want, let me know.

If you have a question file an issue or, find me on the Twitters @schneems.

This project rocks and uses MIT-LICENSE.

Contributing

  1. Fork it ( https://github.com/schneems/wicked/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

About

Use wicked to turn your controller into a wizard

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

Step-By-Step Wizard Controllers

Build StatusCode Climate

Use wicked to make your Rails controllers into step-by-step wizards. To see Wicked in action check out the example Rails app or watch the screencast.

Why

Many times I'm left wanting a RESTful way to display a step by step process that may or not be associated with a resource. Wicked gives the flexibility to do what I want while hiding all the really nasty stuff you shouldn't do in a controller to make this possible. At its core Wicked is a RESTful(ish) state machine, but you don't need to know that, just use it.

Install

Add this to your Gemfile

gem'wicked'

Then run bundle install and you're ready to start

Quicklinks

How

We are going to build an 'after signup' wizard. If you don't have a current_user then check out how to Build a step-by-step object with Wicked.

First create a controller:

rails g controller after_signup

Add Routes into config/routes.rb:

resources:after_signup

Next include Wicked::Wizard in your controller

classAfterSignupController < ApplicationControllerincludeWicked::Wizardsteps:confirm_password,:confirm_profile,:find_friends# ...

You can also use the old way of inheriting from Wicked::WizardController.

classAfterSignupController < Wicked::WizardControllersteps:confirm_password,:confirm_profile,:find_friends# ...

The wizard is set to call steps in order in the show action, you can specify custom logic in your show using a case statement like below. To send someone to the first step in this wizard we can direct them to after_signup_path(:confirm_password).

classAfterSignupController < ApplicationControllerincludeWicked::Wizardsteps:confirm_password,:confirm_profile,:find_friendsdefshow@user=current_usercasestepwhen:find_friends@friends=@user.find_friendsendrender_wizardendend

Note: Wicked uses the :id parameter to control the flow of steps, if you need to have an id parameter, please use nested routes. See building objects with wicked for an example. It will need to be prefixed, for example a Product's :id would be :product_id

You'll need to call render_wizard at the end of your action to get the correct views to show up.

By default the wizard will render a view with the same name as the step. So for our controller AfterSignupController with a view path of /views/after_signup/ if call the :confirm_password step, our wizard will render /views/after_signup/confirm_password.html.erb

Then in your view you can use the helpers to get to the next step.

<%= link_to 'skip', next_wizard_path %>

You can manually specify which wizard action you want to link to by using the wizard_path helper.

<%= link_to 'skip', wizard_path(:find_friends) %>

In addition to showing sequential views we can update elements in our controller.

classAfterSignupController < ApplicationControllerincludeWicked::Wizardsteps:confirm_password,:confirm_profile,:find_friendsdefupdate@user=current_usercasestepwhen:confirm_password@user.update_attributes(params[:user])endsign_in(@user,bypass: true)# needed for deviserender_wizard@userendend

We're passing render_wizard our @user object here. If you pass an object into render_wizard it will show the next step if the object saves or re-render the previous view if it does not save.

To get to this update action, you simply need to submit a form that PUT's to the same url

<%= form_for @user, url: wizard_path, method: :put do |f| %><%= f.password_field :password %><%= f.password_field :password_confirmation %><%= f.submit "Change Password" %><% end %>

We explicitly tell the form to PUT above. If you forget this, you will get a warning about the create action not existing, or no route found for POST. Don't forget this.

In the controller if you find that you want to skip a step, you can do it simply by calling skip_step

defshow@user=current_usercasestepwhen:find_friendsif@user.has_facebook_access_token?@friends=@user.find_friendselseskip_stependendrender_wizardend

Now you've got a fully functioning AfterSignup controller! If you have questions or if you struggled with something, let me know on twitter, and i'll try to make it better or make the docs better.

Quick Reference

View/URL Helpers

wizard_path# Grabs the current path in the wizardwizard_path(:specific_step)# Url of the :specific_stepnext_wizard_path# Url of the next stepprevious_wizard_path# Url of the previous step# These only work while in a Wizard, and are not absolute paths# You can have multiple wizards in a project with multiple `wizard_path` calls

Controller Tidbits:

steps:first,:second# Sets the order of stepsstep# Gets current stepnext_step# Gets next stepprevious_step# Gets previous stepskip_step# Tells render_wizard to skip to the next logical stepjump_to(:specific_step)# Jump to :specific_steprender_wizard# Renders the current steprender_wizard(@user)# Shows next_step if @user.save, otherwise renders current step

Finally:

Don't forget to create your named views

app/
views/
controller_name/
first.html.erb
second.html.erb
# ...

Finish Wizard Path

You can specify the url that your user goes to by over-riding the finish_wizard_path in your wizard controller.

deffinish_wizard_pathuser_path(current_user)end

Testing with RSpec

# Test find_friends block of show actionget:show,id: :find_friends# Test find_friends block of update actionput:update,{'id'=>'find_friends',"user"=>{"id"=>@user.id.to_s}}

Internationalization of URLS (I18n)

If your site works in multiple languages, or if you just want more control over how your URLs look you can now use I18n with wicked. To do so you need to replace this:

includeWicked::Wizard

With this:

includeWicked::Wizard::Translated

This will allow you to specify translation keys instead of literal step names. Let's say you've got steps that look like this:

steps :first, :second

So the urls would be /after_signup/first and /after_signup/second. But you want them to show up differently for different locales. For example someone coming form a Spanish speaking locale should see /after_signup/uno and after_signup/dos.

To internationalize first you need to create your locales files under config/locales such as config/locales/es.yml for Spanish. You then need to add a first and second key under a wicked key like this:

es:
hello: "hola mundo"wicked:
first: "uno"second: "dos"

It would also be a good idea to create a english version under config/locales/en.yml or your english speaking friends will get errors. If your app already uses I18n you don't need to do anything else, if not you will need to make sure that you set the I18n.locale on each request you could do this somewhere like a before filter in your application_controller.rb

before_filter:set_localeprivatedefset_localeI18n.locale=params[:locale]ifparams[:locale].present?enddefdefault_url_options(options={}){locale: I18n.locale}end

For a screencast on setting up and using I18n check out Railscasts. You can also read the free I18n Rails Guide.

Now when you visit your controller with the proper locale set your URLs should be more readable like /after_signup/uno and after_signup/dos.

Wicked expects your files to be named the same as your keys, so when a user visits after_signup/dos with the es locale it will render the second.html.erb file.

Important: When you do this the value of step as well as next_step and previous_step and all the values within steps will be translated to what locale you are using. To translate them to the "canonical" values that you've have in your controller you'll need so use wizard_value method.

For example, if you had this in your controller, and you converted it to a use Wicked translations, so this will not work:

steps:confirm_password,:confirm_profile,:find_friendsdefshowcasestepwhen:find_friends@friends=current_user.find_friendsendrender_wizardend

Instead you need to use wizard_value to get the "reverse translation" in your controller code like this:

steps:confirm_password,:confirm_profile,:find_friendsdefshowcasewizard_value(step)when:find_friends@friends=current_user.find_friendsendrender_wizardend

The important thing to remember is that step and the values in steps are always going to be in the same language if you're using the Wicked translations. If you need any values to match the values set directly in your controller, or the names of your files (i.e. views/../confirm_password.html.erb, then you need to use wizard_value method.

Custom URLs

Very similar to using I18n from above but instead of making new files for different languages, you can stick with one language. Make sure you are using the right module:

includeWicked::Wizard::Translated

Then you'll need to specify translations in your language file. For me, the language I'm using is english so I can add translations to config/locales/en.yml

en:
hello: "hello world"wicked:
first: "verify_email"second: "if_you_are_popular_add_friends"

Now you can change the values in the URLs to whatever you want without changing your controller or your files, just modify your en.yml. If you're not using English you can set your default_locale to something other than en in your config/application.rb file.

config.i18n.default_locale=:de

Important: Don't forget to use wizard_value() method to make sure you are using the right cannonical values of step, previous_step, next_step, etc. If you are comparing them to non wicked generate values.

Custom crafted wizard urls: just another way Wicked makes your app a little more saintly.

Dynamic Step Names

If you wish to set the order of your steps dynamically you can do this with a prepend_before_filter and self.steps = like this:

includeWicked::Wizardprepend_before_filter:set_steps# ...privatedefset_stepsifparams[:flow] == "twitter"self.steps=[:ask_twitter,:ask_email]elsifparams[:flow] == "facebook"self.steps=[:ask_facebook,:ask_email]endend

Keywords

There are a few "magical" keywords that will take you to the first step, the last step, or the "final" action (the redirect that happens after the last step). Prior to version 0.6.0 these were hardcoded strings. Now they are constants which means you can access them or change them. They are:

Wicked::FIRST_STEPWicked::LAST_STEPWicked::FINISH_STEP

You can build links using these constants after_signup_path(Wicked::LAST_STEP) which will redirect the user to the first step you've specified. This might be useful for redirecting a user to a step when you're not already in a Wicked controller. If you change the step names, they are expected to be strings (not symbols).

About

Please poke around the source code, if you see easier ways to get a Rails controller to do what I want, let me know.

If you have a question file an issue or, find me on the Twitters @schneems.

This project rocks and uses MIT-LICENSE.

Contributing

  1. Fork it ( https://github.com/schneems/wicked/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

About

Use wicked to turn your controller into a wizard

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Step-By-Step Wizard Controllers

Build StatusCode Climate

Use wicked to make your Rails controllers into step-by-step wizards. To see Wicked in action check out the example Rails app or watch the screencast.

Why

Many times I'm left wanting a RESTful way to display a step by step process that may or not be associated with a resource. Wicked gives the flexibility to do what I want while hiding all the really nasty stuff you shouldn't do in a controller to make this possible. At its core Wicked is a RESTful(ish) state machine, but you don't need to know that, just use it.

Install

Add this to your Gemfile

gem'wicked'

Then run bundle install and you're ready to start

Quicklinks

How

We are going to build an 'after signup' wizard. If you don't have a current_user then check out how to Build a step-by-step object with Wicked.

First create a controller:

rails g controller after_signup

Add Routes into config/routes.rb:

resources:after_signup

Next include Wicked::Wizard in your controller

classAfterSignupController < ApplicationControllerincludeWicked::Wizardsteps:confirm_password,:confirm_profile,:find_friends# ...

You can also use the old way of inheriting from Wicked::WizardController.

classAfterSignupController < Wicked::WizardControllersteps:confirm_password,:confirm_profile,:find_friends# ...

The wizard is set to call steps in order in the show action, you can specify custom logic in your show using a case statement like below. To send someone to the first step in this wizard we can direct them to after_signup_path(:confirm_password).

classAfterSignupController < ApplicationControllerincludeWicked::Wizardsteps:confirm_password,:confirm_profile,:find_friendsdefshow@user=current_usercasestepwhen:find_friends@friends=@user.find_friendsendrender_wizardendend

Note: Wicked uses the :id parameter to control the flow of steps, if you need to have an id parameter, please use nested routes. See building objects with wicked for an example. It will need to be prefixed, for example a Product's :id would be :product_id

You'll need to call render_wizard at the end of your action to get the correct views to show up.

By default the wizard will render a view with the same name as the step. So for our controller AfterSignupController with a view path of /views/after_signup/ if call the :confirm_password step, our wizard will render /views/after_signup/confirm_password.html.erb

Then in your view you can use the helpers to get to the next step.

<%= link_to 'skip', next_wizard_path %>

You can manually specify which wizard action you want to link to by using the wizard_path helper.

<%= link_to 'skip', wizard_path(:find_friends) %>

In addition to showing sequential views we can update elements in our controller.

classAfterSignupController < ApplicationControllerincludeWicked::Wizardsteps:confirm_password,:confirm_profile,:find_friendsdefupdate@user=current_usercasestepwhen:confirm_password@user.update_attributes(params[:user])endsign_in(@user,bypass: true)# needed for deviserender_wizard@userendend

We're passing render_wizard our @user object here. If you pass an object into render_wizard it will show the next step if the object saves or re-render the previous view if it does not save.

To get to this update action, you simply need to submit a form that PUT's to the same url

<%= form_for @user, url: wizard_path, method: :put do |f| %><%= f.password_field :password %><%= f.password_field :password_confirmation %><%= f.submit "Change Password" %><% end %>

We explicitly tell the form to PUT above. If you forget this, you will get a warning about the create action not existing, or no route found for POST. Don't forget this.

In the controller if you find that you want to skip a step, you can do it simply by calling skip_step

defshow@user=current_usercasestepwhen:find_friendsif@user.has_facebook_access_token?@friends=@user.find_friendselseskip_stependendrender_wizardend

Now you've got a fully functioning AfterSignup controller! If you have questions or if you struggled with something, let me know on twitter, and i'll try to make it better or make the docs better.

Quick Reference

View/URL Helpers

wizard_path# Grabs the current path in the wizardwizard_path(:specific_step)# Url of the :specific_stepnext_wizard_path# Url of the next stepprevious_wizard_path# Url of the previous step# These only work while in a Wizard, and are not absolute paths# You can have multiple wizards in a project with multiple `wizard_path` calls

Controller Tidbits:

steps:first,:second# Sets the order of stepsstep# Gets current stepnext_step# Gets next stepprevious_step# Gets previous stepskip_step# Tells render_wizard to skip to the next logical stepjump_to(:specific_step)# Jump to :specific_steprender_wizard# Renders the current steprender_wizard(@user)# Shows next_step if @user.save, otherwise renders current step

Finally:

Don't forget to create your named views

app/
views/
controller_name/
first.html.erb
second.html.erb
# ...

Finish Wizard Path

You can specify the url that your user goes to by over-riding the finish_wizard_path in your wizard controller.

deffinish_wizard_pathuser_path(current_user)end

Testing with RSpec

# Test find_friends block of show actionget:show,id: :find_friends# Test find_friends block of update actionput:update,{'id'=>'find_friends',"user"=>{"id"=>@user.id.to_s}}

Internationalization of URLS (I18n)

If your site works in multiple languages, or if you just want more control over how your URLs look you can now use I18n with wicked. To do so you need to replace this:

includeWicked::Wizard

With this:

includeWicked::Wizard::Translated

This will allow you to specify translation keys instead of literal step names. Let's say you've got steps that look like this:

steps :first, :second

So the urls would be /after_signup/first and /after_signup/second. But you want them to show up differently for different locales. For example someone coming form a Spanish speaking locale should see /after_signup/uno and after_signup/dos.

To internationalize first you need to create your locales files under config/locales such as config/locales/es.yml for Spanish. You then need to add a first and second key under a wicked key like this:

es:
hello: "hola mundo"wicked:
first: "uno"second: "dos"

It would also be a good idea to create a english version under config/locales/en.yml or your english speaking friends will get errors. If your app already uses I18n you don't need to do anything else, if not you will need to make sure that you set the I18n.locale on each request you could do this somewhere like a before filter in your application_controller.rb

before_filter:set_localeprivatedefset_localeI18n.locale=params[:locale]ifparams[:locale].present?enddefdefault_url_options(options={}){locale: I18n.locale}end

For a screencast on setting up and using I18n check out Railscasts. You can also read the free I18n Rails Guide.

Now when you visit your controller with the proper locale set your URLs should be more readable like /after_signup/uno and after_signup/dos.

Wicked expects your files to be named the same as your keys, so when a user visits after_signup/dos with the es locale it will render the second.html.erb file.

Important: When you do this the value of step as well as next_step and previous_step and all the values within steps will be translated to what locale you are using. To translate them to the "canonical" values that you've have in your controller you'll need so use wizard_value method.

For example, if you had this in your controller, and you converted it to a use Wicked translations, so this will not work:

steps:confirm_password,:confirm_profile,:find_friendsdefshowcasestepwhen:find_friends@friends=current_user.find_friendsendrender_wizardend

Instead you need to use wizard_value to get the "reverse translation" in your controller code like this:

steps:confirm_password,:confirm_profile,:find_friendsdefshowcasewizard_value(step)when:find_friends@friends=current_user.find_friendsendrender_wizardend

The important thing to remember is that step and the values in steps are always going to be in the same language if you're using the Wicked translations. If you need any values to match the values set directly in your controller, or the names of your files (i.e. views/../confirm_password.html.erb, then you need to use wizard_value method.

Custom URLs

Very similar to using I18n from above but instead of making new files for different languages, you can stick with one language. Make sure you are using the right module:

includeWicked::Wizard::Translated

Then you'll need to specify translations in your language file. For me, the language I'm using is english so I can add translations to config/locales/en.yml

en:
hello: "hello world"wicked:
first: "verify_email"second: "if_you_are_popular_add_friends"

Now you can change the values in the URLs to whatever you want without changing your controller or your files, just modify your en.yml. If you're not using English you can set your default_locale to something other than en in your config/application.rb file.

config.i18n.default_locale=:de

Important: Don't forget to use wizard_value() method to make sure you are using the right cannonical values of step, previous_step, next_step, etc. If you are comparing them to non wicked generate values.

Custom crafted wizard urls: just another way Wicked makes your app a little more saintly.

Dynamic Step Names

If you wish to set the order of your steps dynamically you can do this with a prepend_before_filter and self.steps = like this:

includeWicked::Wizardprepend_before_filter:set_steps# ...privatedefset_stepsifparams[:flow] == "twitter"self.steps=[:ask_twitter,:ask_email]elsifparams[:flow] == "facebook"self.steps=[:ask_facebook,:ask_email]endend

Keywords

There are a few "magical" keywords that will take you to the first step, the last step, or the "final" action (the redirect that happens after the last step). Prior to version 0.6.0 these were hardcoded strings. Now they are constants which means you can access them or change them. They are:

Wicked::FIRST_STEPWicked::LAST_STEPWicked::FINISH_STEP

You can build links using these constants after_signup_path(Wicked::LAST_STEP) which will redirect the user to the first step you've specified. This might be useful for redirecting a user to a step when you're not already in a Wicked controller. If you change the step names, they are expected to be strings (not symbols).

About

Please poke around the source code, if you see easier ways to get a Rails controller to do what I want, let me know.

If you have a question file an issue or, find me on the Twitters @schneems.

This project rocks and uses MIT-LICENSE.

Contributing

  1. Fork it ( https://github.com/schneems/wicked/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

About

Use wicked to turn your controller into a wizard

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Step-By-Step Wizard Controllers

Build StatusCode Climate

Use wicked to make your Rails controllers into step-by-step wizards. To see Wicked in action check out the example Rails app or watch the screencast.

Why

Many times I'm left wanting a RESTful way to display a step by step process that may or not be associated with a resource. Wicked gives the flexibility to do what I want while hiding all the really nasty stuff you shouldn't do in a controller to make this possible. At its core Wicked is a RESTful(ish) state machine, but you don't need to know that, just use it.

Install

Add this to your Gemfile

gem'wicked'

Then run bundle install and you're ready to start

Quicklinks

How

We are going to build an 'after signup' wizard. If you don't have a current_user then check out how to Build a step-by-step object with Wicked.

First create a controller:

rails g controller after_signup

Add Routes into config/routes.rb:

resources:after_signup

Next include Wicked::Wizard in your controller

classAfterSignupController < ApplicationControllerincludeWicked::Wizardsteps:confirm_password,:confirm_profile,:find_friends# ...

You can also use the old way of inheriting from Wicked::WizardController.

classAfterSignupController < Wicked::WizardControllersteps:confirm_password,:confirm_profile,:find_friends# ...

The wizard is set to call steps in order in the show action, you can specify custom logic in your show using a case statement like below. To send someone to the first step in this wizard we can direct them to after_signup_path(:confirm_password).

classAfterSignupController < ApplicationControllerincludeWicked::Wizardsteps:confirm_password,:confirm_profile,:find_friendsdefshow@user=current_usercasestepwhen:find_friends@friends=@user.find_friendsendrender_wizardendend

Note: Wicked uses the :id parameter to control the flow of steps, if you need to have an id parameter, please use nested routes. See building objects with wicked for an example. It will need to be prefixed, for example a Product's :id would be :product_id

You'll need to call render_wizard at the end of your action to get the correct views to show up.

By default the wizard will render a view with the same name as the step. So for our controller AfterSignupController with a view path of /views/after_signup/ if call the :confirm_password step, our wizard will render /views/after_signup/confirm_password.html.erb

Then in your view you can use the helpers to get to the next step.

<%= link_to 'skip', next_wizard_path %>

You can manually specify which wizard action you want to link to by using the wizard_path helper.

<%= link_to 'skip', wizard_path(:find_friends) %>

In addition to showing sequential views we can update elements in our controller.

classAfterSignupController < ApplicationControllerincludeWicked::Wizardsteps:confirm_password,:confirm_profile,:find_friendsdefupdate@user=current_usercasestepwhen:confirm_password@user.update_attributes(params[:user])endsign_in(@user,bypass: true)# needed for deviserender_wizard@userendend

We're passing render_wizard our @user object here. If you pass an object into render_wizard it will show the next step if the object saves or re-render the previous view if it does not save.

To get to this update action, you simply need to submit a form that PUT's to the same url

<%= form_for @user, url: wizard_path, method: :put do |f| %><%= f.password_field :password %><%= f.password_field :password_confirmation %><%= f.submit "Change Password" %><% end %>

We explicitly tell the form to PUT above. If you forget this, you will get a warning about the create action not existing, or no route found for POST. Don't forget this.

In the controller if you find that you want to skip a step, you can do it simply by calling skip_step

defshow@user=current_usercasestepwhen:find_friendsif@user.has_facebook_access_token?@friends=@user.find_friendselseskip_stependendrender_wizardend

Now you've got a fully functioning AfterSignup controller! If you have questions or if you struggled with something, let me know on twitter, and i'll try to make it better or make the docs better.

Quick Reference

View/URL Helpers

wizard_path# Grabs the current path in the wizardwizard_path(:specific_step)# Url of the :specific_stepnext_wizard_path# Url of the next stepprevious_wizard_path# Url of the previous step# These only work while in a Wizard, and are not absolute paths# You can have multiple wizards in a project with multiple `wizard_path` calls

Controller Tidbits:

steps:first,:second# Sets the order of stepsstep# Gets current stepnext_step# Gets next stepprevious_step# Gets previous stepskip_step# Tells render_wizard to skip to the next logical stepjump_to(:specific_step)# Jump to :specific_steprender_wizard# Renders the current steprender_wizard(@user)# Shows next_step if @user.save, otherwise renders current step

Finally:

Don't forget to create your named views

app/
views/
controller_name/
first.html.erb
second.html.erb
# ...

Finish Wizard Path

You can specify the url that your user goes to by over-riding the finish_wizard_path in your wizard controller.

deffinish_wizard_pathuser_path(current_user)end

Testing with RSpec

# Test find_friends block of show actionget:show,id: :find_friends# Test find_friends block of update actionput:update,{'id'=>'find_friends',"user"=>{"id"=>@user.id.to_s}}

Internationalization of URLS (I18n)

If your site works in multiple languages, or if you just want more control over how your URLs look you can now use I18n with wicked. To do so you need to replace this:

includeWicked::Wizard

With this:

includeWicked::Wizard::Translated

This will allow you to specify translation keys instead of literal step names. Let's say you've got steps that look like this:

steps :first, :second

So the urls would be /after_signup/first and /after_signup/second. But you want them to show up differently for different locales. For example someone coming form a Spanish speaking locale should see /after_signup/uno and after_signup/dos.

To internationalize first you need to create your locales files under config/locales such as config/locales/es.yml for Spanish. You then need to add a first and second key under a wicked key like this:

es:
hello: "hola mundo"wicked:
first: "uno"second: "dos"

It would also be a good idea to create a english version under config/locales/en.yml or your english speaking friends will get errors. If your app already uses I18n you don't need to do anything else, if not you will need to make sure that you set the I18n.locale on each request you could do this somewhere like a before filter in your application_controller.rb

before_filter:set_localeprivatedefset_localeI18n.locale=params[:locale]ifparams[:locale].present?enddefdefault_url_options(options={}){locale: I18n.locale}end

For a screencast on setting up and using I18n check out Railscasts. You can also read the free I18n Rails Guide.

Now when you visit your controller with the proper locale set your URLs should be more readable like /after_signup/uno and after_signup/dos.

Wicked expects your files to be named the same as your keys, so when a user visits after_signup/dos with the es locale it will render the second.html.erb file.

Important: When you do this the value of step as well as next_step and previous_step and all the values within steps will be translated to what locale you are using. To translate them to the "canonical" values that you've have in your controller you'll need so use wizard_value method.

For example, if you had this in your controller, and you converted it to a use Wicked translations, so this will not work:

steps:confirm_password,:confirm_profile,:find_friendsdefshowcasestepwhen:find_friends@friends=current_user.find_friendsendrender_wizardend

Instead you need to use wizard_value to get the "reverse translation" in your controller code like this:

steps:confirm_password,:confirm_profile,:find_friendsdefshowcasewizard_value(step)when:find_friends@friends=current_user.find_friendsendrender_wizardend

The important thing to remember is that step and the values in steps are always going to be in the same language if you're using the Wicked translations. If you need any values to match the values set directly in your controller, or the names of your files (i.e. views/../confirm_password.html.erb, then you need to use wizard_value method.

Custom URLs

Very similar to using I18n from above but instead of making new files for different languages, you can stick with one language. Make sure you are using the right module:

includeWicked::Wizard::Translated

Then you'll need to specify translations in your language file. For me, the language I'm using is english so I can add translations to config/locales/en.yml

en:
hello: "hello world"wicked:
first: "verify_email"second: "if_you_are_popular_add_friends"

Now you can change the values in the URLs to whatever you want without changing your controller or your files, just modify your en.yml. If you're not using English you can set your default_locale to something other than en in your config/application.rb file.

config.i18n.default_locale=:de

Important: Don't forget to use wizard_value() method to make sure you are using the right cannonical values of step, previous_step, next_step, etc. If you are comparing them to non wicked generate values.

Custom crafted wizard urls: just another way Wicked makes your app a little more saintly.

Dynamic Step Names

If you wish to set the order of your steps dynamically you can do this with a prepend_before_filter and self.steps = like this:

includeWicked::Wizardprepend_before_filter:set_steps# ...privatedefset_stepsifparams[:flow] == "twitter"self.steps=[:ask_twitter,:ask_email]elsifparams[:flow] == "facebook"self.steps=[:ask_facebook,:ask_email]endend

Keywords

There are a few "magical" keywords that will take you to the first step, the last step, or the "final" action (the redirect that happens after the last step). Prior to version 0.6.0 these were hardcoded strings. Now they are constants which means you can access them or change them. They are:

Wicked::FIRST_STEPWicked::LAST_STEPWicked::FINISH_STEP

You can build links using these constants after_signup_path(Wicked::LAST_STEP) which will redirect the user to the first step you've specified. This might be useful for redirecting a user to a step when you're not already in a Wicked controller. If you change the step names, they are expected to be strings (not symbols).

About

Please poke around the source code, if you see easier ways to get a Rails controller to do what I want, let me know.

If you have a question file an issue or, find me on the Twitters @schneems.

This project rocks and uses MIT-LICENSE.

Contributing

  1. Fork it ( https://github.com/schneems/wicked/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

About

Use wicked to turn your controller into a wizard

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

Step-By-Step Wizard Controllers

Build StatusCode Climate

Use wicked to make your Rails controllers into step-by-step wizards. To see Wicked in action check out the example Rails app or watch the screencast.

Why

Many times I'm left wanting a RESTful way to display a step by step process that may or not be associated with a resource. Wicked gives the flexibility to do what I want while hiding all the really nasty stuff you shouldn't do in a controller to make this possible. At its core Wicked is a RESTful(ish) state machine, but you don't need to know that, just use it.

Install

Add this to your Gemfile

gem'wicked'

Then run bundle install and you're ready to start

Quicklinks

How

We are going to build an 'after signup' wizard. If you don't have a current_user then check out how to Build a step-by-step object with Wicked.

First create a controller:

rails g controller after_signup

Add Routes into config/routes.rb:

resources:after_signup

Next include Wicked::Wizard in your controller

classAfterSignupController < ApplicationControllerincludeWicked::Wizardsteps:confirm_password,:confirm_profile,:find_friends# ...

You can also use the old way of inheriting from Wicked::WizardController.

classAfterSignupController < Wicked::WizardControllersteps:confirm_password,:confirm_profile,:find_friends# ...

The wizard is set to call steps in order in the show action, you can specify custom logic in your show using a case statement like below. To send someone to the first step in this wizard we can direct them to after_signup_path(:confirm_password).

classAfterSignupController < ApplicationControllerincludeWicked::Wizardsteps:confirm_password,:confirm_profile,:find_friendsdefshow@user=current_usercasestepwhen:find_friends@friends=@user.find_friendsendrender_wizardendend

Note: Wicked uses the :id parameter to control the flow of steps, if you need to have an id parameter, please use nested routes. See building objects with wicked for an example. It will need to be prefixed, for example a Product's :id would be :product_id

You'll need to call render_wizard at the end of your action to get the correct views to show up.

By default the wizard will render a view with the same name as the step. So for our controller AfterSignupController with a view path of /views/after_signup/ if call the :confirm_password step, our wizard will render /views/after_signup/confirm_password.html.erb

Then in your view you can use the helpers to get to the next step.

<%= link_to 'skip', next_wizard_path %>

You can manually specify which wizard action you want to link to by using the wizard_path helper.

<%= link_to 'skip', wizard_path(:find_friends) %>

In addition to showing sequential views we can update elements in our controller.

classAfterSignupController < ApplicationControllerincludeWicked::Wizardsteps:confirm_password,:confirm_profile,:find_friendsdefupdate@user=current_usercasestepwhen:confirm_password@user.update_attributes(params[:user])endsign_in(@user,bypass: true)# needed for deviserender_wizard@userendend

We're passing render_wizard our @user object here. If you pass an object into render_wizard it will show the next step if the object saves or re-render the previous view if it does not save.

To get to this update action, you simply need to submit a form that PUT's to the same url

<%= form_for @user, url: wizard_path, method: :put do |f| %><%= f.password_field :password %><%= f.password_field :password_confirmation %><%= f.submit "Change Password" %><% end %>

We explicitly tell the form to PUT above. If you forget this, you will get a warning about the create action not existing, or no route found for POST. Don't forget this.

In the controller if you find that you want to skip a step, you can do it simply by calling skip_step

defshow@user=current_usercasestepwhen:find_friendsif@user.has_facebook_access_token?@friends=@user.find_friendselseskip_stependendrender_wizardend

Now you've got a fully functioning AfterSignup controller! If you have questions or if you struggled with something, let me know on twitter, and i'll try to make it better or make the docs better.

Quick Reference

View/URL Helpers

wizard_path# Grabs the current path in the wizardwizard_path(:specific_step)# Url of the :specific_stepnext_wizard_path# Url of the next stepprevious_wizard_path# Url of the previous step# These only work while in a Wizard, and are not absolute paths# You can have multiple wizards in a project with multiple `wizard_path` calls

Controller Tidbits:

steps:first,:second# Sets the order of stepsstep# Gets current stepnext_step# Gets next stepprevious_step# Gets previous stepskip_step# Tells render_wizard to skip to the next logical stepjump_to(:specific_step)# Jump to :specific_steprender_wizard# Renders the current steprender_wizard(@user)# Shows next_step if @user.save, otherwise renders current step

Finally:

Don't forget to create your named views

app/
views/
controller_name/
first.html.erb
second.html.erb
# ...

Finish Wizard Path

You can specify the url that your user goes to by over-riding the finish_wizard_path in your wizard controller.

deffinish_wizard_pathuser_path(current_user)end

Testing with RSpec

# Test find_friends block of show actionget:show,id: :find_friends# Test find_friends block of update actionput:update,{'id'=>'find_friends',"user"=>{"id"=>@user.id.to_s}}

Internationalization of URLS (I18n)

If your site works in multiple languages, or if you just want more control over how your URLs look you can now use I18n with wicked. To do so you need to replace this:

includeWicked::Wizard

With this:

includeWicked::Wizard::Translated

This will allow you to specify translation keys instead of literal step names. Let's say you've got steps that look like this:

steps :first, :second

So the urls would be /after_signup/first and /after_signup/second. But you want them to show up differently for different locales. For example someone coming form a Spanish speaking locale should see /after_signup/uno and after_signup/dos.

To internationalize first you need to create your locales files under config/locales such as config/locales/es.yml for Spanish. You then need to add a first and second key under a wicked key like this:

es:
hello: "hola mundo"wicked:
first: "uno"second: "dos"

It would also be a good idea to create a english version under config/locales/en.yml or your english speaking friends will get errors. If your app already uses I18n you don't need to do anything else, if not you will need to make sure that you set the I18n.locale on each request you could do this somewhere like a before filter in your application_controller.rb

before_filter:set_localeprivatedefset_localeI18n.locale=params[:locale]ifparams[:locale].present?enddefdefault_url_options(options={}){locale: I18n.locale}end

For a screencast on setting up and using I18n check out Railscasts. You can also read the free I18n Rails Guide.

Now when you visit your controller with the proper locale set your URLs should be more readable like /after_signup/uno and after_signup/dos.

Wicked expects your files to be named the same as your keys, so when a user visits after_signup/dos with the es locale it will render the second.html.erb file.

Important: When you do this the value of step as well as next_step and previous_step and all the values within steps will be translated to what locale you are using. To translate them to the "canonical" values that you've have in your controller you'll need so use wizard_value method.

For example, if you had this in your controller, and you converted it to a use Wicked translations, so this will not work:

steps:confirm_password,:confirm_profile,:find_friendsdefshowcasestepwhen:find_friends@friends=current_user.find_friendsendrender_wizardend

Instead you need to use wizard_value to get the "reverse translation" in your controller code like this:

steps:confirm_password,:confirm_profile,:find_friendsdefshowcasewizard_value(step)when:find_friends@friends=current_user.find_friendsendrender_wizardend

The important thing to remember is that step and the values in steps are always going to be in the same language if you're using the Wicked translations. If you need any values to match the values set directly in your controller, or the names of your files (i.e. views/../confirm_password.html.erb, then you need to use wizard_value method.

Custom URLs

Very similar to using I18n from above but instead of making new files for different languages, you can stick with one language. Make sure you are using the right module:

includeWicked::Wizard::Translated

Then you'll need to specify translations in your language file. For me, the language I'm using is english so I can add translations to config/locales/en.yml

en:
hello: "hello world"wicked:
first: "verify_email"second: "if_you_are_popular_add_friends"

Now you can change the values in the URLs to whatever you want without changing your controller or your files, just modify your en.yml. If you're not using English you can set your default_locale to something other than en in your config/application.rb file.

config.i18n.default_locale=:de

Important: Don't forget to use wizard_value() method to make sure you are using the right cannonical values of step, previous_step, next_step, etc. If you are comparing them to non wicked generate values.

Custom crafted wizard urls: just another way Wicked makes your app a little more saintly.

Dynamic Step Names

If you wish to set the order of your steps dynamically you can do this with a prepend_before_filter and self.steps = like this:

includeWicked::Wizardprepend_before_filter:set_steps# ...privatedefset_stepsifparams[:flow] == "twitter"self.steps=[:ask_twitter,:ask_email]elsifparams[:flow] == "facebook"self.steps=[:ask_facebook,:ask_email]endend

Keywords

There are a few "magical" keywords that will take you to the first step, the last step, or the "final" action (the redirect that happens after the last step). Prior to version 0.6.0 these were hardcoded strings. Now they are constants which means you can access them or change them. They are:

Wicked::FIRST_STEPWicked::LAST_STEPWicked::FINISH_STEP

You can build links using these constants after_signup_path(Wicked::LAST_STEP) which will redirect the user to the first step you've specified. This might be useful for redirecting a user to a step when you're not already in a Wicked controller. If you change the step names, they are expected to be strings (not symbols).

About

Please poke around the source code, if you see easier ways to get a Rails controller to do what I want, let me know.

If you have a question file an issue or, find me on the Twitters @schneems.

This project rocks and uses MIT-LICENSE.

Contributing

  1. Fork it ( https://github.com/schneems/wicked/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

About

Use wicked to turn your controller into a wizard

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages