So, How about a templating engine for BlogCFC?

Recently I finished overhauling the way HomePortals 3.1 handles page templates and wanted to find a real-life (and useful) way in which I could demonstrate its new features. So, in this post I want to show how the templating features in the new HomePortals version can be leveraged to provide layout management capabilities on top of an existing application like BlogCFC. Why BlogCFC? well, first because it has a great segmentation between the blogging engine (the actual blog.cfc) and its presentation layer, and second because I already use it on my own blog and have plenty of real-life data to play with.

For this project I chose a design from Styleshout that was well suited to a blog and looked nothing like the default BlogCFC structure or design (or mine for that matter). The design I chose was this one.

Without going into much details, HomePortals uses two different types of templates: "Page" templates and "Module" templates. A page template describes the overall structure of the entire page, things like HTML markup and where the content will go. And module templates describe the HTML markup of the containers of such content. A site can have multiple page templates, however each individual page may have only one page template. On the other hand, each module or content block on the page can have a different module template.

Templates in HomePortals are nothing more than plain HTML files, the only thing that makes them special is that they contain special tokens or keywords that tell the rendering engine where to place special content such as the page title, modules, and many other things.

Since templates are just HTML files, the first part of adapting the pure HTML and CSS design is very straightforward. Starting with the original html page, its just a matter of going through the source and identifying the sections where we want the dynamic content to go and replace it with the appropriate tokens.

For example, here is a fragment of the HEAD section of the page:

Original design:

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">

<head>

<title>Keep It Simple</title>

<meta http-equiv="content-type" content="application/xhtml+xml; charset=UTF-8" />
<meta name="author" content="Erwin Aligam - styleshout.com" />
<meta name="description" content="Site Description Here" />
<meta name="keywords" content="keywords, here" />
<meta name="robots" content="index, follow, noarchive" />
<meta name="googlebot" content="noarchive" />

<link rel="stylesheet" type="text/css" media="screen" href="css/screen.css" />

</head>

Converted to page template:

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">

<head>
   <title>$PAGE_TITLE$</title>
   <meta http-equiv="content-type" content="application/xhtml+xml; charset=UTF-8" />
   <meta name="author" content="$PAGE_PROPERTY["author"]$" />
   <meta name="description" content="$PAGE_PROPERTY["description"]$" />
   <meta name="keywords" content="$PAGE_PROPERTY["keywords"]$" />
   <meta name="robots" content="index, follow" />
   <link rel="stylesheet" type="text/css" media="screen" href="css/screen.css" />
</head>

Here, $PAGE_TITLE$ is the token that indicates the location of the page's title. The $PAGE_PROPERTY[xxx]$ tokens are replaced dynamically by custom properties of the page. (more on page properties later)

Similarly we identify the sections where the actual content will go, so we end up with fragments like the following on our template:

<!-- main -->
      <div id="main" class="grid_8">
            
         $PAGE_LAYOUTSECTION["main"][""]$
      
      <!-- main ends -->
      </div>

Which indicates a location named "main" that will be used to place the actual content.

After we have completed converting the original html page into a page template, we need to create one or more "module" templates to accomodate the different sections or blocks of content. Again, we create those templates based on the original HTML markup and just replace any dummy or fake copy with a token.

Here is the module template that will be used for each blog post:

<h2><a href="$MODULE_POSTURL$">$MODULE_TITLE$</a></h2>

<p class="post-info">Posted by <a href="$MODULE_AUTHORURL$">$MODULE_AUTHOR$</a></p>
   
$MODULE_BODY$
   
<p class="postmeta">      
<a href="$MODULE_POSTURL$#comments" class="comments">Comments ($MODULE_NUMCOMMENTS$)</a> |      
<span class="date">$MODULE_POSTDATE$</span>
</p>

Remember that all the tokens will be replaced at rendering time with appropriate content.

So, after going through the original document, I ended up with the following templates:

frontpage.html (page)
blogPost.html (module)
featuredElement.html (module)
plainElement.html (module)
sideElement.html (module)

Click on each to view the source if you want.

Of course all CSS and Images remain the same.

The next part is to have HomePortals put everything together and hookup into the BlogCFC engine.

