javascriptruby-on-railsbootstrap-modalmulti-stepwicked-gem

Rails wicked wizard with javascript


I want to create a wizard in js.

steps   :first_step,
        :second_step

In my 'controller_step'

def show
        case step
            when :first_step
                @r  = R.new
            when :second_step

            end
        render_wizard
end

def update
        case step
            when :first_step
                @r = R.new(r_params)
            when :second_step

            end

        render_wizard @r
end

I have problems after the update of the first step. I'm receive the following error message:

"Missing template controller_step/second_step, application/second_step with {:locale=>[:en], :formats=>[:html], :variants=>[], :handlers=>[:erb, :builder, :raw, :ruby, :coffee, :jbuilder]}. ".

How can I force loading of js templete? I would load "second_step.js.erb".

I tried to change the update method:

respond_to do |format|
  format.js { render :js => ( render_wizard @r ) }  
end

Of course I get the following error:

"AbstractController::DoubleRenderError in ...Controller#update Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like "redirect_to(...) and return"."

I also tried to change the code (in update):

respond_to do |format|
   format.js { render :js => ( render_wizard @room_types and return ) } 
end

I'm get the same error ( ... application/second_step with {:locale=>[:en], :formats=>[:html] .... )

P.S.

In view of the first step:

<%= form_for(@r, url: wizard_path, method: :put, remote: true) do |f| %>
   ....
   <%= f.submit "Submit", class: "btn btn-default" %>
<% end %>

How do I fix ? thanks in advance


Solution

  • The #render_wizard method defined in the Wicked::Controller::Concerns::RenderRedirect is a wrapper method around the ActionController::Base#render method. It accepts a hash of options and passes it down to the controller's regular #render method.

    This is the source code from the Wicked library:

    def render_wizard(resource = nil, options = {})
      ...
    
      if @skip_to
        ...
      else
        render_step wizard_value(step), options
      end
    end
    
    def render_step(the_step, options = {})
      if the_step.nil? || the_step.to_s == Wicked::FINISH_STEP
        ...
      else
        render the_step, options #<-- Here
      end
    end
    

    Your code:

    respond_to do |format|
      format.js { render :js => ( render_wizard @r ) }  
    end
    

    is basically doing:

    respond_to do |format|
      format.js { render :js => ( render @r ) }  
    end
    

    which is in fact calling the render method twice.

    As it is searching for a .html template rather than a .js.erb one, try adding a formats: 'js' option to the render_wizard method. It should prepend ['js'] to the :formats=>[:html] we see in the Missing template error message.

    respond_to do |format|
      format.js { render_wizard(@r, formats: 'js') }
    end
    

    also, make sure the template's filename follows the rails convention and start with a _. (ie: _second_step.js.erb)


    About the double render error, you are correct. You must return from the controller #show or #update method and prevent further code from calling the #render method a second time. You seem to have fixed that problem already.

    EDIT#1

    It seems like you may be able to call this method directly in your controller.. :

    def render_step(the_step, options = {})
      # Wicked::FINISH_STEP = "wicked_finish"
      if the_step.nil? || the_step.to_s == Wicked::FINISH_STEP
        redirect_to_finish_wizard options
      else
        render the_step, options
      end
    end
    

    I believe the_step will be the partial's name. I think you should be able to call the #render_step method from your controller.

    You may be able to do:

    def show
      respond_to do |f|
        f.js do
          case step
          when :first_step
              @r  = R.new
              render_step(step) and return
          when :second_step
            ...
          end
        end
      end
    end