Content Management : Processing Dynamic Content

ColdFusion Add comments

One of things I see a lot of in my various jobs and contracts is Content Management Systems (CMS), and they come all manner of shapes and sizes. The most common is where you are given nothing more than a textarea to edit your pages HTML content, some might implement a WYSIWYG editor such as HTMLAREA, TinyMCE or FCKEditor, but on the whole they are usually very simple and do not give the client any real control. Then you have the other end of the scale, where the CMS is so complex that no-one except the developer who wrote it knows how it works and even the client doesn't really know how to use it properly. 

 

One of common issues I have found with all the CMS systems I have worked on is "how to insert dynamic content". Dynamic content would be anything that is not static HTML and needs to be generated from code, whether it is just inserting a variable, including a file or calling a CFC. How do you allow this in your WYSIWYG HTML editor without having to insert actual code or rely on the client to do so or hard code routines between your content that the client has no control over, the answer is actually quite simple, lexicons (or macros to use a more generic term). All this really means is putting a placeholder in your HTML and at runtime substituting it with the code that generates the dynamic content, and even the code that does the substitution is not complex, so in this article I will be showing you how it's done.

 

I have put together a simple demo showing this in action which you can see HERE 

You can download all the code for this demo HERE

 

For the purposes of this demo I have used the TinyMCE editor, which is the simplest solution I have found for adding WYSIWYG capabilities to your pages as it automatically replaces any textarea with a WYSIWYG editor, so no re-coding is even required for the most basic implementation.

I have also purposefully kept the code as simple as possible, so no framework, no methodology, just a few simple stand alone files, so anyone should be able to understand it and use it. 

 

After you have downloaded the demo, create a folder called CMS on your server and unzip the contents to this folder, then browse to http://servername/cms/index.cfm and you should see the same as my above demo.

 

The first file we will look at is lexicons.xml

 

 <?xml version="1.0" encoding="UTF-8" ?>

    <lexicon>
        <description>Component Call</description>
        <value>[component]</value>
        <action>
            <component method="fooMethod">fooComponent</component>
        </action>
        
        
    </lexicon>
    <lexicon>
        <description>variable substitute</description>
        <value>[variable]</value>
        <action>
            <variable>request.myVar</variable>
        </action>
    </lexicon>
    <lexicon>
        <description>cfinclude</description>
        <value>[cfinclude]</value>
        <action>
            <include>content/signature.html</include>
        </action>
    </lexicon>
    <lexicon>
        <description>customTag</description>
        <value>[customTag]</value>
        <action>
            <customTag>myTag</customTag>
        </action>
    </lexicon>

 As you can see this is just a straight forward XML file, inside which we define our lexicons elements, each one having the following child elements:-

 

  • description : this is the text that appears in the drop down list
  • value : what is actually inserted into the content
  • action : what happens when the lexicon is invoked

 

The action is just a descriptive identifier, you can create an action for anything you like as long as you create the associated code for it in the lexicons.cfc. The default actions I have created here are probably going to cater for most everything you will need to do though as any code you need to execute is going to be in a CFC, custom tag or cfinclude. 

 

ACTIONS

  • component : inside this element you specify the cfc you want to call including its path, and in the method attribute you specify a function within the CFC that you will call. This will result in a <cfinvoke component="component" method="method"> call and the result will replace the lexicon.
  •  variable : simply specify a coldfusion variable whose value you wish to replace the lexicon.
  • include : the path/name of a file to include. the path should either be relative to the lexicons.cfc or use a mapping. The specified fille will then simply be CFINCLUDED in place of the lexicon.
  • customTag : the name of a custom tag. As the tag will be called using CFMODULE you must include the full path to the tag either relative to the lexicons.cfc or using a mapping.


So lets say you have a CFC called getNews.cfc with a method called latestNews that gets the latest news items from your database and you want to insert these into your content, so you would simply create a new lexion that will call this component. E.G.

 

 <lexicon>
        <description>Latest News Items</description>
        <value>[LatestNews]</value>
        <action>
            <component method="latestNews">getNews</component>
        </action>
 </lexicon>

Your component then simply needs to return the correctly formatted HTML. 

 

To create a dropdown list containing a list of your lexicons, you would do the following, and then a simple bit of javascript to insert the selected item into your HTML as I have done in the demo.

<cfinvoke component="lexicons" method="xml2struct" returnvariable="lexicons">
<select name="lexicon" id="lexicon">
        <cfloop from="1" to="#arraylen(lexicons.lexicon)#" index="x">
            <option value="#lexicons.lexicon[x].value#">#lexicons.lexicon[x].description#
        </cfloop>
</select>

 

Now lets have a look at lexicons.cfc, which actually does the work of parsing the content, looking for lexicons, and performing the specified action.

 

