This document discusses customizing content management systems (CMS) using extensions. It describes how Radiant CMS meets the ideal criteria of being simple, allowing standards markup and design freedom, having powerful built-in tools, and having clear, customizable code. Extensions can add new template tags, customize the admin interface, and inject views into regions without overwriting templates. Tag definitions have access to contextual data and can render other tags.
7. ugly code
http://ļ¬ickr.com/photos/ļ¬ickerbulb/187044366/
and opaque, poorly-architected and undocumented code that refuses to yield to
customization attempts.
54. tags expose
functionality
Most built-in and plugin functionality is exposed through template tags, so you don't have to
worry that some feature will produce bad markup;
55. write your own
markup
you can write the markup yourself.
56. #3 :
powerful tools
Third, Radiant has powerful tools
57. over 50 tags
about 50 built-in template tags to help you accomplish many content-generation tasks.
65. show me the code
Since this is a technical talk, I'm going to gloss over the details of using Radiant to create a
site and focus on how we can customize Radiant to our own needs.
81. description āMy first extension.ā
url āhttp://foo.comā
and some metadata that is displayed in the user-interface.
82. TIMTOWTDI
Depending on the scenario, there are a number of dierent ways we can customize Radiant to
accomplish our goals, all of which can be nicely packaged up in extensions.
83. I could easily ļ¬ll many hours talking about all the ways to tweak Radiant with extensions, so
I'm going to give you the 10,000-foot view of the primary techniques.
84. #1 :
new tags
The ļ¬rst and simplest way to add functionality is to deļ¬ne new Radius tags. As I said before,
Radius is the template language used to expose dynamic functionality to the designer and
content editor,
90. r:children:each order=ādescā
foo
/r:children:each
any nested contents, and a closing tag. A few notes on these tag names: Radius will execute
the deļ¬nition of each of those colon separated names in order and determine which
deļ¬nitions to use based on context.
91. r:children
r:each order=ādescā
foo
/r:each
/r:children
The colon-separated version is just a shortcut for nesting them, so this snippet is logically
equivalent to the last one we saw.
92. r:children
r:each order=ādescā
foo
/r:each
/r:children
Also, the last named tag in the colon-separated list gets the attributes passed to its
deļ¬nition.
98. %Q[link rel=āStylesheetā
type=ātext/cssā href=ā#{url}ā /]
and interpolate that into the proper place in the HTML `link` tag.
99. end
The return value of the tag-deļ¬nition block is what is rendered, so we're done.
100. def activate
Page.send :include, LazyTags
end
Last, we mix the module into the Page model in the 'activate' method of our extension, and
we have access to our new tag deļ¬nition.
102. TagBinding === tag
It's your access to the contextual parsing environment, including what page is being
rendered, the request and response objects, and any variables set by surrounding tags.
103. tag.globals
everywhere
Its primary properties are 'globals', which gives you access to the global environment,
including the current page being rendered;
104. tag.locals
cascading, contextual
ālocals' which is a cascading symbol-table of sorts, contextual to the current parsing
environment;
105. tag.attr
Hash
'attr', a hash of attributes placed on the current tag;
106. tag.double?
r:footext/r:foo
'double?', which is true when the tag contains other tags or text,
107. tag.single?
r:bar /
and 'single?' which is true when the tag is self-closing.
108. be kind,
Iām sensitive
Because many tags are sensitive to their surrounding environment, you can easily do things
like iteration, changing some locals property on each step of the iteration, and causing
contained tags to use that property.
109. children.map do |child|
tag.locals.page = child
tag.expand
end.join
This is essentially what the `r:children:each` tag does - iterate on each of the children,
changing `tag.locals.page` on each iteration, then joining the output. To trigger rendering of
a tag's contents, we just call `tag.expand`.
110. tag.render(ātitleā)
You can also directly invoke the rendering of another tag using `tag.render`. The result will
be returned to you as a string.
111. tag.block
One neat trick is capture the contents of a container tag and reuse it in multiple places with
`tag.block`.
112. r:navigation urls=āFoo:/foo |
Bar: /barā
...
/r:navigation
The built-in `r:navigation` tag uses this to render navigation links depending on whether the
current page matches the URL being output.
113. tag.locals.hash = {}
tag.expand
# ...
First, it sets up a hash that can be used inside nested tags, then renders its contents.
114. tag ānavigation:hereā do |tag|
tag.locals.hash[:here] = tag.block
end
The contained quot;navigation:herequot; tag assigns its block into the created hash.
115. # for each link...
if url == page.url
tag.locals.hash[:here].call
end
Then the quot;navigationquot; tag calls the quot;herequot; block when the passed URL exactly matches the
current page's URL. It uses the same technique to render preļ¬x-matching and non-matching
links by capturing blocks.
116. #2:
Admin UI
So now that we can customize page output with tag deļ¬nitions, let's look at the next big area
of customization -- the admin UI.
117. class Widget
ActiveRecord::Base
Let's say you've built a model for widgets
126. a dash of spice
Often what you want to do is just add a little piece to one view or another.
127. widget list
Say you need to be able to see your widget list while editing a page.
128. fine-grained
regions
Luckily, you can inject a view partial into any number of deļ¬ned quot;regionsquot; in the view
template you want to modify, without overwriting the whole thing. This is enabled again
through the admin object.
129. app/views/admin/pages/
_widget_list.erb
To add our widget list, we just put it in the app/views/admin/pages directory,
130. admin.pages.edit.add :form_top,
āwidget_listā
and add it to the proper region like so. In this case, weāre adding it to the form_top region of
the edit view for the pages controller.
131. include_stylesheet āwidgetsā
include_javascript āwidgetsā
Inside your partial, in addition to simply rendering, you can add stylesheets and javascript
ļ¬les to the head block in the layout so you can make your widget list look awesome.
132. brute-force
override
If you don't like the ļ¬nessed approach, you can brute-force override any view template,
134. extensions
before core
by putting one of the same name in your extension, since extension view paths are searched
before Radiant's built-in paths.
135. #3:
front-end
So now you've got your admin interface doing cool things, but you want to provide a little
more functionality on the front-end.
139. request.get? or
request.head?
# read cache
If the request method is GET or HEAD, the controller checks whether the requested URL is
cached, populating the response if it is.
159. 5-minute cache
with headers
Radiant nicely captures the response headers and status when caching pages so, if we want
to cache this, it won't have to call our process method again for 5 minutes after the ļ¬rst
render.
160. GET HEAD
cache
Now you may have noticed that when I walked through the SiteController workļ¬ow, it only
checks for cached versions of a page on GET or HEAD requests.
161. POST PUT DELETE
donāt cache
This means that when a page receives POST, PUT, or DELETE, you can do cool stu with the
data in the request.
162. mailer
database_form
For example, the mailer and database_form extensions respectively process email forms and
store data to the database on POST requests,
163. seamless
experience
allowing a seamless front-end experience.
164. #4:
I want my MVC
This is great and all, but sometimes you'd just rather use a controller and views instead of a
page.
166. ERb, Haml -
Radiant Layout
share_layouts lets you set a Layout, as in a Radiant Layout, within which your regular ERb or
Haml view will render.
167. content_for :side
part(:side)
Captured content blocks - using `content_for` - become page-parts,
168. @content_for_layout
part(:body)
and the default content becomes the body part, all of which are rendered in your Radiant
Layout via `r:content` tags.
169. @breadcrumbs
@title
You can also set the breadcrumbs and title that will be rendered inside our phantom page.
170. MOAR context
http://icanhascheezburger.com/2007/02/23/moar/
And should that not be good enough, and you need even better context, say, for rendering
localized navigation or an inherited sidebar,
175. just a
spoonful of sugar
And that's just a smidgen of how you can customize Radiant.
176. Radiant
Rails
Ruby
Because it's built on Rails, nearly anything you can do with Ruby and Rails can be done inside
Radiant extensions.
177. workflow
lifecycle
You could play with the model workļ¬ow and lifecycle,
178. concurrent_draft
(working copies)
like the `concurrent_draft` extension that enables working copies,
179. scheduler
(timed publish)
or the `scheduler` extension that lets you specify dates for your pages to appear and
disappear from the site.
180. multi_site
(virtual hosts)
Or you could modify built-in models and controllers, like the `multi_site` extension that
creates virtual hosts as multiple page-trees inside the same Radiant instance.
181. twitter
thirty_boxes
You could integrate with a third-party web-service, like the `twitter` and `thirty-boxes`
extensions.
182. file_system
DB - files
Or you could make design, development, and maintenance easier like the `ļ¬le_system`
extension, which serializes models into text ļ¬les,
183. import_export
(big YAML file)
or the `import_export` extension that dumps the whole database to a YAML ļ¬le.