Using FrameworkOne (FW/1) Layouts To Strip Whitespace In Lucee CFML 5.3.7.47
At InVision, we use FrameworkOne (FW/1) as our ColdFusion / CFML web application framework. With FW/1, you can define a Controller, a collection of Views, and a Layout for a given feature-set. The Views get rendered and then "rolled up" into the Layout (optionally) at which point they are served to the client. Yesterday, I came up with a fun use-case for Layouts - I had to generate a View that had a lot of data on it (it was a report). So, in an effort to minimize the number of bytes that I was sending over the network, and to minimize the client-side DOM (Document Object Model) structure, I used the FW/1 Layout to strip out whitespace from the response. I had never used FW/1 in this way before; so, I thought it might make for an interesting demo in Lucee CFML 5.3.7.47.
With FW/1, each "subsystem" - which is a macro-organizational unit within the framework - has a directory structure that looks like this:
- Subsystem/
- controllers/
my_feature.cfc
- layouts/
my_feature.cfm
- views/
- my_feature/
default.cfm
- my_feature/
- controllers/
FW/1 is a convention-based framework, which means that the names of the various files and directories are used to auto-wire the response composition. In this case, the my_feature.cfc
is the Controller whose default method is defult()
. This method is automatically wired into the default.cfm
View, which is in turn, automatically rolled-up into the my_feature.cfm
Layout.
When a View is being merged into a Layout, it is exposed to the Layout template as the body
variable. This allows the Layout to provide the overall HTML structure and then just interpolate the View(s) using #body#
:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>
A Simple FW/1 Layout Template
</title>
</head>
<body>
<!--- Embed the current View output into the Layout. --->
<cfoutput> #body# </cfoutput>
</body>
</html>
Yesterday, in my case, the report that I was generating didn't really have a Layout. Or, rather, the layout HTML was embedded directly within the View and I didn't need to have an additional Layout template to wrap the View. With FW/1 you can tell the request processing to skip the Layout by setting request.layout
to false
:
<!--- DO NOT WRAP THIS VIEW IN A LAYOUT. --->
<cfset request.layout = false />
<cfoutput>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>
A FW/1 View That Does Not Need A Layout
</title>
</head>
<body>
<!--- Lots of REPORT GENERATION LOGIC. --->
<!--- Lots of REPORT GENERATION LOGIC. --->
<!--- Lots of REPORT GENERATION LOGIC. --->
<!--- Lots of REPORT GENERATION LOGIC. --->
<!--- Lots of REPORT GENERATION LOGIC. --->
</body>
</html>
</cfoutput>
The reporting View that I was generating was several megabytes in size. As such, I figured that any whitespace that I could remove as part of the request processing might be a win. Yes, GZIP should handle some compression; but, with the size of the View being so large, I figured that every little improvement might help the transfer and subsequent rendering time.
So, instead of having the View be "self contained", I removed the request.layout
flag and, instead, wired it into a Layout that looked like this:
<!--- This is a stand-alone layout (even layouts can be nested in FW/1). --->
<cfset request.layout = false />
<!--- Reset the output buffer. --->
<cfcontent type="text/html; charset=utf-8" />
<!---
Since this layout is going to produce a LARGE HTML payload, let's strip out as much
leading whitespace as possible. This should reduce both the transfer time over the
wire and the size of the subsequent DOM structure that is parsed by the browser.
--->
<cfoutput>
<!---
Using a multi-line Regular Expression to strip any "space characters" from the
beginning of each line.
--->
#body.reReplace( "(?m)^\s+", "", "all" )#
</cfoutput>
Remember, within a FW/1 Layout, the body
variable contains the rendered View markup. And, in this case, we're not just embedding the View within the Layout - we're modifying the View as part of the interpolation. Specifically, we're using Regular Expressions (RegEx), in multi-line pattern matching mode, to remove whitespace characters from the front of every newline in the View markup before we nested it within the Layout markup.
Now, if I render a FW/1 View with the given Layout, I get the following HTML response:
As you can see, all leading whitespace characters have been stripped from every line in the document. You gotta love Regular Expressions, baby!
I am sure there are other ways to accomplish this with FrameworkOne (FW/1). To be honest, I know only enough about the framework to get my work done; I am - by no stretch of the imagination - an expert on FW/1 application development. That said, I thought this a fun little "hack".
Want to use code from this post? Check out the license.
Reader Comments
That's so cool! I use CFWheels and this concept would totally work with it too. Why not also remove the new line characters, joining all the lines? Love how you think outside the box! We'll done!
@Chris,
Thanks! always get a bit nervous about removing the newline characters because I think I might accidentally join two strings together than need a "space" between them. So I figure as long as I need one white-space character to separate tokens, I might as well make it the "newline" so that the view-source is still somewhat readable.
I tried looking through the FW/1 docs to see if there was some sort of "post view" hook, where I might be able to do this more globally based on a
rc
variable. Imagine something like being able to set:rc.removeViewIndentation = true;
... somewhere and then it would just magically work for that request.
Though, now that I say that, maybe there is some way that I can keep nesting the layout so that there is always a "remove whitespace layout" that I can wrap any other layout into based on some variable.
I know enough FW/1 to "get the job done". But, I'm not really familiar with all the ins-and-outs of the features.
In CFWheels, we can
set('removeViewIndentation')
andget('removeViewIndentation)
any variable we want to be accessed globally. Perhaps FW/1 has something similar. They also roll their templates up, one being included into the next until it reached the top level layout. I'd imagine your top level layout couldget('removeViewIndentation')
and remove the white space (or not) depending.@Chris,
Yeah, in FW/1 you can just keep jamming stuff in the
rc
scope (I think it stands for "Request Context" or "Request Collection"). Therc
scope is available to all the controllers, views, and layouts. I tried poking around in the docs, and it does look like you can set a site-wide layout, which - to your point - you can probably just read from therc
scope there and apply some special logic.It's gonna be a nice little trick to have in my back pocket!