• home
  • forum
  • my
  • kt
  • download
  • ColdFusion Methodologies for Content Management

    Author: 2008-08-05 07:06:44 From:

    Click here to download a .zip file of this article.

    This document was created for the purpose of explaining a personal programming technique using ColdFusion. The document explains an example Web site in terms of logic, ColdFusion architecture, and file structure. This document is technical in nature and a strong understanding of ColdFusion is required.

    Once you have reviewed this guide, you should be able to:

    • Maintain independent ColdFusion logic with minimal impact on other pages
    • Easily find the pieces of logic without having to sift through thousands of lines of code
    • Combine applications with existing or future systems

    This guide uses a real Web site for all examples. This Web site is used by Allaire instructors and is called "TERA," or Trainers and Education Resource Area.

    Note: This methodology is not an "official" practice, nor is it modeled after other ColdFusion methodologies such as Fusebox, cfObjects, or SmartObjects.

    Basic Architecture

    Tera exists in the webroot under a directory named "IC" (for Instructor Central).

    Almost every directory is named after an "object" in TERA. Inside of each object directory are a bunch of files with names such as "getAnnouncements.cfm" or "delete.cfm." Inside of the Announcement directory, the following files exist:

    As you can see, each file is the name of some "method" for that object. For this object (announcements), we have a number of methods that do things for the announcement.

    For instance, the delete method will eliminate a certain announcement. The GetInArray method will get a list of announcements for a certain page and return them in the correct order.

    Flow of Processing

    If you look at the previous directory structure, you will notice a directory called docs. Unlike most of the directories, this one is not the name of an object. This is the directory where all of the visual pages will be stored. If a user clicks on a link in the Web site, they will go to a page in docs such as /tera/docs/index.cfm, which is the home page for the Web site. (The /tera/ preface is a ColdFusion mapping that represents the directory IC).

    When a page runs in the docs directory, it typically invokes methods on objects. The most common methods are display methods. For instance, the announcment.cfm page invokes the display method on the news object.

    So how are methods invoked? Methods are invoked by a file called invoker.cfm which lives in the root directory (ic or /tera/ through the mapping). The invoker is called by using the CFMODULE tag as follows:

    announcement.cfm (in docs)
    <CFMODULE 
    	TEMPLATE="/tera/invoker.cfm" 
    	OBJECT="announcement" 
    	METHOD="display"
    	AnnouncementID = "6">

    The news.cfm page is now invoking the display method on the news object. Under the covers, the logic inside of the invoker is relatively straightforward. Here is the code inside of invoker.cfm

    invoker.cfm (in IC or /tera/)

    <!--- this is the invoker.  it calls objects.  
    	Every object has its own folder. 
     	The methods are the file names --->
    
    <CFIF thisTag.executionMode is "start">
    <!--- send attributes down to the custom tag --->
    
    <CFMODULE
    TEMPLATE="/tera/#attributes.object#/#attributes.method#.cfm"
     	stAttributes = "#attributes#">
    </CFIF>

    Now it is clear how the directory structure and the invoker.cfm page are inter-related. When an object is invoked, the object name passed to the invoker.cfm file is used as a folder name and the method is used as a file name. The attributes passed to the invoker.cfm page are subsequently passed onto the method file as a structure called stAttributes.

    The final file to analyze would be the display.cfm file in the news directory. This file displays news from the database. Yet there may be some attributes that were passed from the invoker that must now be available to display.cfm as normal attributes. Sure, we could have just left them as stAttributes, but this is not the normal ColdFusion way of dealing with attributes, so we change them back again. We do this by initializing the method. At the top of display.cfm is the initialization:

    display.cfm (for announcement object)
    <CFMODULE template="/tera/InitializeMethod.cfm">

    The initialize method template simply takes all of the attributes from the passed stAttributes structure and turns it back into the ATTRIBUTES structure. This code lives in every method page.

    The other thing that the InitializeMethod.cfm page does is it performs necessary security. Since all of these object folders are in the webroot (they don't have to be, but in this case they are), it is important to halt processing if someone opens them by themselves. Sure, it would be hard for a user to know what the directories are called and such, but it's better to be safe than sorry.

    The entire code for InitializeMethod.cfm looks like this:

    InitializeMethod.cfm (in IC or /tera/)
    <!--- security - this must be called as a custom tag --->
    <CFIF NOT isDefined("caller")>
    	<CFABORT>
    <CFELSE>
    <!--- make sure caller is a structure… otherwise they could have passed it in the URL --->
    	<CFIF NOT isStruct(caller)><CFABORT></CFIF>
    </cfif>
    
    <CFIF NOT StructKeyExists(caller.attributes,"stAttributes")>
    	<CFSET caller.attributes.stAttributes = StructNew()>
    </CFIF>
    
    <CFLOOP COLLECTION="#caller.attributes.stAttributes#" ITEM="i">
    	<CFSET 'caller.attributes.#i#' = caller.attributes.stAttributes[i]>	
    </CFLOOP>

    Once the method has been initialized, the code can run and the display method can do its thing with normal ATTRIBUTES instead of the stAttributes structure which was passed from the invoker.

    display.cfm (for announcement object)

    <CFMODULE template="/tera/initializeMethod.cfm">
    
    <CFQUERY NAME="request.Announcements" DATASOURCE="#request.dsn#">
    select * from announcements where announcementID = #attributes.announcementID#
    </CFQUERY>
    
    <CFOUTPUT  etc…

    Note that most methods do not get data and display it - most of them do one or the other. So you would typically call one method to get the info and another to show it. Also, note that this example named the query request.announcements. We use the REQUEST scope so we don't have to worry about scoping - the query is available to any page that calls this method. The only difference between this and the real world scenario is that the name is hardcoded here and in most cases, we allow our user to name the query.

    Either way, however, we want to use the request scope instead of the caller scope to get the query back. So in the real world, a getAnnouncements method would be called like this:

    Announcement.cfm (in docs)

    <CFMODULE 
    	TEMPLATE="/tera/invoker.cfm" 
    	OBJECT="getAnnouncement" 
    	METHOD="display"
    	AnnouncementID = "6"
    	r_query = "qGetAnnouncements">

    This version of announcement.cfm declares the name of the returned query (r_query attribute) as qGetAnnouncements. This name is then placed in the request scope so that it may be printed on the page.

    Announcement.cfm (in docs)
    <CFMODULE 
    	TEMPLATE="/tera/invoker.cfm" 
    	OBJECT="announcement" 
    	METHOD="display"
    	AnnouncementID = "6"
    	r_query = "qGetAnnouncements">
    
    <CFOUTPUT query = "request.qGetAnnouncements" etc…

    It may not be obvious how this query is returned with the name qGetAnnouncements, so we will not modify the display method to return the query as the name as it was passed in the attribute r_query.

    getAnnouncement.cfm (method for announcement object)

    <CFMODULE template="/tera/initializeMethod.cfm">
    
    <CFQUERY NAME="request.#attributes.r_query#" DATASOURCE="#request.dsn#">
    select * from announcements where announcementID = #attributes.announcementID#
    </CFQUERY>

    By naming the query "request.#attributes.r_query#" we are returning the results of the query in the request scope so that other pages can read them. Now it is up to you whether you want to print the query from the announcement.cfm page (as we just did in the example above) or create another method for announcements that displays them based on a query passed as an attribute. The full example of this could look like this:

    Announcement.cfm (in docs)
    <CFMODULE 
    	TEMPLATE="/tera/invoker.cfm" 
    	OBJECT="announcement" 
    	METHOD="getAnnouncement"
    	AnnouncementID = "6"
    	r_query = "qGetAnnouncements">
    
    <CFMODULE 
    	TEMPLATE="/tera/invoker.cfm" 
    	OBJECT="announcement" 
    	METHOD="display"
    	query = "#request.qGetAnnouncements#">
    
    getAnnouncement.cfm (method for announcement object)
    <CFMODULE template="/tera/initializeMethod.cfm">
    
    <CFQUERY NAME="request.#attributes.r_query#" DATASOURCE="#request.dsn#">
    select * from announcements where announcementID = #attributes.announcementID#
    </CFQUERY>
    
    display (method for announcement object)
    <CFMODULE template="/tera/initializeMethod.cfm">
    <CFOUTPUT query = "request.qGetAnnouncements" etc…

    The clear advantage of this type of methodology is that a different query could be retrieved using one method and displayed differently depending on the display method that is called.

    Content Management Using This Approach

    Content management is typically handled by administrators of a Web site who perform the following tasks:

    • Update information on the Web site quickly and easily without knowing HTML or CFML
    • Schedule content to be published to the Web site by selecting content and dates for publishing them
    • Change the look and feel of the Web site as needed by redesigning the site visually

    To perform these functions, the content of the Web site must be stored in a database. The database schema strongly matches that of the directory structure of the site. Looking at the announcement table, for example, it is a very typical relational table:



    To create a new announcement and assign it to a page, the user must be an admin user and they must also be in design mode. The design mode is something we will make up to allow admin users to create content for the site. The user can enter design mode by clicking on a special link that appears in the title of every page. Let's take a look:

    Every page has a header and a footer. The header and footer are methods in the page object (showHeader and showFooter would be good examples of methods here, although I just named them header and footer). To show the header, we simply invoke it the same way we did the announcements (using the invoker).

    Inside of the showHeader (or header) method, we will give admin users the ability to enter design mode with a link like this:

    Code from showHeader.cfm
    <CFIF session.ADMIN AND NOT session.design>
    	<CFOUTPUT><A HREF="#cgi.script_name#?design=1">design mode</A>
    <CFELSEIF session.ADMIN and session.design>
    	<CFOUTPUT><A HREF="#cgi.script_name#?design=0">design mode OFF</A>
    </CFIF>
    
    Code from application.cfm
    <CFPARAM NAME="design" DEFAULT="0">
    <CFIF session.ADMIN AND design>
    	<CFET session.design = 1>
    <CFELSEIF not design>
    	<CFSET session.design = 0>
    </CFIF>

    Now we can control whether or not the user is in this thing we called design mode, and we can give them links to turn the design mode on or off. If the user clicks on the design mode link the application.cfm will create a session variable for them showing they are in design mode. When they turn it off, the design mode will be shut off.

    We will look at the purpose of this design mode later. First we need to look at something more important!

    Alleviating the Locking Problem

    While we are on the subject of session variables, let me stray from the main topic for a moment and turn towards locking in ColdFusion. Our design mode uses SESSION variables. If you have ever built a ColdFusion application, you probably acquainted yourself with the <CFLOCK> tag. The idea of the <CFLOCK> tag is that it must be used to lock all shared variables in ColdFusion such as SESSION, APPLICATION, or CLIENT scopes. The reason for this has to do with the fact that ColdFusion is a multi-threaded server and two pages can run at the same time. If two pages run simultaneously, one page could set a SESSION variable while the other page simultaneously RESETS the same SESSION variables to other values. Without locking your shared variables, a large ColdFusion application will eventually fail.

    An easy answer to this problem is to dump the entire SESSION or APPLICATION scope into the REQUEST scope. ColdFusion does not 'share' the REQUEST scope between threads and it does not need to be locked. So by dumping all the shared variables to this scope, we can lock our variables once and then forget about it. Actually, we have to lock it twice: once in the application.cfm and once in the OnRequestEnd.cfm. Here is the code:

    Code from application.cfm
    <CFSET request.session =  structNew()>
    <CFLOCK scope="SESSION" timeout="30" type="READONLY">
        <CFLOOP COLLECTION="#session#" ITEM="i">
    		<CFSET request.session[i] = session[i]>
    	</cfloop>
    </CFLOCK>
    
    <CFSET request.application =  structNew()>
     <CFLOCK scope="APPLICATION" timeout="30" type="READONLY">
        <CFLOOP COLLECTION="#application#" ITEM="i">
    		<CFSET request.application[i] = application[i]>
    	</cfloop>
    </CFLOCK>

    Code from OnRequestEnd.cfm

    <CFLOCK scope="SESSION" timeout="30" type="EXCLUSIVE">
    	    <CFLOOP COLLECTION="#request.session#" ITEM="i">
    		<CFSET session[i]=request.session[i]>
    	</cfloop>
    </CFLOCK>
    
    <CFLOCK scope="APPLICATION" timeout="30" type="EXCLUSIVE">
    	    <CFLOOP COLLECTION="#request.application#" ITEM="i">
    		<CFSET application[i]=request.application[i] >
    	</cfloop>
    </CFLOCK>

    Now that we have dumped the session variables and the application variables into the REQUEST scope (and then returned them to their prospective scopes at the end of the request), we need not lock our shared variables ever again. So, from now on, instead of referencing session.designmode we will reference request.session.designmode.

    Before we move on from here, note that the SESSION and APPLICATION variables will be lost if the request does not finish entirely. If <CFLOCATION> is used at all, it will interrupt the request and the SESSION and APPLICATION scopes will not be refreshed in the OnRequestEnd.cfm. The answer to this is to create a separate page - I named mine closeSession.cfm - and <CFINCLUDE> it before you do any <CFLOCATION> procedure. The closeSession.cfm is an exact replica of the logic we have in OnRequestEnd.cfm above.

    Design Mode Explored

    When announcements show up in the announcement.cfm page, they do so through the display method. We already covered that. What we did not look at is that all display methods have links in them that allow administrators - who are in design mode - to modify the application.

    The following code is inside of the display method for announcements:

    Code from display.cfm (method for announcement object)

    <!--- this takes place SOMEPLACE on the page. This is not the entire page --->
    <A HREF="#attributes.url#" 
    onclick="return popup('admin.cfm?object=announcement&method=edit&id=#attributes.announcementID#')>
    click here to edit announcement
    </A>

    As you can see, this code references a JavaScript function called "popup," which must be in the header of your page. We put that code in the header method of our page object. The JavaScript looks like this:

    This logic needs to go in the header of the page:

    function open_popup(page) {
        window.open(page,'newConsole','width=700,resizable=yes,toolbar=no,
    location=yes,directories=yes,status=no,menubar=no,scrollbars=yes');
        return false;}	

    This code will open an admin window. In reality, I made the popup link in display.cfm a custom tag, and named it "grapple.cfm" which takes the url and text as attributes. Either way, it doesn't matter. The important part is that we are opening a page called admin.cfm and we are passing an object (announcement), and a method (edit), and an ID which is the announcementID.

    So what is going on in this popup admin window? Well, the admin window simply calls the method for the object that was sent to it in the URL. The method is edit and this method shows a form and allows the user to edit the announcement based on the ID that was passed.

    admin.cfm (in docs - appears as a popup window in design mode)
    
    <CFIF request.session.ADMIN AND request.session.DESIGN>
    <body onunload="opener.location.href = 
    opener.location.href;" ONLOAD="this.focus()">
    
    <CFIF LEN(TRIM(object))>
    	<CFMODULE 
    		TEMPLATE ="/tera/invoker.cfm" 
    		object="#object#" 
    		METHOD="#method#" 
    		ID="#id#">
    </CFIF>
    
    </body>
    </CFIF>

    So this admin page just calls the method and passes the ID along to it. Since all of my edit/admin/design methods seem to only require one ID (the thing I am editing) I made it inflexible to other attributes. In other words, we could have built this so that the admin.cfm page could pass other attributes along as well, but the popup window is now limited to one thing: the ID, which is always the primary key of the thing we are looking up and editing. In this case, that thing is an announcement.

    Notice that the JavaScript in the admin window will refresh the Web site over and over again (look at the onunload event in the body tag). This will allow your user to go back and see changes immediately and will help avoid confusion. Also, note that the window will aways get focus (look at the onload event in the body tag). I put that in because users kept minimizing the window and then wondering why it did not pop up the next time they went to edit some content.

    By now, you should understand how the basic content management works in the application. There is, however, one last topic to consider and that is how a page receives its menus look and feel.

    Page Data

    The last topic we will discuss is the look and feel of the Web site. Should menu items be static? Of course not! Menus change as the content changes. All of these changes should be handled through the same design mode capabilities that we focused on a few minutes ago.

    Let's look at the menu systems in TERA. The menu information in TERA are stored in two database tables: topMenu and sideMenu. The actual links in these tables are contained in a table called links. These tables are not completely normalized. True database experts would be appalled at my storing a list in the tables. It was, however, a quick solution to a complicated problem: ordering the appearance of links in each menu.

    Every page in the application is registered in the database. To that end, each page much have a unique name in this application. When a page runs, two things happen. First, the page gets its own information from the Page table to see what topMenu and SideMenu it has. Then it calls the display methods for those menus. The display methods can be as sophisticated as needed. In TERA, they are sophisticated enough to look at the PageID and highlight that link differently based on the page we are on. This is the reason for the highlightOn column in the link table.

    Instead of looking at both the top and the side menus, let's analyze the side menu only, since the two are basically the same idea.

    The menu code in Announcement.cfm (in doc)
    <!--- we already called a method called getPageData which returned the side menu 
    		for this page in request.stPage.sideMenuID --->
    
    <CFMODULE 
    	TEMPLATE="/tera/invoker.cfm"
     	object="sideMenu" 
    	METHOD="getMenuLinks"  
    	sideMenuID="#request.stPage.sideMenuID#"
    	r_aLinks = "aLinks">
    
    <CFMODULE 
    	TEMPLATE="/tera/invoker.cfm"
     	object="sideMenu" 
    	METHOD="showMenuLinks">
    
    getMenuLinks (method of sideMenu)
    
    <CFMODULE TEMPLATE="/tera/initializeMethod.cfm">
    
    <CFPARAM NAME="attributes.r_alinks" DEFAULT="aLinks">
    
    <CFQUERY NAME="qGetLinkIDList" DATASOURCE="#request.teraDSN#">
    select sideMenu.name, sideMenu.linkIDList from sideMenu
    where sideMenuID = #attributes.sideMenuID#
    </CFQUERY>
    
    <CFQUERY NAME="qGetLinkData" DATASOURCE="#request.teraDSN#">
    select * from link where linkID in (#valuelist(qGetLinkIDLIst.linkIDList)#) and type = 'side'
    </CFQUERY>
    	
    <!--- put 'em in the right order --->
    
    <CFSET stOrder = StructNew()>
    <CFLOOP FROM="1" TO="#qGetLinkData.recordcount#" INDEX="p">
    <CFSET stOrder[qGetLinkData.linkID[p]] = StructNew()>
    <CFSET stOrder[qGetLinkData.linkID[p]].linkID=qGetLinkData.linkID[p]>
    <CFSET stOrder[qGetLinkData.linkID[p]].name=qGetLinkData.name[p]>
    <CFSET stOrder[qGetLinkData.linkID[p]].url=qGetLinkData.url[p]>
    <CFSET stOrder[qGetLinkData.linkID[p]].highlightON=qGetLinkData.highlightON[p]>
    </CFLOOP>
    		
    <CFSET aTemp =ArrayNew(1)>
    
    <CFSET counter=1>
    <CFLOOP LIST="#valueList(qGetLinkIDList.linkIDList)#" INDEX="o">
    <CFSET aTemp[counter] = stOrder[o]>
    <CFSET counter=counter+1>
    </CFLOOP>
    	
    <CFSET 'request.#attributes.r_aLinks#' = aTemp>
    
    showMenuLinks.cfm (method of sideMenu)
    
    <CFMODULE TEMPLATE="/tera/initializeMethod.cfm">
    
    <CFIF request.session.ADMIN AND request.session.DESIGN>
    <A HREF="#attributes.url#" onclick="return popup('admin.cfm?object=sideMenu&method=edit&id=#request.stPage.sideMenuID#')>
    click here to edit the side menu
    </A>
    <!--- we don't look at the edit screens for these menu's but they just let you modify / reorder --->
    </CFIF>
    
    <CFIF NOT ArrayLen(request.alinks)>
    
    <BR>
    <!---  no links assigned --->
    
    <CFELSE>
    				
    <CFLOOP FROM="1" TO="#arrayLen(request.aLinks)#" INDEX="i">
    	<CFOUTPUT>
    	<TABLE border="0" cellpadding="1" cellspacing="0">
    	<TR>
    	<TD NOWRAP>
    	<A HREF="#request.alinks[i].url#">#request.alinks[i].name#</A>
    	</td>
    	</tr>
    	</table> 
      </CFOUTPUT>
     </CFLOOP>
    </CFIF>

    Conclusion

    This document has been an introductory guide to an alternative programming technique using ColdFusion. There are many different avenues that can be traveled using this technique and techniques similar to it. There is never one way to build an application, and this is especially true on the Web, where you often combine four or five different technologies such as JavaScript, HTML, server logic, and SQL to make any application work.

    discuss this topic to forum

    relation tutorial

    No relevant information

    New

    Hot