Thursday 31 October 2013

<cfinclude> and JavaScript

G'day:
Yesterday evening on Twitter, Mike Henke asking a quick quiz question:

Before checking the Gist or progressing... answer the question for yourself.

The answer is "yes". This wasn't immediately apparent to people, and there were a few cautionary tales shared in that Twitter thread, and I thought I'd write a quick article today about this.

Firstly, I suspect the question is sufficiently clear that it doesn't require an example, but to remove all doubt, here's an example:

<--- page.cfm --->
<cfprocessingdirective pageEncoding="utf-8">
<cfscript>
title = "G'day World!";
message = "Kia ora, ao!";
alert = "Tēnā koutou";

include "page.html";
</cfscript>

<!--- page.html --->
<cfoutput>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>#title#</title>
</head>
<body>
    <p>#message#</p>
</body>
</html>
</cfoutput>

If one runs this, the output is:

Kia ora, ao!

So despite the fact the file is a .html file, ColdFusion processes it as CFML. There was some surprise that this occurs given the file extension is not .cfm.

I think the confusion arises because if one requests a .htm file - ie: browses to http://myDomain/file.htm -  it is not processed by ColdFusion. For example, in the situation above I browsed to http://localhost:8500/scribble/shared/git/blogExamples/cfml/cfinclude/page.cfm; if I was to browse to http://localhost:8500/scribble/shared/git/blogExamples/cfml/cfinclude/page.html, what I'd get on the screen is:

#message#

Because the web server has been configured to simply fetch and return .html files. However if a requested file has a .cfm extension: it passes it to ColdFusion. The point is here that it's the web server that concerns itself with file extensions and MIME types etc, it's not ColdFusion. It is never ColdFusion.

I could configure my web server to pass .html files to ColdFusion, and ColdFusion would process them as CFML. I'm just running the inbuilt web server here, but I can configure it to process .html files easily. One can just edit web.xml (for me C:\Apps\Adobe\ColdFusion\10\cfusion\wwwroot\WEB-INF\web.xml) and add a mapping, for example:

<servlet-mapping id="coldfusion_mapping_16">
    <servlet-name>CfmServlet</servlet-name>
    <url-pattern>*.html</url-pattern>
</servlet-mapping>

Having restarted ColdFusion, if I browse to page.html, I get this:

Variable TITLE is undefined.


The error occurred in
C:/Apps/Adobe/ColdFusion/10/cfusion/wwwroot/scribble/shared/git/blogExamples/cfml/cfinclude/page.html: line 6
4 : <head>
5 :  <meta charset="utf-8">
6 :  <title>#title#</title>
7 : </head>
8 : <body>

But that's a sign that ColdFusion is processing the file. Remember the variables are set in page.cfm, so if I hit page.html, they're not there, so it errors as one would expect.

If I was on IIS or Apache, there are similar config options (I don't recall what they are, and have neither at my disposal on this machine, so I cannot remind myself / you).

So you see: ColdFusion does not care about the file extension of a file at all. The only thing that cares about file extensions is the web server.

But what's this got to do with JavaScript? Well the same applies for JS. If I create a page.js, and alter page.html thus:

<!--- page.html --->
<cfoutput>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>#title#</title>
    <script>
    <cfinclude template="page.js">
    </script>
</head>
<body>
    <p>#message#</p>
</body>
</html>
</cfoutput>

<!--- page.js --->
<cfoutput>alert("#alert#");</cfoutput>

I now get a JS alert:

So CF is processing the JS too. bear in mind that all that is happening here is that CF is processing some text with CFML in it, resolving the CFML, and sending the resultant text back to the web server, and it passes it back to the browser. CF is not interacting with JS here. It's just writing out text that is subsequently executed as JS. What gets sent to the browser is this:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>G'day World!</title>
    <script>
    alert("Tēnā koutou");
    </script>
</head>
<body>
    <p>Kia ora, ao!</p>
</body>
</html>

See? All ColdFusion has done is rendered the alert variable in the rest of the text, and the <cfinclude> simply bungs it into the middle of page.html's output.

One caveat to remember here is that this ColdFusion processing only takes place because the files are called in with <cfinclude>.

So if page.js is in a web-browseable directory (as it's intended as an include, it should not be, btw), one can actually browse to it, eg: http://localhost:8500/scribble/shared/git/blogExamples/cfml/cfinclude/page.js. And, of course, the CFML is not processed because the web server has not been configured to pass .js files to ColdFusion, so we get this:

<!--- page.js --->
<cfoutput>alert("#alert#");</cfoutput>

Bear this in mind. This is a very trivial example, but it does represent a bit of a security hole: you do not want your source code to be externally accessible.

This being the case, and given it's an easy thing to accidentally mess up: I recommend you do not put CFML code in anything other than .cfm or .cfc files. This way the only thing ever going to the browser is the result of the source code being compiled and run... the source code will never accidentally fall out onto the screen at the other end.


There's a second thing I want to quickly touch on here, which came out in a conversation on IRC the other day.

It's not uncommon to get CFML to write out JS code, just like one generally writes out HTML. JS is just text, so it's pretty much the same idea.

We abstracted out the alert() code above into a JS file, but it could just as easily been in one .cfm file:

<!--- combined.cfm--->
<cfprocessingdirective pageEncoding="utf-8">
<cfscript>
title = "G'day World!";
message = "Kia ora, ao!";
alert = "Tēnā koutou";
</cfscript>

<cfoutput>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>#title#</title>
    <script>
    alert("#alert#");
    </script>
</head>
<body>
    <p>#message#</p>
</body>
</html>
</cfoutput>

So the CFML is writing out JS there as well as mark-up. Fine. And this trivial example is also "OK".

However what I also see is this sort of thing:

<!--- js.cfm --->
<cfset message = "Haere mai, #URL.name#">
<script>
// function to greet in Maori
function greet(){
    alert("<cfoutput>#message#</cfoutput>");
}

greet();
</script>

Do not do that if you can avoid it. The CFML code is steamrolling into the middle of the JavaScript. This is equivalent (except worse!) to breaking encapsulation in CFML by having a function reference external variables (eg:

function greet(){
    writeOutput("#application.message#");
}

Wherever possible (and it's pretty much always possible), stick all your JS functionality into JS files, and include them as JS files in your mark-up, eg as:

<script src="clientsideCode.js">

If you need to pass parameters from CFML to JS, then factor that code out into some JS that only sets JS variables from the CFML, and then refactor the JS to accept arguments, and pass the JS variable as arguments. But via JS. EG:

<!--- refactored.cfm --->
<script src="refactored.js"></script>
<script>
<cfoutput>
var message = "Haere mai";
var name = "#URL.name#";
</cfoutput>
greet(message,name);
</script>

// refactored.js
function greet(message, name){
    alert(message + " " + name);
}

What this results in is this mark-up:

<script src="refactored.js"></script>
<script>

var message = "Haere mai";
var name = "Zachary";

greet(message,name);
</script>

This is a very trivial example so it barely demonstrates the point, but what we've done here is to have the interchange between CFML code and JS code to be as minimal as possible: it just ports variables from CF to JS, and performs no logic at all. All the actual client side logic is nicely encapsulated in pure Javscript files (and calls to same).

There is still a small amount of CFML muddled in with the JS, but it's all in one place, and the bulk of the JS is just JS: there's the bare minimum of mish-mashing languages, which is a win.

Another win is that JS file doesn't need to be processed by ColdFusion any more, and as it's called in as a discrete file, it can be cached on the browser, rather than being served up anew every request. So this is a doubly good thing to do.

And... I've run out of time, and things to say on this topic. And I need to crack on with some work...

--
Adam