CFTHREAD is Sweet!

Just had my first occasion to use CFTHREAD in an app, and it's nice.  As part of rendering an image gallery, I wanted to ensure that the thumbnails (which are generated server-side) exist so that they the user doesn't have to wait for them to be auto-generated as they're requested.  Unfortunately, generating the thumbnails is relatively slow, so I didn't want to do it as part of the page execution.

However, using CFTHREAD I can just put my thumbnail generation loop inside a "throwaway" thread that will execute in the background until it's finished, hopefully after the page goes back to the browser, but before the browser gets very far into requesting the thumbnails.  That way most of the thumbnails should be pregenerated by the time they're requested.

This is nothing that you couldn't do with the Async CFML Event Gateway (or even a throwaway Ajax call), but the CFTHREAD method is much cleaner than either one.  It also provides the capability to rejoin the threads with the main page thread if you need to do that, which is doubly nice, and something that you can't do with the async gateway (nor obviously an Ajax request, as that happens after the generated output is client-side).

Wednesday Contest Solution (pt. 1)

Since I had a whopping zero takers for my contest, I'm thinking it won't be a repeat event. Here's my solution for the first portion of the challenge:

<cfif NOT isDefined("attributes")>
  <cfset attributes = structNew() />
  <cfset structAppend(attributes, form, false) />
  <cfset structAppend(attributes, url, false) />
</cfif>
<cfparam name="attributes.width" default="100" />
<cfparam name="attributes.height" default="100" />
<cfparam name="attributes.backgroundColor" default="f7f7f7" />
<cfparam name="attributes.borderColor" default="cccccc" />
<cfparam name="attributes.textColor" default="990000" />
<cfparam name="attributes.text" default="Hello!" />

<cfset image = imageNew("", attributes.width - 2, attributes.height - 2, "rgb", attributes.backgroundColor) />
<cfset imageAddBorder(image, 1, attributes.borderColor) />

<cfset imageSetAntialiasing(image, "on") />
<cfset imageSetDrawingColor(image, attributes.textColor) />

<cfset graphics = imageGetBufferedImage(image).getGraphics() />

<cfset bounds = graphics.getFontMetrics().getStringBounds(attributes.text, graphics) />
<cfset imageDrawText(
  image,
  attributes.text,
  (attributes.width - bounds.getWidth()) / 2,
  (attributes.height + bounds.getHeight()) / 2
) />

<cfimage action="writeToBrowser"
  source="#image#" />

I'm withholding the extra credit solution until tomorrow, in case anyone wants to take a crack at it.

Contest Update

For those of you who missed it, I posted a little contest this morning and have gotten a whopping zero submissions!  There is still time to submit a solution, but be quick, as my solution (just the base, not extra credit) will be published tomorrow morning.  I'll be posting a full solution (with the extra credit) Friday morning.  If you tried and gave up, you're smart.  ;)  It's really circuitous for how simple it seems on the surface, and way more effort than it ought to be.  That being said, I'm quite interested in how other people have solved, or would approach solving, the problem.

And for my wife's sake, I didn't do this so someone else could do my work for me.  I have my solutions already; I did it because I'm interested in other approaches.

Wednesday Contest

Today's contest uses the new image manipulation routines in ColdFusion 8. The objective is to take the custom tag/template skeleton below and add to it so that it'll generate the following image:

Hello Tile

The tag/template can be invoked as a custom tag, or directly on the URL with URL and/or form parameters to set it's values. Just save it off to your CF8 instance and replace the "add your stuff here" with your code to build out the 'image' variable. Note that you have to center the text the "right" way, not just by figuring out the proper pixel offset from the top and left edges for the hard coded defaults. You can test this by supplying alternate text to render on the URL.

<cfif NOT isDefined("attributes")>
  <cfset attributes = structNew() />
  <cfset structAppend(attributes, form, false) />
  <cfset structAppend(attributes, url, false) />
</cfif>
<cfparam name="attributes.width" default="100" />
<cfparam name="attributes.height" default="100" />
<cfparam name="attributes.backgroundColor" default="f7f7f7" />
<cfparam name="attributes.borderColor" default="cccccc" />
<cfparam name="attributes.textColor" default="990000" />
<cfparam name="attributes.text" default="Hello!" />

<cfset image = ... add your stuff here

<cfimage action="writeToBrowser"
  source="#image#" />