<cfcomponent displayname="lexicons" hint="These are the functions for parsing and processing of lexicons" output="true">
    <cffunction name="XML2Struct" access="public" hint="parses the lexicons xml and convert to a CF struct for easier manipulation" output="false" returntype="struct">
        <cfset var locals = StructNew()>
        <cffile action="read" file="#expandpath('.')#/lexicons.xml" variable="locals.lexicons">
        <cf_easyxml xml="#locals.lexicons#" saveas="locals.lexiconsStruct">
        <cfreturn locals.lexiconsStruct>
    </cffunction>
   
    <cffunction name="parseContent" access="public" hint="parses any supplied string and performs the required action on any found lexicons" output="false" returntype="string">
        <cfargument name="content" hint="the content to be parsed" type="string" required="true">
        <cfset locals = StructNew()>
        <cfset locals.lexicons = XML2Struct()>
        <!--- loop over the lexicons and search for them in the content, REGEX may be faster --->
        <cfloop from="1" to="#arraylen(locals.lexicons.lexicon)#" index="locals.x">
            <!--- if lexicon is found, determine the action to be performed, perform it, and replace the lexicon with the result --->
            <cfif findNoCase(locals.lexicons.lexicon[locals.x].value,arguments.content)>
                <cfif StructKeyExists(locals.lexicons.lexicon[locals.x].action, "component")>
                    <!--- the action is a component call --->
                    <cfinvoke component="#locals.lexicons.lexicon[locals.x].action.component.tagcontent#" method="#locals.lexicons.lexicon[locals.x].action.component.method#" returnvariable="locals.substitute">
                    <cfset arguments.content = ReplacenoCase(arguments.content,locals.lexicons.lexicon[locals.x].value, locals.substitute,"ALL")>
                <cfelseif StructKeyExists(locals.lexicons.lexicon[locals.x].action, "variable")>
                    <!--- the action is a simple variable substitute --->
                   
                    <cfset arguments.content = ReplacenoCase(arguments.content,locals.lexicons.lexicon[locals.x].value, evaluate(locals.lexicons.lexicon[locals.x].action.variable),"ALL" )>
                <cfelseif StructKeyExists(locals.lexicons.lexicon[locals.x].action, "include")>
                    <!--- the action is a cfinclude, the path must be relative to this file or use a mapping --->
                    <cfsavecontent variable="locals.substitute"><cfinclude template="#locals.lexicons.lexicon[locals.x].action.include#"></cfsavecontent>
                    <cfset arguments.content = ReplacenoCase(arguments.content,locals.lexicons.lexicon[locals.x].value, locals.substitute, "ALL")>
                <cfelseif StructKeyExists(locals.lexicons.lexicon[locals.x].action, "customTag")>
                    <!--- the action is a custom Tag --->
                    <cfsavecontent variable="locals.substitute"><cfmodule template="myTag.cfm"></cfsavecontent>
                    <cfset arguments.content = ReplacenoCase(arguments.content,locals.lexicons.lexicon[locals.x].value, locals.substitute, "ALL")>
                </cfif>
            </cfif>
        </cfloop>
        <!--- return the parsed content --->
       
        <cfreturn arguments.content>
    </cffunction>
</cfcomponent>

 

XML2Struct simply loads the lexicons.xml file and converts it to a coldfusion structure for easier manipulation. For this I have used a handy tag called easyXML.

parseContent actually does the rest. It takes only 1 argument "content", and then loops over the lexicons searching for each one in the supplied content, when it finds one, it then grabs the associated action for that lexicon and performs it. So if you wanted to create your own action, you would simply add it to the lexicons.xml and then add a new condition to the if/else block to deal with that action.

 

So to process your HTML content that contains lexicons you then simply call this function and pass in your content and then display what it gives you back, E.G.

<cfinvoke component="lexicons" method="parseContent" content="originalContent" returnvariable="newContent">
<cfoutput>#newContent#</cfoutput>

 

That's it, can't get much simpler than that can you. But honestly I have seen some amazingly complex applications with thousands of lines of code and dozens of files to achieve this same thing.

 

This is of course a really basic example, and you can easily extend this further to allow your lexicons themselves to have attributes, e.g. [getnews id=5] or perhaps set new action parameters to allow you to optionally pass form and url scopes into your component calls, the possibilities are pretty endless and with this as a base you can build a pretty powerful CMS system quite easily.

This solution works whether your content comes from flat files or form a database, and you can of course optionally choose to cache your generated content as well.

 

 

2 responses to “Content Management : Processing Dynamic Content”

  1. Monique Says:
    I have been struggling with fckEditor on a project for about 8 months now and I REALLY need a better solution. A search led me to your post but I am not quite sure how this works. I don't get the demo. For the "inserting dynamic data" portion, is that going to be part of my editor?
  2. Rakesh Says:
    One of the reasons that Hebrew and Greek have been so diuflcfit to retain is because the traditional teaching method focuses on memorizing grammar tables. Studies have found that this produces very poor retention. Methods based on how languages are learned naturally are far more effective. Decades ago, teachers of modern language phased out the grammar-based approach, but biblical language educators have been slow to change.The Hebrew and Greek courses from the Biblical Language Center (www.biblicalulpan.org) have pioneered some of these newer methods. As a very satisfied graduate, I recommend them highly. Check out their website for more.Lois

Leave a Reply

Leave this field empty

Powered by Mango Blog. Design and Icons by N.Design Studio