For this, there are only three files involved: Application.cfc, appInit.cfm and index.cfm

We use Application.cfc only to declare the application and provide a mechanism to call the application initialization code (appInit.cfm) on demand by using the resetApp url variable.

Application.cfc:

<cfcomponent>
   <cfset this.applicationName = "keepItSimple">
   
   <cffunction name="onApplicationStart">
      <cflock name="appInit" timeout="30">
         <cfinclude template="appInit.cfm">
      </cflock>
   </cffunction>

   <cffunction name="onRequestStart">
      <cfparam name="url.resetApp" default="">
      <cfif isDefined("url.resetApp") and isBoolean(url.resetApp) and url.resetApp>
         <cfset onApplicationStart()>
      </cfif>
   </cffunction>
   
</cfcomponent>

appInit.cfm is used to initialize the homePortals and blogCFC engines and store them in memory.

appInit.cfm:

<cfsilent>
   <!--- initialize and configure homePortals --->
   <cfset application.homePortals = createObject("component","homePortals.components.homePortals").init("/keepItSimple")>
   
   <!--- modify config settings --->
   <cfset c = application.homePortals.getConfig()>
   <cfset c.setRenderTemplate("frontpage","page","/keepItSimple/templates/frontpage.html")>
   <cfset c.setRenderTemplate("sitepage","page","/keepItSimple/templates/sitepage.html")>
   <cfset c.setRenderTemplate("featured","module","/keepItSimple/templates/featuredElement.html")>
   <cfset c.setRenderTemplate("sidebar","module","/keepItSimple/templates/sideElement.html")>
   <cfset c.setRenderTemplate("blogPost","module","/keepItSimple/templates/blogPost.html")>
   <cfset c.setRenderTemplate("plain","module","/keepItSimple/templates/plainElement.html")>
   <cfset application.homePortals.initEnv()>
   
   
   <!--- initialize blog --->
   <cfset blogname = "Default">
   <cfset application.blog = createObject("component","org.camden.blog.blog").init(blogname)>
</cfsilent>

After initializing the homePortals environment, the first thing to do is tell the engine that we want to use our own page and module templates instead of the default ones. This is done by calling the setRenderTemplate() method. We only need to give each template a name, state what type of template is and give its location. After that we call initEnv() to tell HomePortals to apply the new settings.

NOTE: This configuration can also be done using a configuration xml document, but to keep everything short and simple we are doing it this way.

Next, we initialize the blogCFC engine and pass the name of our blog instance ("Default").

Now let's take a look at index.cfm. Here is where all the action takes place:

Here is the full listing:

index.cfm:

