There’s a few behaviours to consider in relation to tags in script;
- Immediate output
- Captured output
- Returned values (built-in tags, and/or custom tags)
I wrote a couple of blog posts on the topic quite a while ago now;
To answer the question of “why would anyone want to do this?”, take a look at the huge traction of React. People like the JSX syntax; it’s not inherently “ugly” (which is somewhat subjective), and it provides both readability and productivity benefits.
class ShoppingList extends React.Component {
render() {
return (
<div className="shopping-list">
<h1>Shopping List for {this.props.name}</h1>
<ul>
<li>Instagram</li>
<li>WhatsApp</li>
<li>Oculus</li>
</ul>
</div>
);
}
}
In the case of JSX it’ll always return an object. In the case of CFML and/or Lucee we may want multiple behaviours that are all consistent in their syntactical approach.
I know it’s cool to say “all tags are bad”, but it’s just not true 100% of the time. Look at JSX. Look at the power we are afforded in CFML by nested custom tags for designing declarative data structures or rendering other complex output that is a total nightmare to do in “pure script” (again, look at why JSX approaches things in the way it does – the declarative syntax is more concise and easier to read than the alternatives). For those who want to take advantage of these languages features but want to still be able to use them in script-based components, we should accommodate them
From the options presented above, declaring a tag block boils down to a few ideas;
Triple backticks
var name = "world";
```
<h1>hello, #name#</h1>
<cfloop from="1" to="10" index="i">
#i#
</cfloop>
```
Script style block
var name = "world";
cftags {
<h1>hello, #name#</h1>
<cfloop from="1" to="10" index="i">
#i#
</cfloop>
}
Tag style block
var name = "world";
<cftags>
<h1>hello, #name#</h1>
<cfloop from="1" to="10" index="i">
#i#
</cfloop>
</cftags>
This deals well with immediate output, but perhaps doesn’t quite satisfy capturing output or returning values. You could place an additional <cfsavecontent>
tag inside each of those, but it seems a little redundant. Another option might be to use the “cfoutput” keyword instead of “cftags”, and allow an assignment to a variable on the left hand side;
Immediate output
var name = "world";
<cfoutput>
<h1>hello #name#</h1>
<cfloop from="1" to="10" index="i">
#i#
</cfloop>
</cfoutput>
Captured output
var name = "world";
var out = <cfoutput>
<h1>hello #name#</h1>
<cfloop from="1" to="10" index="i">
#i#
</cfloop>
</cfoutput>
// do something with 'out'
return out;
This example is effectively how the JSX syntax reads and works (i.e. tags which produce a result which is assigned to a variable on the left).
Returned values
Dealing with output is nice, but the features could go much further than this too. For example, these blocks would allow you to embed any tags that return values (either from built-in or custom tags), which means it would be possible to do something like this (contrived example) but not recommended;
// assume arguments.id = 1 and defaults to 0;
var q = "";
// NOTE: you wouldn't want to do this even though it might be possible, given the above examples
<cfoutput>
<cfquery name="q">
SELECT name
FROM person
<cfif arguments.id>
WHERE id = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.id#">
</cfif>
</cfquery>
</cfoutput>
Instead, the syntax here for assigning a “primary” return value could be simplified to work the same way as captured output;
// assume arguments.id = 1 and defaults to 0;
var q = <cfquery>
SELECT name
FROM person
<cfif arguments.id>
WHERE id = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.id#">
</cfif>
</cfquery>
Similarly for custom tags, you could either capture output or a primary return value (if a feature was added for that, possibly in custom tag CFCs), or do both at the same time;
import "t:/my/tags";
var p = getPerson(1);
var c = p.getChildren();
var svg = <t:person r_result="person">
<t:name>#p.name#</t:name>
<t:children>
<cfloop query="c">
<t:name>#c.name#</t:name>
</cfloop>
</t:children>
</t:person>
This is just a fairly quick write up, but I think there is a lot of scope for some nice features here that will “modernise” script and let us do away with tag-based CFCs
Thanks Igal for kicking off the discussion again!