Radiant CMS and the DRY principle

Pathfinderwebsiteredesign
Pathfinder is maybe a week away from deployment of its new website. I've posted several times previously about our experiences with Radiant CMS, the Rails-based publishing platform we're using to shield business users from actual code. Now that my Radiant work is coming to a close, I've got a pretty good handle on how build flexible templates without creating a maintenance nightmare. Sure, you're going to repeat yourself more with a CMS than with a custom, server-side templating engine. But you can get pretty darn DRY even in a tool as scaled-back as Radiant - all without conscripting your on-staff Rails developer to extend Radiant for you.

Pathfinder's primary objective in choosing Radiant was to allow content authors to create new pages using Textile - no HTML required. My own primary objective, as the keeper of HTML and CSS, was to build templates that would adjust to the needs of the content without requiring me to constantly create new layouts.

Our page design calls for a number of different content combinations: up to five discrete articles per page, and up to five discrete pieces of sidebar content. All sidebar modules require a special wrapper - two divs with specific classes applied - to achieve the correct look and feel. (Darn rounded corners.) To further complicate matters, most articles require their own special wrappers for CSS purposes, but some don't. If I'd built a different template to handle each possible combination, I would have gone insane the first time I needed to tweak the markup in all the places I had cut and pasted it.

Instead, my colleague Dietrich Kappe and I came up with the following approach:

  • Create a library of named page parts that get plugged into the layout in a specific order.
  • Educate business users about what these page parts are and how their naming conventions work.
  • Create templating logic that renders only those page parts which are present on a given page. Radiant's built-in <r:if_content/> and <r:unless_content/> tags will do nicely.
  • If multiple design treatments can be applied to the same named page part, create two alternate versions of the same page part and make them mutually exclusive in the rendering logic.
  • Where advisable, make page parts inheritable from the parent page using the <r:content inherit="true"/> tag.

The resulting template code looks like this:

<!--if there's a "body" page part,	wrap it in markup and show it--><r:if_content part="body">	<div class="articleWrapper">		<div class="article">			<r:content part="body"/>		</div>	</div></r:if_content>

<!--otherwise, look for a "bodyNoShell"	and show it sans markup wrappers--><r:unless_content part="body">	<r:if_content part="bodyNoShell">		<r:content part="bodyNoShell"/>	</r:if_content></r:unless_content>

<!--repeat for up to four more articles,	wrapped or unwrapped--><r:if_content part="body2">	<div class="articleWrapper">		<div class="article">			<r:content part="body2"/>		</div>	</div></r:if_content>

<r:unless_content part="body2">	<r:if_content part="body2NoShell">		<r:content part="body2NoShell"/>	</r:if_content></r:unless_content>

<r:if_content part="body3">	<div class="articleWrapper">		<div class="article">			<r:content part="body3"/>		</div>	</div></r:if_content>

<r:unless_content part="body3">	<r:if_content part="body3NoShell">		<r:content part="body3NoShell"/>	</r:if_content></r:unless_content>

<r:if_content part="body4">	<div class="articleWrapper">		<div class="article">			<r:content part="body4"/>		</div>	</div></r:if_content>

<r:unless_content part="body4">	<r:if_content part="body4NoShell">		<r:content part="body4NoShell"/>	</r:if_content></r:unless_content>

<r:if_content part="body5">	<div class="articleWrapper">		<div class="article">			<r:content part="body5"/>		</div>	</div></r:if_content>

<r:unless_content part="body5">	<r:if_content part="body5NoShell">		<r:content part="body5NoShell"/>	</r:if_content></r:unless_content>

Our business users now know they can create up to five articles per page, simply by placing their content in page parts with the correct names. For each of the five available slots, a slightly different name for the page part will result in that slot getting a special design treatment.

We follow a similar convention with our sidebar modules. The code there looks a little different, though, because we wanted to make sure the sidebar was never empty. To accomplish this, we made the first two sidebar modules non-conditional but also made them inheritable. Then we made sure that our homepage had both of these page parts defined. Individual pages can define their own sidebar modules, but if they define less than two, then they'll inherit the modules from their parent page or the homepage. The resulting code looks something like this:

<!--always show the first sidebar module;	inherit from homepage--><div class="railBlock">	<div class="header">		<h3>			<r:content part="railHead" inherit="true"/>		</h3>	</div>	<div class="text"><div class="inner">

		<r:content part="railBody" inherit="true"/>

	</div></div></div>

