323 lines
10 KiB
Plaintext
323 lines
10 KiB
Plaintext
|
= Radius Quick Start
|
||
|
|
||
|
|
||
|
== Defining Tags
|
||
|
|
||
|
Before you can parse a template with Radius you need to create a Context object which defines
|
||
|
the tags that will be used in the template. This is actually quite simple:
|
||
|
|
||
|
require 'radius'
|
||
|
|
||
|
context = Radius::Context.new
|
||
|
context.define_tag "hello" do |tag|
|
||
|
"Hello #{tag.attr['name'] || 'World'}!"
|
||
|
end
|
||
|
|
||
|
Once you have defined a context you can easily create a Parser:
|
||
|
|
||
|
parser = Radius::Parser.new(context)
|
||
|
puts parser.parse('<p><radius:hello /></p>')
|
||
|
puts parser.parse('<p><radius:hello name="John" /></p>')
|
||
|
|
||
|
This code will output:
|
||
|
|
||
|
<p>Hello World!</p>
|
||
|
<p>Hello John!</p>
|
||
|
|
||
|
Note how you can pass attributes from the template to the context using the attributes hash.
|
||
|
Above, the first tag that was parsed didn't have a name attribute so the code in the +hello+
|
||
|
tag definition uses "World" instead. The second time the tag is parsed the name attribute is
|
||
|
set to "John" which is used to create the string "Hello John!". Tags that do not follow this
|
||
|
rule will be treated as if they were undefined (like normal methods).
|
||
|
|
||
|
|
||
|
== Container Tags
|
||
|
|
||
|
Radius also allows you to define "container" tags. That is, tags that contain content and
|
||
|
that may optionally manipulate it in some way. For example, if you have RedCloth installed
|
||
|
you could define another tag to parse and create Textile output:
|
||
|
|
||
|
require 'redcloth'
|
||
|
|
||
|
context.define_tag "textile" do |tag|
|
||
|
contents = tag.expand
|
||
|
RedCloth.new(contents).to_html
|
||
|
end
|
||
|
|
||
|
(The code <tt>tag.expand</tt> above returns the contents of the template between the start and end
|
||
|
tags.)
|
||
|
|
||
|
With the code above your parser can easily handle Textile:
|
||
|
|
||
|
parser.parse('<radius:textile>h1. Hello **World**!</radius:textile>')
|
||
|
|
||
|
This code will output:
|
||
|
|
||
|
<h1>Hello <b>World</b>!</h1>
|
||
|
|
||
|
|
||
|
== Nested Tags
|
||
|
|
||
|
But wait!--it gets better. Because container tags can manipulate the content they contain
|
||
|
you can use them to iterate over collections:
|
||
|
|
||
|
context = Radius::Context.new
|
||
|
|
||
|
context.define_tag "stooge" do |tag|
|
||
|
content = ''
|
||
|
["Larry", "Moe", "Curly"].each do |name|
|
||
|
tag.locals.name = name
|
||
|
content << tag.expand
|
||
|
end
|
||
|
content
|
||
|
end
|
||
|
|
||
|
context.define_tag "stooge:name" do |tag|
|
||
|
tag.locals.name
|
||
|
end
|
||
|
|
||
|
parser = Radius::Parser.new(context)
|
||
|
|
||
|
template = <<-TEMPLATE
|
||
|
<ul>
|
||
|
<radius:stooge>
|
||
|
<li><radius:name /></li>
|
||
|
</radius:stooge>
|
||
|
</ul>
|
||
|
TEMPLATE
|
||
|
|
||
|
puts parser.parse(template)
|
||
|
|
||
|
This code will output:
|
||
|
|
||
|
<ul>
|
||
|
|
||
|
<li>Larry</li>
|
||
|
|
||
|
<li>Moe</li>
|
||
|
|
||
|
<li>Curly</li>
|
||
|
|
||
|
</ul>
|
||
|
|
||
|
Note how the definition for the +name+ tag is defined. Because "name" is prefixed
|
||
|
with "stooge:" the +name+ tag cannot appear outside the +stooge+ tag. Had it been defined
|
||
|
simply as "name" it would be valid anywhere, even outside the +stooge+ tag (which was
|
||
|
not what we wanted). Using the colon operator you can define tags with any amount of
|
||
|
nesting.
|
||
|
|
||
|
|
||
|
== Exposing Objects to Templates
|
||
|
|
||
|
During normal operation, you will often want to expose certain objects to your templates.
|
||
|
Writing the tags to do this all by hand would be cumbersome of Radius did not provide
|
||
|
several mechanisms to make this easier. The first is a way of exposing objects as tags
|
||
|
on the context object. To expose an object simply call the +define_tag+
|
||
|
method with the +for+ option:
|
||
|
|
||
|
context.define_tag "count", :for => 1
|
||
|
|
||
|
This would expose the object <tt>1</tt> to the template as the +count+ tag. It's basically the
|
||
|
equivalent of writing:
|
||
|
|
||
|
context.define_tag("count") { 1 }
|
||
|
|
||
|
So far this doesn't save you a whole lot of typing, but suppose you want to expose certain
|
||
|
methods that are on that object? You could do this:
|
||
|
|
||
|
context.define_tag "user", :for => user, :expose => [ :name, :age, :email ]
|
||
|
|
||
|
This will add a total of four tags to the context. One for the <tt>user</tt> variable, and
|
||
|
one for each of the three methods listed in the +expose+ clause. You could now get the user's
|
||
|
name inside your template like this:
|
||
|
|
||
|
<radius:user><radius:name /></radius:user>
|
||
|
|
||
|
If "John" was the value stored in <tt>user.name</tt> the template would render as "John".
|
||
|
|
||
|
|
||
|
== Tag Shorthand
|
||
|
|
||
|
In the example above we made reference to <tt>user.name</tt> in our template by using the
|
||
|
following code:
|
||
|
|
||
|
<radius:user><radius:name /></radius:user>
|
||
|
|
||
|
There is a much easer way to refer to the <tt>user.name</tt> variable. Use the colon operator
|
||
|
to "scope" the reference to <tt>name</tt>:
|
||
|
|
||
|
<radius:user:name />
|
||
|
|
||
|
Radius allows you to use this shortcut for all tags.
|
||
|
|
||
|
|
||
|
== Changing the Tag Prefix
|
||
|
|
||
|
By default, all Radius tags must begin with "radius". You can change this by altering the
|
||
|
tag_prefix attribute on a Parser. For example:
|
||
|
|
||
|
parser = Radius::Parser.new(context, :tag_prefix => 'r')
|
||
|
|
||
|
Now, when parsing templates with +parser+, Radius will require that every tag begin with "r"
|
||
|
instead of "radius".
|
||
|
|
||
|
|
||
|
== Custom Behavior for Undefined Tags
|
||
|
|
||
|
Context#tag_missing behaves much like Object#method_missing only it allows you to define
|
||
|
specific behavior for when a tag is not defined on a Context. For example:
|
||
|
|
||
|
class LazyContext < Radius::Context
|
||
|
def tag_missing(tag, attr, &block)
|
||
|
"<strong>ERROR: Undefined tag `#{tag}' with attributes #{attr.inspect}</strong>"
|
||
|
end
|
||
|
end
|
||
|
|
||
|
parser = Radius::Parser.new(LazyContext.new, :tag_prefix => 'lazy')
|
||
|
puts parser.parse('<lazy:weird value="true" />')
|
||
|
|
||
|
This will output:
|
||
|
|
||
|
<strong>ERROR: Undefined tag `weird' with attributes {"value"=>"true"}</strong>
|
||
|
|
||
|
Normally, when the Radius Parser encounters an undefined tag for a Context it raises an
|
||
|
UndefinedTagError, but since we have defined #tag_missing on LazyContext the Parser now
|
||
|
outputs a nicely formated error message when we parse a string that does not contain a
|
||
|
valid tag.
|
||
|
|
||
|
|
||
|
== Tag Bindings
|
||
|
|
||
|
Radius passes a TagBinding into the block of the Context#define_tag method. The tag
|
||
|
binding is useful for a number of tasks. A tag binding has an #expand instance method
|
||
|
which processes a tag's contents and returns the result. It also has a #attr method
|
||
|
which returns a hash of the attributes that were passed into the tag. TagBinding also
|
||
|
contains the TagBinding#single? and TagBinding#double? methods which return true or false
|
||
|
based on wether the tag is a container tag or not. More about the methods which are
|
||
|
available on tag bindings can be found on the Radius::TagBinding documentation page.
|
||
|
|
||
|
|
||
|
== Tag Binding Locals, Globals, and Context Sensitive Tags
|
||
|
|
||
|
A TagBinding also contains two OpenStruct-like objects which are useful when developing
|
||
|
tags. TagBinding#globals is useful for storing variables which you would like to be
|
||
|
accessible to all tags:
|
||
|
|
||
|
context.define_tag "inc" do |tag|
|
||
|
tag.globals.count ||= 0
|
||
|
tag.globals.count += 1
|
||
|
""
|
||
|
end
|
||
|
|
||
|
context.define_tag "count" do |tag|
|
||
|
tag.globals.count || 0
|
||
|
end
|
||
|
|
||
|
TagBinding#locals mirrors the variables that are in TagBinding#globals, but allows child
|
||
|
tags to redefine variables. This is valuable when defining context sensitive tags:
|
||
|
|
||
|
class Person
|
||
|
attr_accessor :name, :friend
|
||
|
def initialize(name)
|
||
|
@name = name
|
||
|
end
|
||
|
end
|
||
|
|
||
|
jack = Person.new('Jack')
|
||
|
jill = Person.new('Jill')
|
||
|
jack.friend = jill
|
||
|
jill.friend = jack
|
||
|
|
||
|
context = Radius::Context.new do |c|
|
||
|
c.define_tag "jack" do |tag|
|
||
|
tag.locals.person = jack
|
||
|
tag.expand
|
||
|
end
|
||
|
c.define_tag "jill" do |tag|
|
||
|
tag.locals.person = jill
|
||
|
tag.expand
|
||
|
end
|
||
|
c.define_tag "name" do |tag|
|
||
|
tag.locals.person.name rescue tag.missing!
|
||
|
end
|
||
|
c.define_tag "friend" do |tag|
|
||
|
tag.locals.person = tag.locals.person.friend rescue tag.missing!
|
||
|
tag.expand
|
||
|
end
|
||
|
end
|
||
|
|
||
|
parser = Radius::Parser.new(context, :tag_prefix => 'r')
|
||
|
|
||
|
parser.parse('<r:jack:name />') #=> "Jack"
|
||
|
parser.parse('<r:jill:name />') #=> "Jill"
|
||
|
parser.parse('<r:jill:friend:name />') #=> "Jack"
|
||
|
parser.parse('<r:jack:friend:friend:name />') #=> "Jack"
|
||
|
parser.parse('<r:jill><r:friend:name /> and <r:name /></r:jill>') #=> "Jack and Jill"
|
||
|
parser.parse('<r:name />') # raises a Radius::UndefinedTagError exception
|
||
|
|
||
|
Notice how TagBinding#locals enables intelligent nesting. "<r:jill:name />" evaluates to
|
||
|
"Jill", but "<r:jill:friend:name />" evaluates to "Jack". Locals lose scope as soon as
|
||
|
the tag they were defined in closes. Globals on the other hand, never lose scope.
|
||
|
|
||
|
The final line in the example above demonstrates that calling "<r:name />" raises a
|
||
|
TagMissing error. This is because of the way the name tag was defined:
|
||
|
|
||
|
tag.locals.person.name rescue tag.missing!
|
||
|
|
||
|
If person is not defined on locals it will return nil. Calling #name on nil would normally
|
||
|
raise a NoMethodError exception, but because of the 'rescue' clause the TagBinding#missing!
|
||
|
method is called which fires off Context#tag_missing. By default Context#tag_missing raises
|
||
|
a UndefinedTagError exception. The 'rescue tag.missing!' idiom is extremly useful for adding
|
||
|
simple error checking to context sensitive tags.
|
||
|
|
||
|
|
||
|
== Tag Specificity
|
||
|
|
||
|
When Radius is presented with two tags that have the same name, but different nesting
|
||
|
Radius uses an algorithm similar to the way winning rules are calculated in Cascading Style
|
||
|
Sheets (CSS) to determine which definition should be used. Each time a tag is encountered
|
||
|
in a template potential tags are assigned specificity values and the tag with the highest
|
||
|
specificity wins.
|
||
|
|
||
|
For example, given the following tag definitions:
|
||
|
|
||
|
nesting
|
||
|
extra:nesting
|
||
|
parent:child:nesting
|
||
|
|
||
|
And template:
|
||
|
|
||
|
<r:parent:extra:child:nesting />
|
||
|
|
||
|
Radius will calculate specificity values like this:
|
||
|
|
||
|
nesting => 1.0.0.0
|
||
|
extra:nesting => 1.0.1.0
|
||
|
parent:child:nesting => 1.1.0.1
|
||
|
|
||
|
Meaning that parent:child:nesting will win. If a template contained:
|
||
|
|
||
|
<r:parent:child:extra:nesting />
|
||
|
|
||
|
The following specificity values would be assigned to each of the tag definitions:
|
||
|
|
||
|
nesting => 1.0.0.0
|
||
|
extra:nesting => 1.1.0.0
|
||
|
parent:child:nesting => 1.0.1.1
|
||
|
|
||
|
Meaning that extra:nesting would win because it is more "specific".
|
||
|
|
||
|
Values are assigned by assigning points to each of the tags from right to left.
|
||
|
Given a tag found in a template with nesting four levels deep, the maximum
|
||
|
specificity a tag could be assigned would be:
|
||
|
|
||
|
1.1.1.1
|
||
|
|
||
|
One point for each of the levels.
|
||
|
|
||
|
In practice, you don't need to understand this topic to be effective with Radius.
|
||
|
For the most part you will find that Radius resolves tags precisely the way that
|
||
|
you would expect. If you find this section confusing forget about it and refer
|
||
|
back to it if you find that tags are resolving differently from the way that you
|
||
|
expected.
|