Proposal: generic conditionals for Radiant CMS

Over the years we have had many requests on the Radiant CMS Mailing List to add better support for conditional tags of some kind. Right now we have an array of tags for making conditional statements that are included in the standard distribution. The following is an incomplete list:

<r:if_children>           <r:unless_children>
<r:if_content> <r:unless_content>
<r:if_url> <r:unless_url>
<r:if_ancestor_or_self> <r:unless_ancestor_or_self>
<r:if_self> <r:unless_self>
<r:if_parent> <r:unless_parent>
<r:if_dev> <r:unless_dev>

When Radiant was first released, we only had if_content and the rest were added as the need arose. We have considered adding generic support for conditionals, but every proposal thus far seems to require additional syntax of some kind. Chris Parish took a stab at adding generic support for conditionals at one point. His extension allows a user to type expressions like:

<r:if cond="breadcrumb matches /your regexp/">

or even,

<r:if cond="parts includes-any ['body', 'other part']">

To date we have avoided adding something like this to the core because of quibbles over the precise syntax. We also value the rigidity that the Radius attribute syntax provides. To be effective with tags in Radiant, a designer only has to understand tags, attributes, and nesting. Adding another strange comparison syntax on top of this seems needlessly complex.

Towards A Generic Syntax for Conditionals

While thinking about some of this today, it occurred to me that there might be a reasonable compromise that preserves HTML-like, attribute-oriented syntax while allowing a high degree of flexibility.

So without further ado, I would like to propose that we add generic support for conditionals to Radiant with the following basic syntax:

<r:if property="name" matches="regexp">

or unless,

<r:unless property="name" matches="regexp">

Above, the property attribute indicates a property on a page that a conditional operates on. (I will talk about properties in detail in a minute.) The matches attribute indicates that the property should match the given regular expression if the tag is going to expand. Additional attributes would include equals, nequals (for not equals), and nmatches (for not matches).

Now what exactly are properties? Properties would be a new first class concept in Radiant. There would be properties for all of the default page attributes (title, slug, url, etc.), but you would also be able to define your own properties with a class method on Page. So subclasses of Page could define their own properties or extensions could add properties to Page to declare properties that are accessible to all pages.

To declare a property you would define it within the class definition of a page like this:

property 'name'

By default, with no additional parameters this would assume that the property ‘name’ is a reference to the ActiveRecord attribute ‘name’. But you could also pass a block to calculate properties on the fly:

# Under the covers, properties are just augmented method
# declarations so you still have access to other methods on
# the page, like "url" below.
property 'full_url' do
  'http://wiseheartdesign.com' + url
end

This would allow you to create constructs like:

property 'author.name' do
  author.name
end

Of course, some additional sugar would make this much easier:

property 'author', :method => 'author', :attributes => [:name, :email, :website]

This would declare three properties, ‘author.name’, ‘author.email’, and ‘author.website’.

Once properties are declared you could access them within tag definitions like this:

# A basic implementation of the if tag
tag 'if' do |tag|
  page = tag.locals.page
  property = tag.attr['property']
  value = page.properties[property]
  equals = tag.attr['equals']
  tag.expand if value == equals
end

The bracket syntax would give tag authors easy access to properties on a page and make properties a first class concept in Radiant that extension authors can take to another level.

A Generic Case Statement

I would also suggest that there is room for a third type of conditional statement. We already have if and unless, why not case?

<r:case property="author.name">
  <r:when equals="John">Lead Designer</r:when>
  <r:when equals="Sean">Lead Developer</r:when>
  <r:else>Core Developer</r:else>
</r:case>

And yes did you see that? case, when, and else allow us to avoid if, then, and else – another one of the commonly suggested features. We can keep it clean with single tags for if and unless, and allow case, when, and else for complex conditionals.

Suggestions, Addendums, Thoughts?

So there you have it. My proposal for adding a generic syntax for Radiant conditionals to the standard tag library. Any thoughts or comments from the Radiant community? Is this a can of worms or a powerful, much needed addition? Put your thoughts in the comments below.

© 2013 John W. Long