3. DRYML
With the hobo plugin installed, Dryml will be used for any view templates with a .dryml suffix.
Defining tags
def:
Definition
<def tag="my_tag">This is my tag</def>
Use
<my_tag/>
Output
This is my tag
Tags with attributes:
Definition
<def tag="my_name" attrs="first_name, last_name">
<p>My name is <%= first_name %> <%= last_name %></p>
</def>
Use
<my_name first_name="Tom" last_name="Locke"/>
Output
<p>My name is Tom Locke</p>
The attributes become local variables inside the tag.
Passing arbitrary values
Definition
<def tag="my_age" attrs="age">My age is <%= age %></def>
Use
<my_age age="#3 * 4"/> => My age is 12
The # at the start of the attribute value signifies an expression as opposed to a string.
Tag Libraries
Use <taglib> to load tags from another Dryml file
Note that app/views/hobolib/application.dryml is implicitly imported into every Dryml template.
Load a taglib from the same directory as the view
Use
<taglib src="tags"/>
Load a taglib from another directory within app/views
Use
<taglib src="shared/date_tags"/>
Load a taglib from a Ruby module
(see Tag Modules below for more)
Use
<taglib module="MyModule::MyNestedModule" />
Tags from Ruby
Tags are just methods, they can be called from ERB scriptlets and from helpers.
Simple tag call
Ruby
my_tag => "This is my tag"
Tag call with parameters
Ruby
my_name :first_name => "Tom", :last_name => "Locke"
(tags only have keyword arguments, never positional arguments)
Tags with content
<tagbody>
Definition
<def tag="box" attrs="title">
<div class="box">
<h2><%= title %></h2>
<tagbody/>
</div>
</def>
Use
<box title="My Box"><p>I'm in a box</p></box>
Output
<div class="box">
<h2>My Box</h2>
<p>I'm in a box</p>
</div>
<tagbody> can occur any number of times.
Tricks with attributes
Accessing undeclared attributes:
Definition
<def tag="my_tag">
<p onclick="<%= options[:clicker] %>">This is my tag</p>
</def>
Use
<my_tag clicker="alert('!')"/>
Output
<p onclick="alert('!')">This is my tag</p>
xattrs – setting multiple attributes at once
Definition
<def tag="my_tag">
<p xattrs="# { :class => 'small', :style => 'color:red;' }">
This is my tag
</p>
</def>
Use
<my_tag/>
Output
<p class='small' style="color:red;">
This is my tag
</p>
Forwarding tag options
It’s often useful to forward all the attributes to another tag:
Definition
<def tag="my_tag">
<p xattrs="#options">This is my tag</p>
</def>
Use
<my_tag class="green" onclick="foo()"/>
Output
<p class="green" onclick="foo()">This is my tag</p>
Because xattrs="#options" is so common, there is a shorthand: xattrs=""
Parameter Tags
<: … > syntax
Definition
<def tag="my_name" attrs="first_name, last_name">
<p>My name is <%= first_name %> <%= last_name %></p>
</def>
Use
<my_name first_name="Tom"><:last_name>Locke</:last_name></my_name>
Output
<p>My name is Tom Locke</p>
Just an alternate syntax for passing parameters for tags. These are useful when there is a need to pass a large block of content. In fact, when there is a need to pass more than one block of content - the tag body can be used if only one such block is needed.
<page> example
Definition
<def tag="page" attrs="sidebar, main">
<html>
<head>...</head>
<body>
<div class="side"><%= sidebar %></div>
<div class="main"><%= main %></div>
</body>
</html>
</def>
Use
<page>
<:sidebar> ... side content ... </:sidebar>
<:main> ... main content ... </:main>
</page>
Implicit Context
Most pages display some object, and sub-sections of the page drill down into sub-objects. Implicit context makes this kind of page very succinct.
The initial context is set in the controller:
def show
@this = BlogPost.find :first
end
Accessing the context
The context is available via the method this
<h1><%= this.title %></h1>
Output
<h1>My First Post -- Welcome to my blog</h1>
Changing the context
To change to an attribute named x of the current context, use attr="x". To change to an unrelated object y, use obj="#y" (note the #). Every tag accepts these attributes.
attr example: (the core tag <show> displays the current context)
Use
<h1><show attr="title"/></h1>
You can think of this as this = this.title (although you can’t actually do that–there is no this= method)
obj example:
Use
<div class='flash'><show obj="#flash[:notice]"/></div>
Note – <show> could be implemented:
Definition
<def tag="show"><%= this %></show>
(Although the built in <show> from the core library has a few extra features)
Tags can change the context
The core tag <repeat> repeat changes the context as it iterates over a collection:
Use
<!-- context is a BlogPost, which has_many :comments -->
<repeat attr="comments">
<div class="comment">
<div class="date">Posted at <show attr="created_at"/></div
<div class="body"/><show attr="body"/></div>
</div>
<repeat/>
To achieve this, use attr or obj on <tagbody/>
Definition
<def tag="repeat">
<% this.each do |x| %>
<tagbody obj="#x"/>
<% end %>
</def>
(again, the core tag <repeat> is smarter than this)
Aliasing tags
Add ”!” to the output of every <show>
Definition
<def tag="boring_show" alias_of="show"/>
<def tag="show"><boring_show xattrs=""/>!</show>
Shorthand for same:
Definition
<def tag="show" alias_current="boring_show"><boring_show xattrs=""/>!</def>
Inner Tags
A convenient mechanism to allow tweaking of the tag inside the tag. Inner tags are created with the content_option and replace_option attributes.
content_option example:
Definition
<def tag="categories">
<div class="categories">
<h2 content_option="heading">Categories</h2>
<ul>
<repeat>
<li><show attr="name"/></li>
</repeat>
</ul>
</div>
</def>
Changing the content:
(using <categories> definition–see above)
Use
<categories attr="categories" heading="Post Categories"/>
Output
<div class="categories">
<h2>Post Categories</h2>
<ul>
<li>General</li>
<li>Documentation</li>
</ul>
</div>
Or use a parameter tag:
(using <categories> definition–see above)
Use
<categories attr="categories">
<:heading>post<b>CATEGORIES</b></:heading>
</categories>
Output
<div class="categories">
<h2>post<b>CATEGORIES</b></h2>
<ul>
<li>General</li>
<li>Documentation</li>
</ul>
</div>
Adding attributes:
(using <categories> definition–see above)
Use
<categories attr="categories"
heading="Post Categories" heading.class="small" />
Output
<div class="categories">
<h2 class="small">Post Categories</h2>
<ul>
<li>General</li>
<li>Documentation</li>
</ul>
</div>
With a parameter tag
(using <categories> definition–see above)
Use
<categories attr="categories">
<:heading class="small">post<b>CATEGORIES</b></:heading>
</categories>
Output
<div class="categories">
<h2 class="small">post<b>CATEGORIES</b></h2>
<ul>
<li>General</li>
<li>Documentation</li>
</ul>
</div>
replace_option example
With replace_option the entire inner tag is replaced:
Definition
<def tag="categories">
<div class="categories">
<h2 replace_option="heading">Categories</h2>
<ul>
<repeat>
<li><show attr="categories"/></li>
</repeat>
</ul>
</div>
</def>
Use
<categories attr="categories">
<:heading><h3>Categories</h3></:heading>
</categories>
Output
<div class="categories">
<h3>Categories</h3>
<ul>
<li>General</li>
<li>Documentation</li>
</ul>
</div>
Nesting inner tags
Definition
<def tag="post">
<div class="post">
<h1><show attr="title"/></h1>
<div class="body"><show attr="body"/></div>
<categories attr="categories" replace_option="categories"/>
</div>
</def>
Use
<post categories.heading.class="small">
Output
<div class="post">
<h1>My First Post</h1>
<div class="body">Nobody reads your first post anyway do they?</div>
<h3 class="small">Categories</h3>
<ul>
<li>General</li>
<li>Documentation</li>
</ul>
</div>
Nested inner tag with attributes and content
(using <post> definition above, and <categories> definition with content_option)
Use
<post>
<:categories.heading class="small">Post Categories</:categories>
</post>
Output
<div class="post">
<h1>My First Post</h1>
<div class="body">Nobody reads your first post anyway do they?</div>
<h3 class="small">Post Categories</h3>
<ul>
<li>General</li>
<li>Documentation</li>
</ul>
</div>
Dynamic Inner Tags
The attributes content_option and replace_option can be given dynamic values ("# ... ").
Definition
<def tag="show_fields" attrs="fields">
<ul>
<% for field in fields %>
<li content_option="#field"><%= field.titleize %>: <show attr="#field"/></li>
<% end %>
<ul>
</def>
Use
<show_fields fields="name, rank, number" number.class="highlighted" />
Output
<ul>
<li>Name: Fred</li>
<li>Rank: Dogsbody</li>
<li class="highlighted">Number: 12345</li>
</ul>
Tag Modules
Helper Modules
The easiest way to define tags in Ruby is to put them in helper modules. All the techniques described in this section will work in helper modules, but you will not need the <taglib> declarations. Helper modules are automatically included using normal Rails rules.
Importing tags from a Ruby module
(not required for helper modules - they are imported automatically)
Use
<taglib module="MyTags"/>
<taglib module="Can::Be::Nested"/>
You would normally put these modules anywhere in the Rails load path, e.g.
lib/my_tags.rblib/can/be/nested.rb
Defining module tags
Tags can be defined in Ruby modules, without using DRYML. Any method that only takes keyword parameters can be used as a tag.
Ruby
module MyTags
def time_now(options)
Time.now.to_s(options[:format].to_sym || :short)
end
end
Use
<time_now format="long"/>
The tag-body from Ruby
If the tag is called with a body, the method will receive a block. Never yield to the block directly.
Ruby - INCORRECT
def uppercase
body = yield # WRONG
body.upcase
end
Instead use the new_context method:
Ruby - CORRECT
def uppercase
body = new_context { yield } # Good
body.upcase
end
Using def_tag
The def_tag macro provides some benefits over using a normal method def.
Ruby
def_tag :link_to_all, :join do
this.map {|x| link_to(tagbody.call(:obj => x),
object_url(x), options) }.join(join)
end
Use
<link_to_all attr="categories" join=", " class="category_link">
<show attr="name"/>
</link_to_all>
Output
<a href="/categories/3" class="category_link">News</a>,
<a href="/categories/9" class="category_link">General</a>
Advantages of def_tag:
* Automatically support obj and attr to change context * tagbody is available as a proc which can be called directly (no need for new_contenxt), and takes obj and attr paramters * Parameters can be listed (join in this example). They become available as methods, and are automatically stripped from options, making it convenient to pass options on as extra html attributes (e.g. setting the css class as in this example).
def_tag gotcha - parameters are not locals
In the above example, join looks like a local, but is actually a method. Be careful not to create a local called join.
Ruby (Wrong!)
def_tag :link_to_all, :join do
join ||= ", " # creates a local that hides the join method
this.map {|x| link_to(tagbody.call(:obj => x),
object_url(x), options) }.join(join)
end
(in this case you should do: join2 = join || ", ")
Note this gotcha does not apply to tags created in DRYML. In DRYML the attributes become proper local variables.
Tag Predicates
Defining a tag with a predicate
Definition
<def tag="nav" if="logged_in? && !current_user.super_user?"> ... </def>
<def tag="nav" if="logged_in? && current_user.super_user?"> ... </def>
<def tag="nav"> ... </def>
There are now three definitions of nav. Each time the tag is used, the correct definition will be used according to the predicate.
-
If no predicate is true, the definition without a predicate (if there is one) will be used
-
If more than one predicate is true, the behaviour is undefined. It is the programmers responsibility to ensure the predicates are mutually exclusive.
The definition without the predicate will always be used if no predicate is true.
Accessing attributes and options
Definition
<def tag="nav" if="logged_in && options[:show_private_]"> ... </def>
Use
<nav show_private="#true"/>
Note that unlike inside the definition, you can’t access attributes directly by name even if you list them in attrs="...". You have to use get them from the options hash.
Tag predicates with def_tag
Give the predicate as the second argument, after the name and before the attributes:
Ruby
def_tag :nav, (proc {logged_in?}) do
...
end
To access the options, give the proc an argument
def_tag :nav, (proc {|opts| logged_in? && opts[:show_private]}) do
...
end
Hobo::PredicateDispatch
Tag predicates are implemented using Hobo’s generic predicate dispatch mechanism for Ruby.
Ruby
def MyModule
include Hobo::PredicateDispatch
defp :foo (proc {|opts| current_user.guest? && opts[:x] > 10}) do |opts, block|
...
end
end
- The method is only called if the predicate evaluates to true.
- You can
defpmany methods with the same name but different conditions. - You can use
defpwithout a predicate (defp :foo do |options, block|). This creates a default which always takes precedence over other methods if none of the predicates are true. - Predicate methods can only take keyword arguments, and optionally, a block.
- Because the method is defined with a macro that takes a block, rather than a Ruby
def, you cannot usereturnin a predicate method.