Changesets can be listed by changeset number.
The Git repository is here.
- Revision:
- 373
- Log:
Initial import of Radiant 0.9.1, which is now packaged as a gem. This is an
import of the tagged 0.9.1 source checked out from GitHub, which isn't quite
the same as the gem distribution - but it doesn't seem to be available in an
archived form and the installed gem already has modifications, so this is
the closest I can get.
- Author:
- rool
- Date:
- Mon Mar 21 13:40:05 +0000 2011
- Size:
- 10476 Bytes
1 | = Radius Quick Start |
2 | |
3 | |
4 | == Defining Tags |
5 | |
6 | Before you can parse a template with Radius you need to create a Context object which defines |
7 | the tags that will be used in the template. This is actually quite simple: |
8 | |
9 | require 'radius' |
10 | |
11 | context = Radius::Context.new |
12 | context.define_tag "hello" do |tag| |
13 | "Hello #{tag.attr['name'] || 'World'}!" |
14 | end |
15 | |
16 | Once you have defined a context you can easily create a Parser: |
17 | |
18 | parser = Radius::Parser.new(context) |
19 | puts parser.parse('<p><radius:hello /></p>') |
20 | puts parser.parse('<p><radius:hello name="John" /></p>') |
21 | |
22 | This code will output: |
23 | |
24 | <p>Hello World!</p> |
25 | <p>Hello John!</p> |
26 | |
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+ |
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 | |
33 | |
34 | == Container Tags |
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 | |
42 | context.define_tag "textile" do |tag| |
43 | contents = tag.expand |
44 | RedCloth.new(contents).to_html |
45 | end |
46 | |
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: |
51 | |
52 | parser.parse('<radius:textile>h1. Hello **World**!</radius:textile>') |
53 | |
54 | This code will output: |
55 | |
56 | <h1>Hello <b>World</b>!</h1> |
57 | |
58 | |
59 | == Nested Tags |
60 | |
61 | But wait!--it gets better. Because container tags can manipulate the content they contain |
62 | you can use them to iterate over collections: |
63 | |
64 | context = Radius::Context.new |
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 |
71 | end |
72 | content |
73 | end |
74 | |
75 | context.define_tag "stooge:name" do |tag| |
76 | tag.locals.name |
77 | end |
78 | |
79 | parser = Radius::Parser.new(context) |
80 | |
81 | template = <<-TEMPLATE |
82 | <ul> |
83 | <radius:stooge> |
84 | <li><radius:name /></li> |
85 | </radius:stooge> |
86 | </ul> |
87 | TEMPLATE |
88 | |
89 | puts parser.parse(template) |
90 | |
91 | This code will output: |
92 | |
93 | <ul> |
94 | |
95 | <li>Larry</li> |
96 | |
97 | <li>Moe</li> |
98 | |
99 | <li>Curly</li> |
100 | |
101 | </ul> |
102 | |
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 |
107 | nesting. |
108 | |
109 | |
110 | == Exposing Objects to Templates |
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 |
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: |
117 | |
118 | context.define_tag "count", :for => 1 |
119 | |
120 | This would expose the object <tt>1</tt> to the template as the +count+ tag. It's basically the |
121 | equivalent of writing: |
122 | |
123 | context.define_tag("count") { 1 } |
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 | |
128 | context.define_tag "user", :for => user, :expose => [ :name, :age, :email ] |
129 | |
130 | This will add a total of four tags to the context. One for the <tt>user</tt> variable, and |
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 | |
136 | If "John" was the value stored in <tt>user.name</tt> the template would render as "John". |
137 | |
138 | |
139 | == Tag Shorthand |
140 | |
141 | In the example above we made reference to <tt>user.name</tt> in our template by using the |
142 | following code: |
143 | |
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>: |
148 | |
149 | <radius:user:name /> |
150 | |
151 | Radius allows you to use this shortcut for all tags. |
152 | |
153 | |
154 | == Changing the Tag Prefix |
155 | |
156 | By default, all Radius tags must begin with "radius". You can change this by altering the |
157 | tag_prefix attribute on a Parser. For example: |
158 | |
159 | parser = Radius::Parser.new(context, :tag_prefix => 'r') |
160 | |
161 | Now, when parsing templates with +parser+, Radius will require that every tag begin with "r" |
162 | instead of "radius". |
163 | |
164 | |
165 | == Custom Behavior for Undefined Tags |
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 | |
176 | parser = Radius::Parser.new(LazyContext.new, :tag_prefix => 'lazy') |
177 | puts parser.parse('<lazy:weird value="true" />') |
178 | |
179 | This will output: |
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 |
185 | outputs a nicely formated error message when we parse a string that does not contain a |
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 |
209 | "" |
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 |
225 | |
226 | jack = Person.new('Jack') |
227 | jill = Person.new('Jill') |
228 | jack.friend = jill |
229 | jill.friend = jack |
230 | |
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" |
254 | parser.parse('<r:jack:friend:friend:name />') #=> "Jack" |
255 | parser.parse('<r:jill><r:friend:name /> and <r:name /></r:jill>') #=> "Jack and Jill" |
256 | parser.parse('<r:name />') # raises a Radius::UndefinedTagError exception |
257 | |
258 | Notice how TagBinding#locals enables intelligent nesting. "<r:jill:name />" evaluates to |
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. |
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! |
266 | |
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 | |
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. |