Sunday, 30 March 2014

How about this for savecontent?

G'day:
The approach that CFML has taken for the CFScript implementation of <cfsavecontent> has always annoyed me, because it doesn't follow the normal syntax for doing processing and returning a result. Plus there was the need to add a new syntax construct as well. It's just occurred to me that this was not necessary. A simple function would have done the trick.


To contextualise things, let's have a look at the <cfsavecontent> tag:

<cfset days = ["Rāhina","Rātū","Rāapa","Rāpare","Rāmere","Rāhoroi","Rātapu"]>
<cfsavecontent variable="contentViaTags">
<ul>
<cfloop index="day" array="#days#">
    <cfoutput><li>#day#</li></cfoutput>
</cfloop>
</ul>
</cfsavecontent>
<cfoutput>#contentViaTags#</cfoutput>

I'm just capturing the output from some simple CFML processing there, and then outputting it. It's a dumb usage of <cfsavecontent>, but it demonstrates it OK. The syntax here is entirely reasonable, and completely clear as to what's going on. It's a handy tag.

ColdFusion and Railo had already both had a go at implementing this for CFScript. And both failed (well: they came up with the same solution):

savecontent variable="contentViaScript" {
    writeOutput(days.reduce(function(previousResult, thisValue){
        return previousResult & "<li>#thisValue#</li>";
    },"<ul>") & "</ul>");
}
writeOutput(contentViaScript);

The problem here is twofold.
  1. CFML syntax for setting a variable to be the result of some processing is:
    variable = process
    Not:
    process variable="nameOfVariable"
    So it's at odds with out CFML works to approach functionality that way.
  2. We need to add this new {} syntax to enclose the code we're saving the output of. This is a new syntax construct implemented solely to emulate opening/closing tags. This is script code, so we should not be thinking in terms of opening and closing tags.
So that syntax is a loser IMO.

ColdFusion 11 has just managed to make this worse. Now we're going back to basically using tags:

cfsavecontent(variable="contentViaScriptNew") {
    writeOutput("<ul>");
    days.each(function(v){
        writeOutput("<li>#v#</li>");
    });
    writeOutput("</ul>");
}
writeOutput(contentViaScriptNew);

It also means we have two separate - and mostly the same but slightly different - constructs for doing a savecontent operation in CFScript. This is a double fail. How long will it be before we start forgetting which syntax takes the parentheses and the commas, and which just takes spaces?

But this is what's just occurred to me today. We never needed any special syntax at all to achieve savecontent in CFScript:

contentViaFunction = savecontent(function(){
    var decoratedDays = days.map(function(v){
        return "<li>#v#</li>";
    });
    writeOutput("<ul>#decoratedDays.toList('')#</ul>");
});
writeOutput(contentViaFunction);

It's just a function call. A plain old function call. We call the savecontent() function, it takes a callback which contains the code which we want to capture the output of, and returns that in its return value.

Isn't that a better approach than messing about with new syntax and weirdo ways of assigning values?

What's more... the implementation is piss-easy. It's just this:

function saveContent(required function content){
    savecontent variable="local.saved" {
        content();
    }
    return local.saved;
}

Obviously because I'm implementing this with CFML I need to reuse one of the other constructs, but it demonstrates what a simple concept to implement it is.

This is another example of what I mean that rather than taking a generic approach to implementing previously tag-only functionality in CFScript, it needs a bespoke approach. CFScript is now a first class citizen of CFML, and it deserves to have its functionality implemented carefully and thoughtfully. Not just in an automated/generic way that makes the life of the engineers and Adobe and Railo a bit easier.

I have added this suggestion as a comment on an existing Adobe ticket: 3643125;  and a new one for Railo: RAILO-3008.

--
Adam