Post your solutions in a comment. All correct submissions will get a congratulatory mention in my next post, and all submissions (correct or not) will get a one-line shout-out to whomever (generated by your own code ;), so make sure you supply your shout-out, name, and URL in your submission.

For extra credit, add this block below the last CFPARAM tag, pass the fontArgs struct as the fifth parameter to imageDrawText, and ensure everything still renders correctly.

<cfparam name="attributes.textSize" default="14" />
<cfparam name="attributes.textStyle" default="bolditalic" />
<cfparam name="attributes.textFont" default="courier" />

<cfset fontArgs = structNew() />
<cfif structKeyExists(attributes, "textSize") AND isNumeric(attributes.textSize)>
  <cfset fontArgs["size"] = attributes.textSize />
</cfif>
<cfif structKeyExists(attributes, "textStyle") AND len(trim(attributes.textStyle)) GT 0>
  <cfset fontArgs["style"] = attributes.textStyle />
</cfif>
<cfif structKeyExists(attributes, "textFont") AND len(trim(attributes.textFont)) GT 0>
  <cfset fontArgs["font"] = attributes.textFont />
</cfif>

Scott Adams on Iran

Scott Adams (of Dilbert fame) posted a very nice piece on Iran (specifically President Ahmadinejad) on his blog.  Definitely worth a read.

My FB3 Lite

I was talking to Sandy Clark back at CFUNITED about how I still use a stripped-down FB3 for a lot of projects. She said I should publicize it, so here goes. At it's heart, it's a toolset for designing something that resembles a single-circuit FB3 application, but with some of the niceties (like DO) of FB4/5. I don't have a sample application to show at the moment, but I thought I'd share the framework itself (all 61 lines of it). It's inline at the end of the post, along with a sample switch file.

To use it, you just need to save the code as 'index.cfm' (or whatever) create a 'fbx_Switch.cfm' file in the same directory that has a CFSWITCH block that switches on 'attributes.currentFuseaction'. There are two mandatory CFCASEs it must contain: onRequestStart and onRequestEnd. The do exactly what you'd expect. Finally, the default fuseaction is named "__default_fuseaction__" (two underscores on the ends), so you'll want to define a CFCASE with that value as well, probably multi-aliased to something a little more friendly (like "home"). That's enough to get code executing.

The "tooling" that the framework exposes is quite simple, and I'll sum it up in a few bullet points:

  • a 'self' variable, which is set to "?#fuseactionVariable#=" and can be used for links (e.g. <a href="#self##xfa.logout#">)
  • an 'attributes.originalFuseaction' variable storing the requested fuseaction (or the default if none was specified)
  • an 'attributes.currentFuseaction' variable storing the currently executing fuseaction
  • a 'do' UDF that accepts a fuseaction to execute, and an optional variable name to put the execution output into
  • an 'include' UDF that accepts a template root to execute, and an optional variable name to put the execution output into
  • a 'location' UDF that accepts a destination URL and does a redirect via a HTTP Location header

Content variables for both the 'do' and the 'include' UDFs are append-only, so if you want to overwrite, you have to manually do it with a CFSET in your switch. Also as you'd expect, this is single-directory centric; index.cfm and fbx_Switch.cfm must be in the same directory, and includes must be pathed relative to that directory.

Why did I build such a framework? Mostly because neither FB3 nor FB5 fits the bill for small applications. Both are a bit on the heavy side, with FB5 being the heavier of the two. But at the same time, they both provide some really nice features (again, FB5 providing a larger number). So I wanted the best of both worlds, and I think I got it. My biggest gripe is that the single-directory nature makes it hard to clearly delineate the app from the framework, but that's a relatively minor issue I've found.

While I think any smaller application could use the framework without too many complaints, it does lend itself to apps where the total number of fuseactions is fairly small (duh), you don't want to do MVC inside the front controller (i.e. you want to use a separate backend), you want the dynamic assembly of FB5 (content variables, DOs and dynamic DOs, etc.), and you prefer to do your controller coding in CFML.

Without further ado, here's the code. I'll admit it seems rather dense, particularly on the web without syntax highlighting. But it's really pretty straightforward, and you probably won't ever need to open the file after you save it if you're using it.

<cfsetting enablecfoutputonly="true" />

<cfset fuseactionVariable = "do" />
<cfset defaultFuseaction = "__default_fuseaction__" />
<cfset self = "?#fuseactionVariable#=" />

