Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at cf.Objective() 2017 (Washington, D.C.) with: Mike Sprague
Ben Nadel at cf.Objective() 2017 (Washington, D.C.) with: Mike Sprague@spraguey )

Skin Spider : Pseudo Application.cfc Events : OnContentStart() And OnContentEnd()

By Ben Nadel on

To learn all about Skin Spider, click here.

To view the most updated application, click here.

To view the current code base, click here.

When we deal with the life cycle of a web page request, we can think about it in terms of:

  • Pre Page Processing
  • Page Processing
  • Post Page Processing

ColdFusion MX 7's Application.cfc does a really good job of giving us hooks into these different page events. The OnRequestStart() and OnRequest() events allow us to perform our pre page processing. The OnRequest() event allows us to handle our page processing (primarily). And, the OnRequestEnd() event allows us to handle our post page processing.

This works really well and is a very nice improvement over the previous Application.cfm methodology. I feel, though, that this event model doesn't quite jive 100% with how I see a page's life cycle. If you look at how the Application.cfc OnRequest() event works, it just includes the requested template (or a alternate template of your choice). Seeing this, we could rework the above life cycle to be:

  • Pre Page Processing
  • Post Page Processing

This means that all global application events must be done outside of the requested template. This doesn't give me the level of control that I need to have over the data. Once I am processing the requested template, there is a lot of template-specific stuff that I do, but there are also global events that I want to occur as well. I like to break down my "requested template processing" into five pseudo-events:

    • Pre Page Processing
    • Pre Content Processing
    • Content Processing
    • Post Content Processing
    • Post Page Processing

Note that this "pre/post page processing" is a template-specific item, not a global application item (such as the event defined in the Application.cfc's ColdFusion component).

Of the sub-events listed above, pre page processing, content processing, and post page processing are inline. These are the portions of the page that I include in the template (such as form processing and html display). The pre CONTENT processing and post CONTENT processing are events that I want to be triggered globally. These are events that provide final hooks just before and just after the content it sent to the browser (we will discuss this importance later).

To set these methods up, I have added two more event methods to the Application.cfc ColdFusion component:

  • OnContentStart()
  • OnContentEnd()

The OnContentStart() event method gets fired right before the first (X)HTML element is sent to the browser. The OnContentEnd() event gets fired right after the last (X)HTML element is sent to the browser. Now, since these events are not built into the Application.cfc ColdFusion component, we have to hack it a little to make this work. If you look in my header and footer files, you will see that I am explicitly calling these events before and after the (X)HTML content is flushed:

_header_standard.cfm and _header_pop.cfm fire the OnContentStart() event. _footer_standard.cfm and _footer_pop.cfm fire the OnContentEnd() event.

If you look at those templates, you will see that they are calling the event through the variable REQUEST.ApplicationCFC. If you look in the Application.cfc ColdFusion component, you will see that in the OnRequestStart() event, I am storing the THIS pointer (pointer to the Application.cfc instance) into the REQUEST.ApplicationCFC variable. I do this because I cannot see any other natural way to refer to the page's Application.cfc instance.

Remember, since a new Application.cfc instance is created for every page call, we never have to worry about two requests having the same reference. Also because of this, we want to store this pointer in the REQUEST scope, which is also created new for each page request (as opposed to persisting it in the SESSION or APPLICATION scopes).

So, what's the point of these extra events and shenanigans that we have to go through to make them fire at the right time? It has to do with data and the context in which it is being used. While the current template is being processed, the context of data changes. In the beginning, the data is being used on the sever and being persisted, validated, manipulated and all that good jazz. But once that is done, the data is being used in conjunction with (X)HTML (such as displaying a form with pre-populated data). Now, while both of these "contexts" actually occur on the server, they have different implications.

Let's take a look at the quote character ("), which is where this all really started. On the server, we don't care about quotes; they are just another character, no different to us that the letter "a" or the carriage return: an ascii character, plain and simple. Because of that, we don't want to do anything special to it. We don't want it escaped (") - on the server, escaping quotes has no relevance and, we certainly don't want to save escaped quotes in the database as that's not the actual data that was entered - how many of you put escaped quotes into a form field?? None of us, escaping it is not our intent! We don't want to store corrupted data in the database.

This is why in my Application.cfc's OnRequestStart() method, I loop over the FORM scope and unescape all quotes. This just helps to ensure that no escaped quote will be in the FORM scope by the time it get validated and persisted. The problem with this is that once we are done with the pre-page processing, we have to display (X)HTML. And, as I am sure most of you know, you can't put a standard quote in a quoted attribute of a form field or the form field will break. To remedy this, we have to escape all quotes prior to their use within the (X)HTML of the web page.

But where to we do this escaping? In the previous iteration, I was doing this at every point that I retrieved data from the data base (ex. when initializing form data for add / edit pages). This is a global rule though, we don't need to go through so much repeated effort. That's why, if you look in the Application.cfc's OnContentStart() event method, you will see that I am escaping all quotes. This way, we can be assured that all data in the FORM and ATTRIBUTES scope is web-ready before we start sending any content to the browser.

You will see that I have removed the inline escaping from the edit_tag.cfm, edit_video.cfm, and spider.cfm pages. It's no longer needed thanks to the global OnContentStart() event. I have, however, left in the comments so you can see where it once was.

Now, you might ask yourself "But wait, when you submit a form, there is no automatic escaping of quotes - why do you unescape quotes during OnRequestStart()?" Excellent question. It's just something that I have found safe to do over the years. You should be able to remove it and it will work fine. You still, however, need to escape quotes prior to web page display, so all the above is still completely valid.

Reader Comments