Programistically choose a layout for Hanami::View

Thanks for asking this question, @krzykamil! This is definitely possible.

To answer the question, let’s start by looking at how Hanami views themselves can have their layout specified. This is typically done via config.layout = "standard_layout" in a view class body, but you can also change it at call time, too: my_view.call(layout: "different_layout").

Next, we can look at how views are called from actions. Views are rendered on the action’s response, like so:

response.render(view, **options)

Under the hood, rendering on a response will call the view object and pass those options (among others) as arguments:

def render(view, **input)
  view_input = {
    **view_options.call(request, self),
    **exposures,
    **input
  }

  self.body = view.call(**view_input).to_str
end

This already gives us one approach for changing the layout when an action is called: update our handle method to render the view and specify the layout as required:

def handle(request, response)
  # `view` is automatically available if we have a matching view class
  response.render(view, layout: "different_layout")
end

In the above approach, you can add whatever logic you need to change "different_layout" to the right value for your circumstances. I presume you want to inspect headers and change the layout if the request is made by HTMX, or similar.

This approach is fine, but it’s probably not the cleanest if you wanted to apply this behaviour to a large number of actions. For this, the code snippet above from hanami-controller points us to another approach, which is its use of the #view_options action method.

We could overload this method to set our view layout for our needs:

def view_options(request, response)
  # Layouts can be `nil` (for no layout) as well as strings
  options = {}
  options[:layout] = nil if request.headers["HX-Request"] == "true"

  {**super, **options}
end

This will automatically set the layout name without requiring you to do so explicitly inside your #handle methods. If you put this in a base action class, then it will work for all its subclasses!

Hopefully this gets you on the right path. Please give this a try and let me know how you go!

1 Like