<cfif NOT isDefined("attributes") OR NOT isStruct(attributes)>
  <cfset attributes = structNew() />
</cfif>
<cfset structAppend(attributes, url) />
<cfset structAppend(attributes, form) />

<cffunction name="do">
  <cfargument name="fuseaction" type="string" required="true" />
  <cfargument name="contentVariable" type="string" required="false" />
  <cfset var oldFuseaction = attributes.currentFuseaction />
  <cfset attributes.currentFuseaction = fuseaction />
  <cfif structKeyExists(arguments, "contentVariable")>
    <cfparam name="#contentVariable#" default="" />
    <cfsavecontent variable="#contentVariable#"><cfoutput>#variables[contentVariable]#</cfoutput>
      <cfinclude template="fbx_Switch.cfm" />
    </cfsavecontent>
  <cfelse>
    <cfinclude template="fbx_Switch.cfm" />
  </cfif>
  <cfset attributes.currentFuseaction = oldFuseaction />
</cffunction>

<cffunction name="include">
  <cfargument name="template" type="string" required="true" />
  <cfargument name="contentVariable" type="string" required="false" />
  <cfif listFind("cfm,cfml,htm,html", listLast(template, ".")) EQ 0>
    <cfset template = template & ".cfm" />
  </cfif>
  <cfif structKeyExists(arguments, "contentVariable")>
    <cfparam name="#contentVariable#" default="" />
    <cfsavecontent variable="#contentVariable#"><cfoutput>#variables[contentVariable]#</cfoutput>
      <cfinclude template="#template#" />
    </cfsavecontent>
  <cfelse>
    <cfinclude template="#template#" />
  </cfif>
</cffunction>

<cffunction name="location" output="false">
  <cfargument name="destUrl" type="string" required="true" />
  <cfheader statuscode="302" statustext="Object Temporarily Moved" />
  <cfheader name="Location" value="#destUrl#" />
</cffunction>

<cfparam name="attributes.#fuseactionVariable#" default="" />
<cfset attributes.fuseaction = attributes[fuseactionVariable] />
<cfif len(trim(attributes.fuseaction)) EQ 0>
  <cfset attributes.fuseaction = defaultFuseaction />
</cfif>
<cfset attributes.originalFuseaction = attributes.fuseaction />
<cfset attributes.currentFuseaction = attributes.fuseaction />

<cfset do("onRequestStart") />
<cfset do(attributes.currentFuseaction) />
<cfset do("onRequestEnd") />

And the sample fbx_Switch.cfm:

<cfswitch expression="#attributes.currentFuseaction#">

  <!--- lifecycle fuseactions --->

  <cfcase value="onRequestStart">
  </cfcase>

  <cfcase value="onRequestEnd">
  </cfcase>

  <!--- normal fuseactions --->

  <cfcase value="home,__default_fuseaction__">
    <cfset include("dsp_home", "bodyContent") />
    <cfset do("lay_default") />
  </cfcase>

  <!--- private fuseactions --->

  <cfcase value="lay_default">
    <cfset xfa.home = "home" />
    <cfset xfa.login = "loginForm" />
    <cfset include("lay_default") />
  </cfcase>

  <cfdefaultcase>
    <cfthrow type="IllegalFuseactionException"
      message="The specified fuseaction (#attributes.currentFuseaction#) is unknown." />
  </cfdefaultcase>

</cfswitch>

Edit: Here's a link to the project page.

More on Prototype Templates

So it turns out I'm a a bit silly (duh).  My previous post on Templates was all well and good, but it didn't consider IE6.  IE6 loudly complains if you have substitution strings in 'id' attributes, so if you need to do that (usually the case), you have to use the TEXTAREA hack I listed.  Which is a bummer, but such is life.

Inline Prototype Templates

If you've ever used Prototype's Templates, you'll know how powerful they are. If you haven't tried them, you should. One thing that makes them even more powerful is creating them from in-your-page HTML, rather than as JS Strings. This lets your designer build templates in raw HTML, and then you can transparently suck them into Template objects for use in dynamic rendering. At it's simplest:

<div id="myTemplate"><h1>#{text}</h1></div>
<script type="text/javascript">
  t = new Template($("myTemplate").innerHTML);
</script>

There are a couple gotchas with this technique, however.