<cfsilent>
   <!--- side elements --->
   <cfset s1 = {moduleType = "content", href="/keepItSimple/content/sidebarMenu.htm", title="Sidebar Menu", moduleTemplate="sidebar"}>   
   <cfset s2 = {moduleType = "view", href="/keepItSimple/content/categories.cfm", title="Categories", moduleTemplate="sidebar"}>   
   <cfset s3 = {moduleType = "view", href="/keepItSimple/content/wiseWords2.cfm", title="Wise Words", moduleTemplate="sidebar"}>   

   
   <!---- bottom content --->
   <cfset b1 = {moduleType = "content", href="/keepItSimple/content/resources.htm", title="Resource Links", moduleTemplate="plain"}>   
   <cfset b3 = {moduleType = "content", href="/keepItSimple/content/about.htm", title="About", moduleTemplate="plain"}>
   
   
   <!--- featured posts --->
   <cfset featuredPosts = arrayNew(1)>
   <cfset features = ["HomePortals-3-Portal-Framework",      
"Using-Polymorphism-and-Inheritance-to-Build-a-Switchable-Data-Access-Layer",
                  "BugLogHQ-Now-at-RiaForge"]>

                     
   <cfloop from="1" to="#arrayLen(features)#" index="i">
      <cfset params = { byAlias = features[i] }>
      <cfset articles = application.blog.getEntries(params)>
      <cfif articles.recordCount gt 0>
         <cfset entry = application.blog.renderEntry(articles.body,false,articles.enclosure)>
         <cfset fragment = left(entry,findNoCase("</p>",entry)-1)>

         <cfset f = {moduleType = "content",
                  moduleTemplate = "featured",
                  body = fragment,
                  title = articles.title,
                  postedBy = articles.name,
                  postedOn = lsDateFormat(articles.posted),
                  postURL = application.blog.makeLink(articles.id)}>
   
         <cfset arrayAppend(featuredPosts,duplicate(f))>
      </cfif>
   </cfloop>
   
   
      
   <!--- main content --->
   <cfset params = { releasedonly = true, maxEntries = 3}>
   <cfset articles = application.blog.getEntries(params)>
   <cfset posts = arrayNew(1)>
   <cfloop query="articles">
      <cfset entry = application.blog.renderEntry(articles.body,false,articles.enclosure)>
      <cfset fragment = left(entry,findNoCase("</p>",entry)-1)>
      <cfif len(fragment) lt len(entry)>
         <cfset fragment = fragment & " &nbsp; <a href='#application.blog.makeLink(articles.id)#'><b>Read More...</b></a>">
      </cfif>
      
      <cfset m = {moduleType = "content",
               moduleTemplate = "blogPost",
               body = fragment,
               title = articles.title,
               author = articles.name,
               authorURL = "http://www.oscararevalo.com",
               numComments = val(articles.commentCount),
               postDate = lsDateFormat(articles.posted),
               postURL = application.blog.makeLink(articles.id)}>
   
      <cfset arrayAppend(posts,duplicate(m))>
   </cfloop>
   
   
   
   <!--- create page --->
   <cfset p = createObject("component","homePortals.components.pageBean").init()
            .setPageTemplate("frontpage")
            .addLayoutRegion("featured","featured")
            .addLayoutRegion("sidebar","sidebar")
            .addLayoutRegion("bottomleft","bottomleft")
            .addLayoutRegion("bottomRight","bottomRight")
            .addLayoutRegion("main","main")
            .setTitle("HomePortals Template Demo")
            .setProperty("mainTitle","HomePortals")
            .setProperty("slogan", "Sweet layouting goodness...")
            .setProperty("rssURL", "http://www.oscararevalo.com/rss.cfm?mode=full")
            .setProperty("description", application.blog.getProperty("blogDescription") )
            .setProperty("keywords", application.blog.getProperty("blogKeywords") )
            .setProperty("author", "Oscar Arevalo")
            .addModule("s1","sidebar",s1)
            .addModule("s2","sidebar",s2)
            .addModule("s3","sidebar",s3)
            .addModule("b1","bottomleft",b1)
            .addModule("b3","bottomRight",b3)
   >


   <cfloop from="1" to="#arrayLen(featuredPosts)#" index="i">
      <cfset p.addModule("f#i#","featured",featuredPosts[i])>
   </cfloop>
   
   <cfloop from="1" to="#arrayLen(posts)#" index="i">
      <cfset p.addModule("m#i#","main",posts[i])>
   </cfloop>
               
   <!--- render page --->
   <cfset html = application.homePortals.loadPageBean(p).renderPage()>
</cfsilent>

<cfoutput>#html#</cfoutput>

Let's examine the code piece by piece:

Here are the first few lines:

<!--- side elements --->
   <cfset s1 = {moduleType = "content", href="/keepItSimple/content/sidebarMenu.htm", title="Sidebar Menu", moduleTemplate="sidebar"}>   
   <cfset s2 = {moduleType = "view", href="/keepItSimple/content/categories.cfm", title="Categories", moduleTemplate="sidebar"}>   
   <cfset s3 = {moduleType = "view", href="/keepItSimple/content/wiseWords2.cfm", title="Wise Words", moduleTemplate="sidebar"}>   

   
   <!---- bottom content --->
   <cfset b1 = {moduleType = "content", href="/keepItSimple/content/resources.htm", title="Resource Links", moduleTemplate="plain"}>   
   <cfset b3 = {moduleType = "content", href="/keepItSimple/content/about.htm", title="About", moduleTemplate="plain"}>

These series of struct definitions are how we define content blocks or modules. Some of the keys on the struct have special meaning and are required by the engine, like moduleType and moduleTemplate, and others are dynamic and are used to describe that particular content block.