<!--ditto the second sidebar module--><div class="railBlock">	<div class="header">		<h3>			<r:content part="rail2Head" inherit="true"/>		</h3>	</div>	<div class="text"><div class="inner">

		<r:content part="rail2Body" inherit="true"/>

	</div></div></div>

<!--now display up to three additional side	modules only if they're defined on the	current page--><r:if_content part="rail3Head"><div class="railBlock">	<div class="header">		<h3>			<r:content part="rail3Head"/>		</h3>	</div>	<div class="text"><div class="inner">

			<r:if_content part="rail3Body">				<r:content part="rail3Body"/>			</r:if_content>

	</div></div></div></r:if_content>

<r:if_content part="rail4Head"><div class="railBlock">	<div class="header">		<h3>			<r:content part="rail4Head"/>		</h3>	</div>	<div class="text"><div class="inner">

		<r:if_content part="rail4Body">			<r:content part="rail4Body"/>		</r:if_content>

	</div></div></div></r:if_content>

<r:if_content part="rail5Head"><div class="railBlock">	<div class="header">		<h3>			<r:content part="rail5Head"/>		</h3>	</div>	<div class="text"><div class="inner">

		<r:if_content part="rail5Body">			<r:content part="rail5Body"/>		</r:if_content>

	</div></div></div></r:if_content>

You'll notice a couple things about my sidebar:

  1. Each sidebar module requires two named page parts, one for the headline and one for the actual content. This is because of the specific demands of our page design, which requires certain markup wrappers around the headline as a CSS "hook." An Agile Ajax reader has suggested a possible solution for this kind of situation: a Radiant extension that would allow me to pass the headline in as an attribute of the <r:content/> tag. I'll post more about this solution once I've seen it in action.
  2. Second, I have persisted in my 10-year habit of calling sidebars "rails," which is downright confusing in a post-Ruby on Rails world.

Nomenclature aside, though, I now have a single layout that can accommodate a lot of different content permutations. Sure, there's tons of repeated markup within the layout, but that's far preferable to repeating the same markup in 20 different layouts. If I need to change the markup pattern for the sidebar, I can do so in exactly one file within Radiant. Using a CMS instead of a templating engine is all about compromise: low technology overhead and simplicity for business users in exchange for less control and more repeated code.

Related posts:

  1. Radiant CMS: Some tradeoffs, but they’re worth it
  2. Radiant CMS: It’s all about the extensions
  3. Yet another trick for Radiant CMS: Reusable snippets
  4. Getting semantic and DRY with microformats and Radiant CMS
  5. Ask a UI Guy: How should I structure my stylesheets?

Comments: 3 so far

  1. This wasn’t the main point of your post, but how satisfied are you with Textile? I’m working on a very simple CMS for a business to allow corporate and sales to add news stories as well as edit page content. I figured most of these people have little to no HTML knowledge, so I decided I would port a WYSIWYG editor to output valid (X)HTML, but the cross-browser quirks are making it more of a challenge then I anticipated. I’m considering Textile or a Wiki-style markup instead, but at that point it seems you might as well use HTML for some people.

    Comment by T.J., Tuesday, March 11, 2008 @ 10:44 am

  2. @T.J.:

    I think it depends on who your business user are. If they have experience dealing with Wikis, then Textile is a great solution. The syntax will be comfortable for them and allow them to do the kinds of markup you want them doing – simple text with the occasional image. If, however, they are strangers to even the simplified syntax of a Wiki, then maybe WYSIWYG is the way to go. Personally I hate WYSIWYG editors – even the one I have to use to post to this blog. They may produce valid markup, but it’s usually pretty dumb markup, too.

    Comment by Brian Dillard, Tuesday, March 11, 2008 @ 12:19 pm

  3. @T.J.: have you tried Tiny MCE? It’s the best WYSIWYG editor that I know of (and I’ve tried a lot) – it produces valid XHTML most of the time (sometimes it replaces with – dunno why), is powerfull yet ease to use, and with propper config loads very fast.

    Comment by BTM, Wednesday, March 12, 2008 @ 3:32 am

Leave a comment

Powered by WP Hashcash

Launch: Pathfinder Newsletter

    Get a monthly update on best practices for delivering successful software.

    Subscribe via email


    Subscribe via RSS      RSS icon

Topics

Search

WordPress

Comments about this site: info@pathf.com