The first is that your markup must be valid (X)HTML in the context of your document itself. So if you need a template for a table row, you have to put the markup for that template inside a "throwaway" table. If you can't package your template as valid markup, then you need to supply it into the DOM as the value of a TEXTAREA element, and extract it from the TEXTAREA element's value (using $F()) . This is a slightly inferior solution because you're not right in the DOM at that point, but only marginally.

When can't you package a template as valid markup? Here's a simplified example from an app I was working on today:

<textarea id="template_tableSkeleton">
<table>
<thead>
  <tr>#{headers}</tr>
</thead>
<tbody>
  #{rows}
</tbody>
</table>
</textarea>

The problem here is that you can't put content (the substitutions) directly in TR or TBODY elements. If you put that markup directly in the page, you'll end up with the substitutions in front of the opening TABLE tag, which is definitely not what you want. Using the TEXTAREA makes the browser treat it as raw text, so you don't get the rendering-based flip-flop.

The second one is a bit more subtle, and has to do with links. Take a look at this example:

<div id="myTemplate"><a href="#{href}">#{text}</a></div>
<script type="text/javascript">
  t = new Template($("myTemplate").innerHTML);
</script>

The problem is that the 'href' attribute of an A element in the DOM is url-escaped as part of parsing.  At least that's what the symptoms seem to indicate.  The template still contains the '#{text}' substitution, but it no longer contains the '#{href}' substitution, instead it contains the string '#%7Bhref%7D' (i.e. the braces were url-escaped). You could get around it by using the TEXTAREA trick, but you can solve it more directly by doing a 'replace' on the template string:

<div id="myTemplate"><a href="#{href}">#{text}</a></div>
<script type="text/javascript">
  t = new Template($("myTemplate").innerHTML.replace(/#%7B([a-zA-Z0-9_-]+)%7D/gi, "#{$1}"));
</script>

That will unescape any url-escaped substitutions in the template, allowing it to again render correctly.

These couple tricks apply to whatever templating toolkit you're using, because they're outside the templating engine itself.  Prototype's is the one that I'm most familiar with, but there are others.  TrimPath is one example with a far richer templating language (conditionals, loops, macros, inline script, etc.).  Overkill in many cases, especially if you're already using Prototype, but very rich if you need the flexibility.

CF8 and Batik (for SVG)

I just discovered that CF8 ships with a more complete Batik than CF7 did. Out of the box it's capable of transcoding SVG graphics to PNGs, which CF7's implementation wasn't capable of doing. With CF7 you had to move the partial Batik that came bundled and replace it with a full version to get transcoding to work, but no more. I'm not sure what CF8 uses it for, but CF7 appeared to use it for CFFORM stuff. Perhaps that's been extended and requires a full Batik, or maybe something else does. My initial thought was maybe the new Image libraries supported SVG, but that doesn't seem to be the case.

"Why SVG?", you ask? Because CFCHART is remarkably inflexible (and rather clumsy to boot) to the point that I wrote my own charting engine. It's hardly a CFCHART replacement, but far better suited to some of the charts that I need to produce. I chose SVG (several years ago, mind you) because it was widely supported and it's all text based, which makes generating it with CF a breeze. It also works as-is, or can be transcoded to a "normal" graphic (though you lose the vector base if you go to a raster format) for easier consumption.

Edit: Rob asked about the charts in the comments, so here's an example chart: capacity.png.  It doesn't really represent the full capabilities (area plots, multiple y-axes, etc.), but you get the idea.  The x-axis is labeled with age in days, with zero being midnight this morning.  If you've ever seen/used RRDTool (often paired with MRTG) the idea is similar, except that my database isn't round robin, it's historical, so I can change the time period on my dashboards and get charts back as far as I have data, if I want them.  Here's the same chart with data back to the beginning: capacity2.png.  As you can see, tracking of the 'imageCapacity' series started later (by about six months) than tracking 'totalImageCount'.  And no, those execution times (in the lower right) aren't representative – I grabbed these after a fresh restart so caches were empty.

CF8 Mail Spool Directory

After a couple days of CF8, I noticed I wasn't getting the emails I was supposed to be getting. Turns out the CF8 installer, at least in multi-server mode, doesn't set the right permissions on the /WEB-INF/cfusion/Mail subdirectories, so CF can't write out the mail spool files. Flipping them to the right group and allowing group write access solved the issue. I seem to recall having to do that with certain installations of CF7 and CF6.1 as well, but I couldn't tell you what the common the thread was (or if it existed at all).