Peter Bell has been blogging lately about page views and peripheral modules and it's got me thinking about what's required for a page to properly function. So far, these are the simple laws that I have come up with:
Law One: A page must NOT do any extra processing!. (This law can be bent, but make it minimal). Just as we don't return unused columns from a database, a page should never process any data or retrieve any data whose eventual display has not yet been decided. For example, if a page is not 100% sure that a "related items" module will be displayed, then those related items should not be pulled from the database.
Law Two: Any code that produces page output (HTML) should allow the ColdFusion CFFlush tag to function normally. The ONLY exceptions to this is a page that returns output not directly used (as in AJAX or for a web service) or a chunk of content that is being "buffered" for later manipulation (such as stripping out extra white space before being flushed to the browser).
That's it. That's all that is required for a page to function. Of course, there are other things that can be done make sure a page runs optimally, however, without the above two features, the page, in my opinion, is not functioning properly.
Let's examine a scenario: the classic data form that a user fills out and submits back to the server. The form processing can result in two action; result one, the form data is valid, used, and the user is CFLocated to another page. Result two, the form data is not valid and the page re-renders the form allowing the user to update their data.
In this scenario, the outcome of "which page to eventually render," hinges singularly on whether or not the form data is valid. This means that until the form has finished processing, no modules on the page are destined to be displayed. According to my first law, that means that until the form has finished processing, no other data for the page can be processed or retrieved (unless it is required, such as for security and page flow).
Now, let's take a look at Law Two, the CFFlush requirement. CFFlush is a powerful little tag that allows page performance and rendering to be optimized for the end user's perception. This has a huge impact on the way page modules are strung together. Some frameworks have a tendency to store module content in variables and the render pages from a top-down, tail-ended bulk rendering approach (rendering a page template that then includes pre-processed module output). This clearly breaks Law Two. For page to render properly, the page must be rendered in top-down order AND in a streaming fashion, not in tail-ended bulk rendering. This will allow CFFlush to be used without it affecting the rest of the page processing.
The requirement of CFFlush has a different type of impact: the late phase exception. Image you have a page that has a bunch of modules, the middle of which is some sort of item detail. Now imagine, someone requests a item via an invalid identifier. As a result, the page must perform a CFLocation to a different page - we cannot display a detail page without the associated item (perhaps we need to CFLocate to a list page or a search form).
To accommodate this, no content can be flushed to the browser before the possibility of a late phase exception. This means that most all data retrieval / validation must be done before any page rendering has taken place. BUT, this also touches on Law One, no excess data retrieval or processing. This forces all modules that might create late phase exceptions to be processed first. This means that all peripheral modules MUST be processed AFTER primary modules that have absolute data requirements (regardless of physical ordering on the rendered page).
This presents us with a very tricky situation! We cannot process a page's data in a purely top-down fashion (might violate Law One). This forces us to select a Primary Action for any given page - an action, whose data requirements dictate the way in which a page is rendered (or ultimately not rendered). Of course, this law must be bent a bit; if you have multiple pieces of data that are required, you cannot always get them at the same time and therefore a certain amount of extra data retrieval must be performed.
So how do we handle all of this? I do not have a great solution, but I have one that I use rather successfully. It requires that each page have a primary action or function. The data for that page module is processed. Right before that module is rendered, I render the top of the page (since we know the page WILL be rendered at this point). Then right after the main module is rendered, I render the bottom of the page. You probably recognize this methodology as the page header / footer methodology:
<!--- Pre page processing is done. Also all data for thie main module is retrieved and processed. This might be done at the top of a file or in a separate action file or in controllers. This is based on your framework. ---> <!--- // .... // ----> <!--- Include page header. ---> <cfinclude template="./header.cfm" /> <p> This is the main module for this page. </p> <!--- Include page footer. ---> <cfinclude template="./footer.cfm" />
This approach satisfies both laws. No extra processing is done since the main page module is processed first. CFFlush will continue to work as the content is rendered in a top-down, streaming fashion (header, then main module, then footer).
What this approach is not very satisfactory at is the decision regarding which of the other page modules will be rendered and in what way they will be rendered. Take for instance, a "Related Items" modules. In the above approach, there would have to be some sort of conditional in the footer.cfm ColdFusion template that includes the related items module and passes in the appropriate data. This can make for a hard to read and hard to maintain page template.
The trick is to be able to make the page rendering "path" more dynamic. I have fooled around with a page object to which you can then add page module file paths (that the header and footer templates will CFInclude) and this has been somewhat satisfactory. I am still not fully satisfied with it. Nothing I have tried feels wholey natural yet.
This method, however, is a bit hard coded. If you wanted to make your templates more dynamic... I have NO idea. If you have a good solution that is dynamic and satisfies both of the above laws please let me know!
Want to use code from this post? Check out the license.