---
title: Custom Tags in Liquid
teaser:
tags: news,web,widgetfinger
author: Chad Pytel
published_on: 2008-10-03
---

We recently rolled out a new feature in Widgetfinger that allows you to quickly
build navigational menus

    {% navigation %}
      {% link About %}
      {% link Services %}
      {% link Contact %}
    {% endnavigation %}

The above tags will create the following navigation <abbr title="HyperText
Markup Language">HTML</abbr>.

* [About](/about)
* [Services](/services)
* [Contact](/contact)

![''](http://images.thoughtbot.com/ui/2008-10-3-t1000_l.jpg)

As we've mentioned before, Widgetfinger uses
[Liquid](http://www.liquidmarkup.org), which provides safe templates that don't
affect the security of the server they are rendered on.

Liquid allows you to write custom tags, and that's exactly what the new
navigation tag in Widgetfinger is.

Writing tags in Liquid is fairly straightforward, once you get the hang of it.
Lets talk a look at what goes into the navigation tag.

Liquid provides for two different types of Tags, a [non-block
tag](http://liquid.rubyforge.org/classes/Liquid/Tag.html), and a [block
tag](http://liquid.rubyforge.org/classes/Liquid/Block.html).  Since our
navigation tag has a starting and ending tag, with other tags inside of it,
that's a block tag, so that's what we'll be implementing.

When the Liquid template is parsed, an instance of our Navigation block tag is
initialized

```ruby
class NavigationBlock < Liquid::Block
  include LiquidExtensions::Helpers
  attr_accessor :links

  def initialize(name, params, tokens)
    @links = []
    super
  end
end
```

In the initialize method, the `name` is the name of the tag, and `params` is the
extra stuff given to the tag.  The navigation tag doesn't have anything extra
given to it, but the `{% contactform :to email@example.com %}` tag in
Widgetfinger does (the `:to email@example.com` would be given in the params as a
string).  Finally, the `tokens` are all of the other tags that appear within
this block tag, including the closing `endnavigation` tag.

In the initialize method above for the navigation tag, we simply initialize the
links i-var and call super.  The `Liquid::Block` initialize method calls
`parse`, which parses each of the tokens, causing each of the tags within the
block to be parsed.  This means that any valid liquid tag can appear inside your
block.  If parse comes across any tag that it doesn't recognize, it calls an
`unknown_tag` method on your Block, allowing you to handle it as you see fit.
Here is the `unknown_tag` method for the Navigation block.

```ruby
def unknown_tag(name, params, tokens)
  if name == "link"
    handle_link_tag(params)
  else
    super
  end
end
```

What we're doing here is pretty straightforward.  The only custom tag that we
want to provide within the Navigation tag is the `link` tag.  So, when check to
see whether the tag is the link tag, otherwise, we call the `unknown_tag` method
in the base class.  If know handler is ever found for a tag, that'll cause a
`Liquid::SyntaxError` exception to occur.  The handle_link_tag method gets a
little more interesting, as it provides the meat of the additional parameters
you can pass a link tag.

```ruby
def handle_link_tag(params)
  args = split_params(params)
  element_id = args[0].downcase
  if args.length > 1
    match = (args[1].first == "/" ? args[1][1..-1] : element_id)
    @links << { :name => args[0], :match => match, :url => args[1],
      :id => element_id, :extra_class => args[2]
    }
  else
    @links << { :name => args[0], :match => element_id,
      :url => "/#{element_id}", :id => element_id
    }
  end
end

def split_params(params)
  params.split(",").map(&:strip)
end
```

In the code above, we're taking all of the parameters passed to link tag.  If
there is only one, then we're going to use several sensible defaults to build
the navigation element.  If there is more then one, then tag defaults are being
overridden.  For more information on the addition parameters of the link tag,
view the [Widgetfinger
documentation](http://help.widgetfinger.com/tags#navigation) on it.

Finally, once everything is parsed, and the template is going to be outputted,
the `render` method on the Block is called.  Here is what the render method
looks like for the Navigation tag.

```ruby
def render(context)
  render_erb(context,
             'editor/navigation.rhtml',
             :links => @links,
             :registers => context.registers)
end
```

The `render` method receives a
[Context](http://liquid.rubyforge.org/classes/Liquid/Context.html).  This is
provided by Liquid and the Context and its `registers` are essentially a hash
where you can store things that'll be passed around for the parsing of the
template.  That's an over simplification, but it should suffice for our purposes
here.

The `render_erb` method is provided by `LiquidExtensions::Helpers`, which you
may have noticed that we included above.  This is something we devised in order
to open it up so Widgetfinger tags would be able to render Erb, and have access
to the normal Rails view helpers.  Here's how it works.

```ruby
def render_erb(context, file_name, locals = {})
  context.registers[:controller].send(:render_to_string, :partial => file_name,
    :locals => locals)
end
```

After floundering around for a while trying to get Erb Rendering to work by
doing it manually, using Erb directly, and then having to deal with making the
Rails view helpers available in that Erb, we realized that we could just add the
Widgetfinger controller responsible for causing the Liquid templates to be
parsed to the Liquid context registers.  From that controller, we can simple
call `:render_to_string` on it.  This allows us to make regular Rails partial
that are responsible for the output of the tags, that have access to all of the
normal view helpers we're used to.

In the case of the Navigation tag partial, we've composed a hash of links to
draw, and the partial outputs it as we expect.

```erb
<% links.each do |link| -%>
  *   <%= match_class registers, link %><%= extra_class link %>">
<%= link_to link[:name], link[:url] %>
```

Finally, in order for Liquid to know about our new navigation tag, we have to
register it like this.

    Liquid::Template.register_tag 'navigation', NavigationBlock

## Lets talk about Testing

In Widgetfinger, the tags are placed in `lib/liquid_extensions.rb`, and we
provide unit tests for this code in `test/unit/liquid_extensions_test.rb`.

I won't cover the full extent of the tests here, but to provide an example of a
basic test case that ensures that the proper erb file is rendered.

```ruby
context "a NavigationBlock tag" do
  setup do
    @tag = LiquidExtensions::Classes::NavigationBlock.new 'navigation', '',
      ['{% endnavigation %}']
  end

  should "render in erb the navigation element when sent render" do
    context = stub(:registers => stub)
    @tag.expects(:render_erb).with(context,
                                  'editor/navigation.rhtml',
                                :links => [],
                                :registers => context.registers).returns ''
    @tag.render context
  end
end
```

In the test code above, we instantiate an instance of the navigation block tag,
with the proper tag name, no parameters, and the ending tag token.

The test then provides a stub context with a stub registers.  We then create an
expectation that when the render Erb method will be called with the expected
arguments: the proper partial, an empty links array (because there are no links
in the tag, as evidenced by the lack of links in the tokens, and the mock
registers.

We then call the `render` method on the tag, which causes the whole thing to go
into action.

Now that you know how to properly instantiate the tag, and stub out some
important pieces of it, providing additional unit tests for the other portions
of the tag, particularly those relating to handling the link tags inside of it
should be clear.
