Robust CFScript Suport For Tags In ColdFusion 11 Beta
Over the past several releases of ColdFusion, the support for CFScript have been getting much better. In fact, I write most of my ColdFusion code in CFScript; but, the CFScript feature set does leaves something to be desired. With ColdFusion 11 Beta, however, it looks like Adobe has finally given us full (enough) CFScript support for CFML tags.
The syntax for the ColdFusion tag support in CFScript is rather straightforward. For tags that have bodies, the format is:
cfTagName( attribute-pairs* ) { ... }
... and for tags that do not have bodies, the format is:
cfTagName( attribute-pairs* );
These tags can be nested in the same way that you would nest CFML tags in your non-script code (see demo below).
For the ColdFusion tags that have bodies, the body of the tag may be associated with a new output buffer. Meaning, if you write output within the context of a CFScript-based tag, the content may or may not get written to the page buffer; the difference in outcome depends on the behavior of the given tag. CFSaveContent and CFThread are examples from prior releases of ColdFusion that have already demonstrated this behavior.
The following is a quick example that demonstrates a ColdFusion tag that consumes its output buffer (CFXml), a ColdFusion tag that consumes nested tags (CFHttp and CFHttpParam), and a ColdFusion tag that has no body (CFContent):
<!--- NOTE: ColdFusion 11 was in BETA at the time of this writing. --->
<cfscript>
// Define our friends to be converted into XML.
friends = [
{
name: "Sarah",
age: 47
},
{
name: "Joanna",
age: 35
},
{
name: "Kim",
age: 39
}
];
// Create a short-hand for the buffer output to make the CFXML
// body a bit more readable.
add = writeOutput;
// When you use the ColdFusion tags in CFScript, the body of the
// becomes an output buffer to which you can write the output-body
// of the tag.
cfxml( variable = "doc" ) {
add( "<Friends>" );
for ( friend in friends ) {
add( "<Friend age='#friend.age#'>" );
add( friend.name );
add( "</Friend>" );
}
add( "</Friends>" );
} // END: cfxml.
// Log the XML document to the "remote" service using CFHTTP.
cfhttp(
result = "apiResponse",
method = "post",
url = "http =//127.0.0.1:8500#cgi.script_name#.logger.cfm",
getAsBinary = "yes"
) {
cfhttpParam(
type = "header",
name = "X-Sent-By",
value = "ColdFusion11"
);
cfhttpParam(
type = "body",
value = doc
);
} // END: cfhttp.
// Reset the output buffer. CFContent support finally in CFScript!
cfcontent( type = "text/html;charset=utf-8" );
writeDump( var = doc, label = "Friends" );
</cfscript>
Pretty easy right! And, just about all ColdFusion tags can be used this way; though, I wasn't able to get CFLoop and CFTimer to work properly (not that those are particularly meaningful tags in a CFScript context).
The ColdFusion tag attributes can be provided as a comma-delimited list of key-value pairs; or, as a precomposed attributeCollection set:
<!--- NOTE: ColdFusion 11 was in BETA at the time of this writing. --->
<cfscript>
// Define all of the attributes first. This allows you to
// conditionally build up the attributes to be used.
attributes = {
name: "asyncCode",
action: "run",
thisVar: "Hello",
thatVar: "World"
};
// Invoke script-based tag with collection.
cfthread( attributeCollection = attributes ) {
thread.echoThis = thisVar;
thread.echoThag = thatVar;
}
// Join and output.
cfthread( name = "asyncCode", action = "join" );
writeDump( cfthread );
</cfscript>
In addition to the generic ColdFusion tag syntax, ColdFusion 11 Beta also provides a convenience function - queryExecute() - specifically for streamlining SQL execution. In previous versions of ColdFusion, we could execute database queries within CFScript by using the Query.cfc component. This always felt a bit junky, which is why I (and many others), still use CFML tags when it comes to data gateway components.
The new CFML/CFScript syntax continues to fall short when it comes to the CFQuery tag. This is due to the commingling of the CFQuery's output buffer with the nested CFQueryParam tags. The queryExecute() function tries to bridge this gap by providing a native method that combines SQL, query params, and tag attributes in the same function signature.
In the following code, I demonstrate the previously available approach, the new CFML/CFScript approach, and then the new queryExecute() approach:
<!--- NOTE: ColdFusion 11 was in BETA at the time of this writing. --->
<cfscript>
// Previous CFScript implementation of CFQuery.
getFriends = new Query(
sql = "
SELECT
*
FROM
friend
WHERE
id > :id
ORDER BY
id ASC
",
datasource = "testing"
);
getFriends.addParam(
name = "id",
value = 1,
cfSqlType = "cf_sql_integer"
);
writeDump( var = getFriends.execute().getResult(), label = "Friends" );
// ------------------------------------------------------ //
// ------------------------------------------------------ //
// New CFScript implementation of CFQuery.
cfquery(
name = "friends",
datasource = "testing"
) {
writeOutput( "
SELECT
*
FROM
friend
WHERE
id >
" );
cfqueryparam( value = 1, cfSqlType = "cf_sql_integer" );
writeOutput( "
ORDER BY id ASC
" );
}
writeDump( var = friends, label = "Friends" );
// ------------------------------------------------------ //
// ------------------------------------------------------ //
// New CFScript implementation of CFQuery.
// --
// NOTE: When passing the SQL as a tag argument, I do not believe
// that it is possible to actualy use query pamaters, which is why
// I am hard-coding the "1" in the WHERE clause.
cfquery(
name = "friends",
datasource = "testing",
sql = "
SELECT
*
FROM
friend
WHERE
id > 1
ORDER BY
id ASC
"
);
writeDump( var = friends, label = "Friends" );
// ------------------------------------------------------ //
// ------------------------------------------------------ //
// Alternate new function implementation for CFQuery.
// --
// NOTE: The last two arguments are optional.
friends = queryExecute(
"
SELECT
*
FROM
friend
WHERE
id > :id
ORDER BY
id ASC
",
{
id: {
value: 1,
cfSqlType: "cf_sql_integer"
}
},
{
datasource: "testing"
}
);
writeDump( var = friends, label = "Friends" );
</cfscript>
I have to say, as someone who has always questioned whether or not I could ever give up the CFQuery tag (the tag-based CFQuery tag), the queryExecute() function does seems rather nice. It gets rid of all the ceremony required by the Query.cfc component and still keeps the logic mostly readable. Could this spell the end of all CFML tags for my non-UI (User Interface) related code?
NOTE: The saving grace of the CFScript-based query is the fact that ColdFusion natively allows for multi-line strings. This allows a query statement to be formatted correctly with out the ceremony of string concatenation or line-break escaping.
While the new CFScript tag syntax does make just about every construct in the ColdFusion language available, there are a few notable tags that we have all been waiting for!
<!--- NOTE: ColdFusion 11 was in BETA at the time of this writing. --->
<cfscript>
// Define page settings. CFSettings support finally in CFScript!
cfsetting(
requestTimeout = 2,
showDebugOutput = true
);
// ------------------------------------------------------ //
// ------------------------------------------------------ //
// Define response headers. CFHeader support finally in CFScript!
cfheader(
name = "X-Served-By",
value = "ColdFusion 11 Beta"
);
// ------------------------------------------------------ //
// ------------------------------------------------------ //
// Set new cookies. CFCookie support finally in CFScript!
cfcookie(
name = "coldFusionLives",
value = "You bet your @$$",
expires = "never"
);
// ------------------------------------------------------ //
// ------------------------------------------------------ //
// Execute binaries. CFExecute support finally in CFScript!
cfexecute(
variable = "standardOutput",
name = "echo",
arguments = "What what!",
timeout = 1
);
// ------------------------------------------------------ //
// ------------------------------------------------------ //
// Build XML documents (via buffer). CFXML support finally in
// CFScript. It's not quite as nice looking at E4X; however, it's
// a heck of a lot better than xmlParse().
cfxml( variable = "doc" ) {
writeOutput( "<Numbers>" );
for ( i = 1 ; i < 5 ; i++ ) {
writeOutput( "<Number>#i#</Number>" );
}
writeOutput( "</Numbers>" );
}
// ------------------------------------------------------ //
// ------------------------------------------------------ //
// Generating PDFs. CFDocument support finally in CFScript.
// ... um, yay, I guess.
cfdocument(
format = "pdf",
filename = expandPath( "./cf11.pdf" ),
overwrite = true
) {
cfdocumentItem( type = "footer" ) {
writeOutput( "Page #cfdocument.currentPageNumber#" );
}
cfdocumentSection() {
writeOutput( "<h1>ColdFusion 11</h1>" );
}
cfdocumentSection() {
writeOutput( "<h1>Getting Started</h1>" );
}
}
// ------------------------------------------------------ //
// ------------------------------------------------------ //
// Process images. While there are a lot of CFIMage methods that
// have been available, I think this is the first time that the
// "WriteToBrowser" action is available in CFScript!
cfimage(
source = "./goose-duck.jpg",
action = "writeToBrowser",
width = 100
);
// ------------------------------------------------------ //
// ------------------------------------------------------ //
// There are other ways to build a dynamic invocation signature
// (such as constructing an argumentCollection object). However,
// I just wanted to see if this would work with cfInvoke.
// --
// NOTE: The ColdFusion 11 Beta documentation states that these
// functions are not available; so, don't use them as they may
// be removed in the final release.
cfinvoke(
returnVariable = "result",
method = "sum"
) {
// Dynamically build up the invocation arguments.
for ( i = 1 ; i <= 10 ; i++ ) {
cfinvokeArgument( name = i, value = i );
}
}
// I am the method we are invoking with a dynamic number of args.
public string function sum() {
// NOTE: Totally overkill (and inefficient) approach - just
// using reduce since it's new in ColdFusion 11.
return(
arguments.reduce(
function( result, item, index, collection ) {
return( result + item );
},
0
)
);
}
// ------------------------------------------------------ //
// ------------------------------------------------------ //
// Invoke a ColdFusion custom tag from CFScript! I don't really
// use custom tags anymore; but, this is an interesting possability.
cf_myTag( variable = "customTagOutput" ) {
writeOutput( "This is my tag content!" );
}
</cfscript>
I will probably never jump through the necessary hoops required to use CFDocument in CFScript. I only have it in this demo as a means to further illustrate using nested CFScript tags with output buffers. And, CFXml, while not the prettiest, is still better than what we had before.
While ColdFusion 11 has made [just about] every CFML tag available in CFScript, this update does leave us with a number of redundant constructs. We now have several tags that can be defined in multiple ways. The following is a non-exhaustive demonstration of both the existing and the new approaches to select tags:
<!--- NOTE: ColdFusion 11 was in BETA at the time of this writing. --->
<cfscript>
// Previous CFScript implementation of CFThread.
thread
action = "run",
name = "AsyncCode"
{
thread.ben = "Jamin";
}
// NOTE: This difference applies to other body-style tags like:
// - CFLock.
// - CFTransaction.
// - CFSaveContent.
// New CFScript implementation of CFThread.
cfthread(
action = "run",
name = "AsyncCode2"
) {
thread.word = "to your mother!";
}
// Join thread using existing syntax.
thread name = "AsyncCode" action = "join";
// Join thread using new syntax.
cfthread( name = "AsyncCode2", action = "join" );
// ------------------------------------------------------ //
// ------------------------------------------------------ //
// Previous CFScript implementation of CFHttp.
apiRequest = new Http(
result = "apiResponse",
method = "post",
url = "http =//127.0.0.1:8500#cgi.script_name#.logger.cfm",
getAsBinary = "yes"
);
apiRequest.addParam(
type = "header",
name = "X-Sent-By",
value = "ColdFusion11"
);
apiResponse = apiRequest.send();
// New CFScript implementation of CFHttp.
cfhttp(
result = "apiResponse",
method = "post",
url = "http =//127.0.0.1:8500#cgi.script_name#.logger.cfm",
getAsBinary = "yes"
) {
cfhttpParam(
type = "header",
name = "X-Sent-By",
value = "ColdFusion11"
);
} // END: cfhttp.
// ------------------------------------------------------ //
// ------------------------------------------------------ //
// Previous CFScript implementation of CFParam that is totally
// broken and you should only use this syntax if you don't like
// your fellow programmers. No, for real, don't use this syntax.
// It's not cool. And people will make fun of you.
param url.groove = "sauce";
// Previous CFScript implementation of CFParam.
param name = "url.ben" type = "string" default = "jamin";
// New CFScript implementation of CFParam.
cfparam( name = "url.foo", type = "string", default = "bar" );
// ------------------------------------------------------ //
// ------------------------------------------------------ //
// Previous CFScript implementation of CFInclude.
include "./code.cfm";
// New CFScript implementation of CFInclude.
cfinclude( template = "./code.cfm" );
// ------------------------------------------------------ //
// ------------------------------------------------------ //
// Previous CFScript implementation of CFThrow.
throw( type = "Fail", message = "Ooops." );
// New CFScript implementation of CFThrow.
cfthrow( type = "Fail", message = "Ooops." );
// ------------------------------------------------------ //
// ------------------------------------------------------ //
// Previous CFScript implementation of CFExit.
exit "exitTemplate";
// New CFScript implementation of CFExit.
cfexit( method = "exitTemplate" );
// ------------------------------------------------------ //
// ------------------------------------------------------ //
// Previous CFScript implementation of CFAbort.
abort "Something went wrong";
// New CFScript implementation of CFAbort.
cfabort( showError = "Something went wrong" );
</cfscript>
To be honest, I am not sure which syntax I like better. I am very familiar with using the existing syntax for tags like CFTransaction and CFLock in CFScript. Plus, the color-coding (in SublimeText 2) is much better for the existing syntax, at least for the moment. That said, I do love consistency; so, if I were to start using some of the new tags, which required the new syntax, I'd probably switch over all tags to the new syntax for the sake of consistent formatting.
Want to use code from this post? Check out the license.
Reader Comments
@All,
I should note that both struct pairs and attribute pairs can be defined using either "=" or ":". However, I have chosen to use "=" for attributes and ":" and for structs. Using the colon with the attribute pairs doesn't sit well with me - it sets my spider sense off.
@All,
Also, one thing I forgot to mention is that the CFML tags do not / cannot return values. So, don't try to do something like this:
response = cfhttp() { ... }
It won't compile. In order to get "return" values, you have to use the normal "result", "variable", and "returnVariable" attributes that you would have in normal CFML markup.
Thanks a lot ben, Really awesome updates from adobe .. waiting for it!!
It is a shame Adobe did not follow Railo's lead for the basic syntax (no cf in front of the script version of a tag).
I like the cf leading the script version of the tag, it makes it easy to differentiate a tag-based cfscript use vs a regular function. I also like the added parentheses to wrap the attributes. I hope railo follows suit so that I can use that syntax.
Ben, you have too much fun with your code. I can tell, merely by your examples, that you're having lots of fun dissecting through CF11.
Keep up the examples; this singular post has been THE BEST reference for cfscript syntax that I could find online.
@Kevin: we originally implemented the tag based notation in cfscript as the CFML advisory committee proposed it 5 years ago. So in the latest 4.2 update, we extended the syntax to match ACF's version of it. So the following works in Railo:
cfloop(from="1", to="10", index="i") {}
or
cfloop(from:"1" to:"10" index:"i") {}
or
loop from="1" to="10" index="i" {}
If possible, we will implement the following syntax as well shortly:
response = cfhttp (...)
Gert
@Franz you all totally rule. Railo rulz
Digging up this older post, since CF 11 has released.
Ben, do you know if these new cf* functions support positional parameters? I don't have CF 11 installed anywhere just yet so I can't try myself. Ex:
cfcookie( 'CookieName', '.domain.com', 'NEVER', true, '', true, false, 'some value');
VS
cfcookie(
name='CookieName',
domain='.domain.com',
expires='NEVER',
httpOnly=true,
path='',
preserveCase=true,
secure=false,
value='some value'
);
@Ben,
Thanks for your examples. They saved me more then once this week. Especially this comment: CFML tags do not / cannot return values. I was stuggling with that when I tried to use the cfscheduler command in cfscript.
Also other examples with angularJS and CF were helpful.
With regards, Ben