— Tagged with: , — Written by

More than once writing view code for a Rails application ends up with a messy template file. The separation of structure and style doesn't always work out as intended. Frameworks like Bootstrap force you to use nested structures and lots of class attributes. The view code ends up with a lot of duplication and is hard to read as a result. You'll get away with that for a while, constantly fearing the next redesign…

To tackle this, you probably start writing helper methods. They help to remove logic out of the template and hide overly complex structures behind a nice interface. Unfortunately some view components are more complex to implement and require more than just a single method. This is even more true when you need to configure the view components in some way or another.

Introducing ActionWidgets

In the following, I'll present you a different approach to this issue: ActionWidget. The gem by Konstantin Tennhard takes helper methods to the next level. The basic idea is simple, instead of writing a complex helper method, you'll write a class inheriting from ActionWidget::Base and put it into the app/widgets directory. The only method you have to implement is the render method. The ActionWidget gem, will provide you with a simple helper method to use your widget automatically. Let me show you a simple example.

A simple button widget

To illustrate the basic usage of ActionWidget, let's write a widget to represent a button. The button will have a title, a target, a type (default, primary, or danger) and a size (small, default, or large). For convenience, the gem provides a generator to get you started:

rails generate action_widget:widget button

The generator creates a file called app/widgets/button_widget.rb for you. This is where the implementation of the button widget lives. An example implementation might look something like this:

# app/widgets/button_widget.rb
class ButtonWidget < ActionWidget::Base

  property :title,
    :converts => :to_s,
    :required => true

  property :target,
    :converts => :to_s,
    :required => true

  property :type,
    :converts => :to_sym,
    :accepts  => [:default, :primary, :danger],
    :default  => :default

  property :size,
    :converts => :to_sym,
    :accepts  => [:small, :default, :large],
    :default  => :default

  def render
    content_tag(:a, title, :href => target, :class => css_classes)  
  end

protected

  def css_classes
    css_classes = ['btn']
    css_classes << "btn-#{size}" unless size == :default
    css_classes << "btn-#{type}" unless type == :default
    css_classes
  end

end

The ActionWidget gem depends on Smart Properties to provide the property macro method. This allows you to add simple configuration values to your widget. It'll also do conversion and input validation for you. With the implementation above, you'll be forced to always provide a title and a target attribute for every button. Otherwise the widget will complain with an exception.

Using the button widget in your view is as simple as a call to the (automatically generated) button_widget method:

button_widget :title => 'Sign Up',
  :target => signup_path,
  :type => :primary,
  :size => :large

Now that you have a basic understanding of the idea behind ActionWidgets, let's dive into a more complex example.

A more advanced example

Tabs are a pretty common user interface element and their implementation is pretty straight forward. Let's look at the implementation of tabs, on a site using Twitter Bootstrap:

<ul class="nav nav-tabs" id="myTab">
  <li><a href="#first">First</a></li>
  <li class="active"><a href="#second">Second</a></li>
  <li><a href="#third">Third</a></li>
</ul>

<div class="tab-content">
  <div class="tab-pane" id="first">...</div>
  <div class="tab-pane active" id="second">...</div>
  <div class="tab-pane" id="third">...</div>
</div>

To implement the tabs, you need a list with links for each tab and a collection of tab panes. For every href attribute of each link, there must be a corresponding tab pane with its id attribute set accordingly. Additionally, the active class has to match, or your users will be a bit confused when clicking through the tabs. Of course when adding the tabs to one of your pages, you'll have to remember all the details of the markup that's required. There has to be both the nav and nav-tabs class in the ul tag, and don't forget the wrapping div tag with class tab-content around all the tab-pane elements. That's a lot to remember, and a lot to mess up. If you don't screw it up now, then probably later when you try to change something quickly. To make your life a little easier, I'll show you how to use ActionWidgets to hide the markup behind this expressive interface.

