Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 323 lines (219 sloc) 10.476 kb
1db6f9b @jlong updated documentation
authored
1 = Radius Quick Start
2
80384ce @jlong got tests passing and removed the enumerable and collection tag types
authored
3
1db6f9b @jlong updated documentation
authored
4 == Defining Tags
4c84c42 @jlong added RDoc
authored
5
6 Before you can parse a template with Radius you need to create a Context object which defines
80384ce @jlong got tests passing and removed the enumerable and collection tag types
authored
7 the tags that will be used in the template. This is actually quite simple:
4c84c42 @jlong added RDoc
authored
8
9 require 'radius'
10
162d2f3 @jlong failing test on long tag prefixes
authored
11 context = Radius::Context.new
a1558f4 @jlong readied for 0.5.0 release
authored
12 context.define_tag "hello" do |tag|
13 "Hello #{tag.attr['name'] || 'World'}!"
4c84c42 @jlong added RDoc
authored
14 end
15
3a74f19 @jlong
authored
16 Once you have defined a context you can easily create a Parser:
4c84c42 @jlong added RDoc
authored
17
80384ce @jlong got tests passing and removed the enumerable and collection tag types
authored
18 parser = Radius::Parser.new(context)
4c84c42 @jlong added RDoc
authored
19 puts parser.parse('<p><radius:hello /></p>')
20 puts parser.parse('<p><radius:hello name="John" /></p>')
21
1db6f9b @jlong updated documentation
authored
22 This code will output:
4c84c42 @jlong added RDoc
authored
23
24 <p>Hello World!</p>
25 <p>Hello John!</p>
26
3a74f19 @jlong
authored
27 Note how you can pass attributes from the template to the context using the attributes hash.
28 Above, the first tag that was parsed didn't have a name attribute so the code in the +hello+
80384ce @jlong got tests passing and removed the enumerable and collection tag types
authored
29 tag definition uses "World" instead. The second time the tag is parsed the name attribute is
30 set to "John" which is used to create the string "Hello John!". Tags that do not follow this
31 rule will be treated as if they were undefined (like normal methods).
32
1db6f9b @jlong updated documentation
authored
33
80384ce @jlong got tests passing and removed the enumerable and collection tag types
authored
34 == Container Tags
4c84c42 @jlong added RDoc
authored
35
36 Radius also allows you to define "container" tags. That is, tags that contain content and
37 that may optionally manipulate it in some way. For example, if you have RedCloth installed
38 you could define another tag to parse and create Textile output:
39
40 require 'redcloth'
41
a1558f4 @jlong readied for 0.5.0 release
authored
42 context.define_tag "textile" do |tag|
43 contents = tag.expand
44 RedCloth.new(contents).to_html
4c84c42 @jlong added RDoc
authored
45 end
46
3a74f19 @jlong
authored
47 (The code <tt>tag.expand</tt> above returns the contents of the template between the start and end
48 tags.)
49
50 With the code above your parser can easily handle Textile:
4c84c42 @jlong added RDoc
authored
51
52 parser.parse('<radius:textile>h1. Hello **World**!</radius:textile>')
53
1db6f9b @jlong updated documentation
authored
54 This code will output:
4c84c42 @jlong added RDoc
authored
55
a74789c @jlong corrected errors in quickstart
authored
56 <h1>Hello <b>World</b>!</h1>
4c84c42 @jlong added RDoc
authored
57
80384ce @jlong got tests passing and removed the enumerable and collection tag types
authored
58
a1558f4 @jlong readied for 0.5.0 release
authored
59 == Nested Tags
80384ce @jlong got tests passing and removed the enumerable and collection tag types
authored
60
1db6f9b @jlong updated documentation
authored
61 But wait!--it gets better. Because container tags can manipulate the content they contain
62 you can use them to iterate over collections:
4c84c42 @jlong added RDoc
authored
63
a74789c @jlong corrected errors in quickstart
authored
64 context = Radius::Context.new
a1558f4 @jlong readied for 0.5.0 release
authored
65
66 context.define_tag "stooge" do |tag|
67 content = ''
68 ["Larry", "Moe", "Curly"].each do |name|
69 tag.locals.name = name
70 content << tag.expand
4c84c42 @jlong added RDoc
authored
71 end
a1558f4 @jlong readied for 0.5.0 release
authored
72 content
73 end
74
a74789c @jlong corrected errors in quickstart
authored
75 context.define_tag "stooge:name" do |tag|
a1558f4 @jlong readied for 0.5.0 release
authored
76 tag.locals.name
4c84c42 @jlong added RDoc
authored
77 end
78
3a74f19 @jlong
authored
79 parser = Radius::Parser.new(context)
4c84c42 @jlong added RDoc
authored
80
81 template = <<-TEMPLATE
82 <ul>
1db6f9b @jlong updated documentation
authored
83 <radius:stooge>
84 <li><radius:name /></li>
85 </radius:stooge>
4c84c42 @jlong added RDoc
authored
86 </ul>
87 TEMPLATE
88
89 puts parser.parse(template)
90
1db6f9b @jlong updated documentation
authored
91 This code will output:
4c84c42 @jlong added RDoc
authored
92
93 <ul>
94
95 <li>Larry</li>
96
97 <li>Moe</li>
98
99 <li>Curly</li>
100
101 </ul>
102
b1d66fb @jlong updated the documentation
authored
103 Note how the definition for the +name+ tag is defined. Because "name" is prefixed
104 with "stooge:" the +name+ tag cannot appear outside the +stooge+ tag. Had it been defined
105 simply as "name" it would be valid anywhere, even outside the +stooge+ tag (which was
106 not what we wanted). Using the colon operator you can define tags with any amount of
a1558f4 @jlong readied for 0.5.0 release
authored
107 nesting.
b1d66fb @jlong updated the documentation
authored
108
109
a1558f4 @jlong readied for 0.5.0 release
authored
110 == Exposing Objects to Templates
b1d66fb @jlong updated the documentation
authored
111
112 During normal operation, you will often want to expose certain objects to your templates.
113 Writing the tags to do this all by hand would be cumbersome of Radius did not provide
3a74f19 @jlong
authored
114 several mechanisms to make this easier. The first is a way of exposing objects as tags
115 on the context object. To expose an object simply call the +define_tag+
116 method with the +for+ option:
b1d66fb @jlong updated the documentation
authored
117
a1558f4 @jlong readied for 0.5.0 release
authored
118 context.define_tag "count", :for => 1
b1d66fb @jlong updated the documentation
authored
119
3a74f19 @jlong
authored
120 This would expose the object <tt>1</tt> to the template as the +count+ tag. It's basically the
121 equivalent of writing:
b1d66fb @jlong updated the documentation
authored
122
a1558f4 @jlong readied for 0.5.0 release
authored
123 context.define_tag("count") { 1 }
b1d66fb @jlong updated the documentation
authored
124
125 So far this doesn't save you a whole lot of typing, but suppose you want to expose certain
126 methods that are on that object? You could do this:
127
a1558f4 @jlong readied for 0.5.0 release
authored
128 context.define_tag "user", :for => user, :expose => [ :name, :age, :email ]
a74789c @jlong corrected errors in quickstart
authored
129
3a74f19 @jlong
authored
130 This will add a total of four tags to the context. One for the <tt>user</tt> variable, and
b1d66fb @jlong updated the documentation
authored
131 one for each of the three methods listed in the +expose+ clause. You could now get the user's
132 name inside your template like this:
133
134 <radius:user><radius:name /></radius:user>
135
a1558f4 @jlong readied for 0.5.0 release
authored
136 If "John" was the value stored in <tt>user.name</tt> the template would render as "John".
80384ce @jlong got tests passing and removed the enumerable and collection tag types
authored
137
138
a1558f4 @jlong readied for 0.5.0 release
authored
139 == Tag Shorthand
b1d66fb @jlong updated the documentation
authored
140
a1558f4 @jlong readied for 0.5.0 release
authored
141 In the example above we made reference to <tt>user.name</tt> in our template by using the
142 following code:
b1d66fb @jlong updated the documentation
authored
143
3a74f19 @jlong
authored
144 <radius:user><radius:name /></radius:user>
145
146 There is a much easer way to refer to the <tt>user.name</tt> variable. Use the colon operator
147 to "scope" the reference to <tt>name</tt>:
b1d66fb @jlong updated the documentation
authored
148
3a74f19 @jlong
authored
149 <radius:user:name />
b1d66fb @jlong updated the documentation
authored
150
a1558f4 @jlong readied for 0.5.0 release
authored
151 Radius allows you to use this shortcut for all tags.
b1d66fb @jlong updated the documentation
authored
152
153
a1558f4 @jlong readied for 0.5.0 release
authored
154 == Changing the Tag Prefix
1db6f9b @jlong updated documentation
authored
155
156 By default, all Radius tags must begin with "radius". You can change this by altering the
3a74f19 @jlong
authored
157 tag_prefix attribute on a Parser. For example:
1db6f9b @jlong updated documentation
authored
158
3a74f19 @jlong
authored
159 parser = Radius::Parser.new(context, :tag_prefix => 'r')
1db6f9b @jlong updated documentation
authored
160
80384ce @jlong got tests passing and removed the enumerable and collection tag types
authored
161 Now, when parsing templates with +parser+, Radius will require that every tag begin with "r"
162 instead of "radius".
163
1db6f9b @jlong updated documentation
authored
164
a1558f4 @jlong readied for 0.5.0 release
authored
165 == Custom Behavior for Undefined Tags
1db6f9b @jlong updated documentation
authored
166
167 Context#tag_missing behaves much like Object#method_missing only it allows you to define
168 specific behavior for when a tag is not defined on a Context. For example:
169
170 class LazyContext < Radius::Context
171 def tag_missing(tag, attr, &block)
172 "<strong>ERROR: Undefined tag `#{tag}' with attributes #{attr.inspect}</strong>"
173 end
174 end
175
3a74f19 @jlong
authored
176 parser = Radius::Parser.new(LazyContext.new, :tag_prefix => 'lazy')
1db6f9b @jlong updated documentation
authored
177 puts parser.parse('<lazy:weird value="true" />')
178
a1558f4 @jlong readied for 0.5.0 release
authored
179 This will output:
1db6f9b @jlong updated documentation
authored
180
181 <strong>ERROR: Undefined tag `weird' with attributes {"value"=>"true"}</strong>
182
183 Normally, when the Radius Parser encounters an undefined tag for a Context it raises an
184 UndefinedTagError, but since we have defined #tag_missing on LazyContext the Parser now
80384ce @jlong got tests passing and removed the enumerable and collection tag types
authored
185 outputs a nicely formated error message when we parse a string that does not contain a
a1558f4 @jlong readied for 0.5.0 release
authored
186 valid tag.
187
188
189 == Tag Bindings
190
191 Radius passes a TagBinding into the block of the Context#define_tag method. The tag
192 binding is useful for a number of tasks. A tag binding has an #expand instance method
193 which processes a tag's contents and returns the result. It also has a #attr method
194 which returns a hash of the attributes that were passed into the tag. TagBinding also
195 contains the TagBinding#single? and TagBinding#double? methods which return true or false
196 based on wether the tag is a container tag or not. More about the methods which are
197 available on tag bindings can be found on the Radius::TagBinding documentation page.
198
199
200 == Tag Binding Locals, Globals, and Context Sensitive Tags
201
202 A TagBinding also contains two OpenStruct-like objects which are useful when developing
203 tags. TagBinding#globals is useful for storing variables which you would like to be
204 accessible to all tags:
205
206 context.define_tag "inc" do |tag|
207 tag.globals.count ||= 0
208 tag.globals.count += 1
a74789c @jlong corrected errors in quickstart
authored
209 ""
a1558f4 @jlong readied for 0.5.0 release
authored
210 end
211
212 context.define_tag "count" do |tag|
213 tag.globals.count || 0
214 end
215
216 TagBinding#locals mirrors the variables that are in TagBinding#globals, but allows child
217 tags to redefine variables. This is valuable when defining context sensitive tags:
218
219 class Person
220 attr_accessor :name, :friend
221 def initialize(name)
222 @name = name
223 end
224 end
a74789c @jlong corrected errors in quickstart
authored
225
a1558f4 @jlong readied for 0.5.0 release
authored
226 jack = Person.new('Jack')
227 jill = Person.new('Jill')
228 jack.friend = jill
229 jill.friend = jack
a74789c @jlong corrected errors in quickstart
authored
230
a1558f4 @jlong readied for 0.5.0 release
authored
231 context = Radius::Context.new do |c|
232 c.define_tag "jack" do |tag|
233 tag.locals.person = jack
234 tag.expand
235 end
236 c.define_tag "jill" do |tag|
237 tag.locals.person = jill
238 tag.expand
239 end
240 c.define_tag "name" do |tag|
241 tag.locals.person.name rescue tag.missing!
242 end
243 c.define_tag "friend" do |tag|
244 tag.locals.person = tag.locals.person.friend rescue tag.missing!
245 tag.expand
246 end
247 end
248
249 parser = Radius::Parser.new(context, :tag_prefix => 'r')
250
251 parser.parse('<r:jack:name />') #=> "Jack"
252 parser.parse('<r:jill:name />') #=> "Jill"
253 parser.parse('<r:jill:friend:name />') #=> "Jack"
a74789c @jlong corrected errors in quickstart
authored
254 parser.parse('<r:jack:friend:friend:name />') #=> "Jack"
a1558f4 @jlong readied for 0.5.0 release
authored
255 parser.parse('<r:jill><r:friend:name /> and <r:name /></r:jill>') #=> "Jack and Jill"
a74789c @jlong corrected errors in quickstart
authored
256 parser.parse('<r:name />') # raises a Radius::UndefinedTagError exception
a1558f4 @jlong readied for 0.5.0 release
authored
257
258 Notice how TagBinding#locals enables intelligent nesting. "<r:jill:name />" evaluates to
8eee083 @jlong work on Quick start: fixed typo, updated last paragraph
authored
259 "Jill", but "<r:jill:friend:name />" evaluates to "Jack". Locals lose scope as soon as
260 the tag they were defined in closes. Globals on the other hand, never lose scope.
a1558f4 @jlong readied for 0.5.0 release
authored
261
262 The final line in the example above demonstrates that calling "<r:name />" raises a
263 TagMissing error. This is because of the way the name tag was defined:
264
265 tag.locals.person.name rescue tag.missing!
a74789c @jlong corrected errors in quickstart
authored
266
a1558f4 @jlong readied for 0.5.0 release
authored
267 If person is not defined on locals it will return nil. Calling #name on nil would normally
268 raise a NoMethodError exception, but because of the 'rescue' clause the TagBinding#missing!
269 method is called which fires off Context#tag_missing. By default Context#tag_missing raises
270 a UndefinedTagError exception. The 'rescue tag.missing!' idiom is extremly useful for adding
271 simple error checking to context sensitive tags.
272
273
274 == Tag Specificity
275
276 When Radius is presented with two tags that have the same name, but different nesting
277 Radius uses an algorithm similar to the way winning rules are calculated in Cascading Style
278 Sheets (CSS) to determine which definition should be used. Each time a tag is encountered
279 in a template potential tags are assigned specificity values and the tag with the highest
280 specificity wins.
281
282 For example, given the following tag definitions:
283
284 nesting
285 extra:nesting
286 parent:child:nesting
287
288 And template:
289
290 <r:parent:extra:child:nesting />
291
292 Radius will calculate specificity values like this:
293
294 nesting => 1.0.0.0
295 extra:nesting => 1.0.1.0
296 parent:child:nesting => 1.1.0.1
297
298 Meaning that parent:child:nesting will win. If a template contained:
299
300 <r:parent:child:extra:nesting />
301
302 The following specificity values would be assigned to each of the tag definitions:
303
304 nesting => 1.0.0.0
305 extra:nesting => 1.1.0.0
306 parent:child:nesting => 1.0.1.1
307
308 Meaning that extra:nesting would win because it is more "specific".
309
310 Values are assigned by assigning points to each of the tags from right to left.
311 Given a tag found in a template with nesting four levels deep, the maximum
312 specificity a tag could be assigned would be:
313
314 1.1.1.1
315
316 One point for each of the levels.
317
8eee083 @jlong work on Quick start: fixed typo, updated last paragraph
authored
318 In practice, you don't need to understand this topic to be effective with Radius.
319 For the most part you will find that Radius resolves tags precisely the way that
320 you would expect. If you find this section confusing forget about it and refer
321 back to it if you find that tags are resolving differently from the way that you
322 expected.
Something went wrong with that request. Please try again.