The first three structs contain the contents of the "sidebar" section, which is the narrow middle column. And the last two structs corrspond to the content that goes at the bottom of the page.

If you look at these structs, you will see that all of them begin with a "moduleType" key. This value tells HomePortals 'How' to handle and render each content block. Here we see two types of modules: "content" and "view". Both are very similar in the sense that they point (href) to some resource. The difference is that content modules read and output the content literally and view modules interpret the pointed resource as ColdFusion code (like a cfinclude). For example, sidebarMenu.htm, resource.htm and about.htm are documents containing html fragments that could be created using any HTML editor by anyone, while categories.cfm and wiseWords2.cfm are actual snippets of CFM code. (by the way, wiseWords uses the very funny Alagad's Fortune webservice).

Also note that each struct has a "moduleTemplate" key. These are used to indicate which of the module templates declared on appInit.cfm we want to use for rendering that particular module.

In a bit we will see how these structs are used and added to the page.

In the next block we actually interact with BlogCFC to get some blog content:

<!--- featured posts --->
   <cfset featuredPosts = arrayNew(1)>
   <cfset features = ["HomePortals-3-Portal-Framework",
"Using-Polymorphism-and-Inheritance-to-Build-a-Switchable-Data-Access-Layer",
                  "BugLogHQ-Now-at-RiaForge"]>

                     
   <cfloop from="1" to="#arrayLen(features)#" index="i">
      <cfset params = { byAlias = features[i] }>
      <cfset articles = application.blog.getEntries(params)>
      <cfif articles.recordCount gt 0>
         <cfset entry = application.blog.renderEntry(articles.body,false,articles.enclosure)>
         <cfset fragment = left(entry,findNoCase("</p>",entry)-1)>

         <cfset f = {moduleType = "content",
                  moduleTemplate = "featured",
                  body = fragment,
                  title = articles.title,
                  postedBy = articles.name,
                  postedOn = lsDateFormat(articles.posted),
                  postURL = application.blog.makeLink(articles.id)}>
   
         <cfset arrayAppend(featuredPosts,duplicate(f))>
      </cfif>
   </cfloop>

In this section we are interacting with the blog engine to pull the content of three blog posts that we want to use for the "Featured" region of the page (the right column). Here I just pull three entries and create corresponding module structs but use the blog post data for the values of module properties. Note that I am only using the first paragraph of each entry and assigning it to a a "body" attribute. And also that there is no "href" key in these content modules.

The reason is that we want to provide the actual content inline on the module struct instead of having homePortals retrieve it from an external source. If you recall from the module template for the blog post, there were several tokens of the form $MODULE_xxx$. In the case of module templates, the xxx in the token is used to indicate a module attribute (attributes are the keys on the struct), so $MODULE_BODY$ will get replaced dynamically by whatever we use for the "body" attribute.

The next section is very similar to the last one except that it retrieves the latest 3 entries on the blog. However, note that it uses a different module template and consequently different module attributes and tokens. But the mechanics are the same.

Now is time to assemble the page.

<!--- create page --->
   <cfset p = createObject("component","homePortals.components.pageBean").init()
            .setPageTemplate("frontpage")
            .addLayoutRegion("featured","featured")
            .addLayoutRegion("sidebar","sidebar")
            .addLayoutRegion("bottomleft","bottomleft")
            .addLayoutRegion("bottomRight","bottomRight")
            .addLayoutRegion("main","main")
            .setTitle("HomePortals Template Demo")
            .setProperty("mainTitle","HomePortals")
            .setProperty("slogan", "Sweet layouting goodness...")
            .setProperty("rssURL", "http://www.oscararevalo.com/rss.cfm?mode=full")
            .setProperty("description", application.blog.getProperty("blogDescription") )
            .setProperty("keywords", application.blog.getProperty("blogKeywords") )
            .setProperty("author", "Oscar Arevalo")
            .addModule("s1","sidebar",s1)
            .addModule("s2","sidebar",s2)
            .addModule("s3","sidebar",s3)
            .addModule("b1","bottomleft",b1)
            .addModule("b3","bottomRight",b3)
   >


   <cfloop from="1" to="#arrayLen(featuredPosts)#" index="i">
      <cfset p.addModule("f#i#","featured",featuredPosts[i])>
   </cfloop>
   
   <cfloop from="1" to="#arrayLen(posts)#" index="i">
      <cfset p.addModule("m#i#","main",posts[i])>
   </cfloop>

Pages in HomePortals are created programmatically by declaring an instance of pageBean.cfc. All modifier methods on pageBean return an instance of itself, so its pretty easy to just chain the method calls to provide a "flow" for describing the page.

We begin by specifying which page template we want to use. Then we use addLayoutRegion() to tell our page which of the secionts on the page template we intend to use for placing modules. Next we set the page title and some custom properties. Note that these properties are completely ad-hoc and are leveraged on our page template by the use of tokens of the form $PAGE_PROPERTY["xxx"]$.

Then we add all our content modules using addModule(). For each module we must give it a name, a location where to place it, and the struct containing its properties or attributes. The 2 cfloops at the end are used to add the modules declared dynamically using blogCFC content.

And the end.....

<!--- render page --->
   <cfset html = application.homePortals.loadPageBean(p).renderPage()>

   <cfoutput>#html#</cfoutput>

These last couple of lines are where we pass our pageBean object to homePortals and ask the rendering engine to process it and give us the corresponding HTML for output.

So, how does all of this looks like? look for yourselves

I probably went into much more detail that I should, but I hope I was able to communicate how HomePortals can be used to provide a programmatic and managed way to deal with layout and modularity, even on applications that were not originally designed with homePortals in mind.

Obviously this example is not a complete rewrite of the entire BlogCFC user interface, but I think it clearly shows that it is in fact possible to do so using the tools and features provided by HomePortals.

Feel free to grab a copy of the files for this project and a copy of HomePortals and play around with your own blog, and maybe try this same approach with other styleshout designs (or anybody else's). If you do drop me a line and I'll try to help in anything I can.

Related Blog Entries

Comments (Comment Moderation is enabled. Your comment will not appear until approved.)
Henry Ho's Gravatar I don't get it... how is this better than cfinclude with shared variables scope?
# Posted By Henry Ho | 5/12/09 4:06 PM
Oscar Arevalo's Gravatar Hey Henry,
yes, the end result is pretty much the same, however the difference relies that with this approach the entire layout and content is abstracted and managed programmatically. For example, lets say you want to move the location of the "categories" pod from one part of the page to another (i.e. from one column to another). If you use the cfinclude route, you have to edit your file, rearrange your html by hand and then save/upload your changes. On the other hand, using a managed approach (like this one) you do the same just by assigning the "categories" module to a different container. More over, since all you are doing are calling methods on objects, you can easily create a much more elegant admin interface to do this entirely using a friendlier user interface than editing HTML files.
# Posted By Oscar Arevalo | 5/12/09 4:15 PM
Oscar Arevalo's Gravatar @Henry, also another benefit is that you can keep your templates with the HTML structure separate from your CFML display logic; which can be useful if you are working together with web designers that may or may not get confused by all the cftags (and cannot mess up anything if they "accidentally" move or delete a cf tag)
# Posted By Oscar Arevalo | 5/12/09 4:19 PM
sandy's Gravatar Hi Oscar,
Thanks for posting this article. I didn't understand about appInit.cfm file. I have checked in BlogCFC code but i didn't find it. Could you pls let me know, where is it? I am very new to this BlogCFC and trying to change the template and your article is very useful for me. It gave me some idea, how can i work on that. Please let me know about appInit.cfm file and If you have any another article on this please, please send it to my e-mail address.
Thank you

Sandy
# Posted By sandy | 1/25/10 12:11 PM
Oscar Arevalo's Gravatar Hey Sandy,
The appinit.cfm is not part of the standard BlogCFC distribution, its something I created for this particular case of using HomePortals as the template engine for BlogCFC. BlogCFC contains its own initialization mechanisms completely different from what is shown here.
Oscar
# Posted By Oscar Arevalo | 1/25/10 2:27 PM
BlogCFC was created by Raymond Camden. This blog is running version 5.9. Contact Blog Owner