= tabs_widget do |t|
  = t.tab 'First' do
    Content of the first tab

  = t.tab 'Second', :active => true do
    Content of the second tab

  = t.tab 'Third' do
    Content of the third tab

I'm using Haml in this example, but that's not a requirement. ActionWidgets work with all of your templates, as long as they support helper methods.

Building the TabsWidget

Let's start by generating the TabsWidget class. Again, it's as simple as creating a file called app/widgets/tabs_widget.rb or running the generator.

class TabsWidget < ActionWidget::Base

  def render(&block)
    navigation = content_tag(:ul, :class => ['nav', 'nav-tabs']) do
      capture(self, &block)
    end

    contents = content_tag(:div, :class => ['tab-content']) do
      tabs.each do |tab|
        concat(tab.render)
      end
    end

    navigation + contents
  end

  def tab(*args)
    # We'll implement this soon...
  end

private

  def tabs
    @tabs ||= []
  end

end

Every ActionWidget has to implement a render method. If the generated helper method gets passed a block, it will be handed to this method as well. Every ActionWidget has delegators to all the methods in your view. We use this to our advantage and use capture to get the contents of the block. We also pass self, which makes it possible to call the widget's tab method from within the block. The captured contents get wrapped in the necessary ul tag.

Next we create a div tag to wrap the tab contents. We iterate over the collection of tabs and render each one. By using concat we make sure, that the rendered tab-pane actually gets appended to the view.

With this code in place, we don't get an error when trying to view the page we're embedding the tabs widget in, but we don't see the tabs either.

Adding a nested TabWidget

The general idea behind ActionWidget is to use objects instead of a set of methods to generate markup code. In this spirit, let's add another ActionWidget to represent a single tab. It's pretty useless without the wrapping tabs widget, so let's add it as a nested class:

class TabsWidget < ActionWidget::Base

  class Tab < ActionWidget::Base

    property :name,
      :required => true,
      :converts => :to_s

    property :active,
      :accepts => [true, false],
      :default => false

    property :target,
      :converts => :to_s


    property :content,
      :required => true

    def render
      content_tag(:div, :id => target, :class => ['tab-pane'] + css_classes, &content)
    end

    def render_navigation
      content_tag(:li, :class => ['tab'] + css_classes) do
        link_to(name, '#' + target)
      end
    end

    def target
      super || name.parameterize
    end

  private

    def css_classes
      css_classes = []
      css_classes << 'active' if active
      css_classes
    end

  end

  # ...

end

As you can see, the class is quite simple. Apart from defining a set of properties, it has two render methods. One to render the tab pane, and another one to render the navigation link. They both make use of the target method so the link's href attribute matches the tab pane's id attribute. The private css_classes method makes sure, both elements get the active class when the tab is active.

Now everything we need to do is to tie the TabsWidget and the TabsWidget::Tab together, usin the TabsWidget#tab method.

def tab(name, options = {}, &block)
  options.merge!({
    :name => name,
    :content => block
  })

  tab = Tab.new(view, options)
  tabs << tab
  tab.render_navigation
end

This method creates a new instance of the TabsWidget::Tab widget, while passing both the view context and a options hash to it. Afterwards it adds the tab to the collection and renders the navigation link.

Now we have everything in place to render tab widgets with a few lines of nice and expressive code. Should you ever need to change the implementation of all tab widgets on your website, you now have a single place to do this.

Conclusion

ActionWidget allows you to separate the concept of a view component from the actual implementation. In contrast to the presenter pattern, the widgets don't necessarily need to wrap a model object. As they are implemented in classes, it's easy to extend them. Additionally the possibility to use private methods internally makes their code easier to maintain than standard helper methods.

Hopefully this short introduction made you curious about the ActionWidget approach and gives you some pointers to implement some (or all?) of your user interface components using this gem. Unfortunately there's no documentation for it yet, but as the gem's code is pretty simple and I covered the basic idea in this article, you should be able to get started easily.