Syntax for HTML Island in Script

We are currently considering the following options for “tag island” inside cfscript:

  1. Three ticks surrounding the tag island, e.g. `` <h1>html code</h1> ` ```

  2. Double braces, e.g. {{ <h1>html code</h1> }}

  3. Triple braces, e.g. {{{ <h1>html code</h1> }}}

  4. Add an html tag, e.g. html { <h1>html code</h1> }

  5. Surround the tags with <cfml> and </cfml>

Thoughts and ideas welcomed.

See also [LDEV-1324] - Lucee

1 Like

I personally like the double braces. That makes it easier to identify. Perhaps an IDE implementation could be implemented accordingly.

1 Like

i personally would like to see #1, because regular tag code could contain }}}. for example in client site javascript code or

<cfdirectory filter="#function (arg1,arg2) {if(arg1){if(arg2){dump(arguments);}}}#"  ...>

also #1 is easy to do on a german swiss keyboard.
is there a way to find out if a certain character is easy to do on most keyboards in the world?

what if we do a tag?

if(susi==sorglos) {
<cftag><cfset x=susi></cftag>
}

an other question also is, how easy is a chosen syntax to teach certain editors (sublime,…)?

I like #1 for exactly that same reason.

also regular script code can contain }}} which is even worse in this case.

For what it’s worth, I searched the Adobe bug tracker and couldn’t find any ticket for this so there doesn’t appear to be any other precedent on the matter.

The three backticks are very familiar to me due to its use in markdown, but for some reason it doesn’t seem consistent with CFML. One question I have is how would we handle escaping the characters if you want to legitimately have three backticks in your output? CFML has historically escaped characters like hashes or pound signs by doubling them up, but what would that look like with a three character sequence?

What would be funny is if we made it work like example below. People would probably complain about it not being valid, but it would make total sense to CFML devs since it’s a natural boundary between tags and script already.

for (i=1; i<100; i++){
    </cfscript>
        <h1>Look Ma! Tags in <i>Script</i></h1>
        <p>at <cfset echo(now())>
    <cfscript>
}

Hmm, does that mean that this would also need to work in a .cfm file??

<cfscript>
    for (i=1; i<100; i++){
        </cfscript>
            <h1>Look Ma! Tags in <i>Script</i></h1>
            <p>at <cfset echo(now())>
        <cfscript>
    }
</cfscript>

I should probably add, that I’d be unlikely to use this feature myself. If I need to output tags in script, it probably means I’m breaking something in my MVC. In the ColdBox world, I’d simply ask the framework’s HTML renderer to render a view for me.

var renderer = getInstance( 'renderer@coldbox' );
var htmlToOutput = renderer.renderView( 'myView' );

And as a general rule, I never have CFCs output directly to the page output stream.

1 Like

This should also work with a script-only file, like Component.cfc or the proposed .cfs files, so you don’t necessarily have an open <cfscript> tag, and having a close tag without an open one is just wrong IMO.

Well, in my first example, you do have both an open and a closed one, they’re just in opposite order. In that case, it’s not really XML-like (CFML isn’t XML anyway…) , but rather a boundary marker saying “script is starting here” or “script is ending here”.

Why not

<< <h1>tags</h1> >>

or

<<< <h1>tags</h1>  >>>

or

<tags>  <h1>tags</h1> </tags>

or

{tags} <h1>tags</h1> {/tags}

or

-- tags
<h1>tags</h1>
--/tags 

Good suggestions @Sam_Jay. I have the same question for you as I had for the original post, which is “How would you escape those as literal strings inside your tag island?”

Had the same thought:

for (i=1; i<100; i++){
  <cftags>
    <h1>Look Ma! Tags in <i>Script</i></h1>
    <p>at <cfset echo(now())>
  </cftags>
}

or even

for (i=1; i<100; i++){
  <cftagisland>
    <h1>Look Ma! Tags in <i>Script</i></h1>
    <p>at <cfset echo(now())>
  </cftagisland>
}

@bdw429s Surely you wouldn’t need to escape them given they’re so cfml-specific. Unlike the back-tick/brace suggestions.

I do pity the IDE theme authors though…

I personally think the idea of a tag island in script is a bad design concept. It took a long time to mature cf away from tags and the bad designs they often lent themselves to.

I’ve tried many ways of having tags in script when I had to. It looks bad, reads bad, is difficult to document, problems with escaping. All a nightmare. On top of that, presentation markup should generally NOT be in script, which is often used for logic.

Best solution that I found was to leverage the best features of both script and cfm files.

In my script only cfcs, when I positively have to use markup in my code, I put it in a cfm file. Then in the cfc, uses savecontent with an include. The cfm’s scope is that of the method, I can use any markup or cf tag, and no problems with escaping characters.

5 Likes

+1 @JimP I would rather Lucee focus on fixing the bugs that currently exist - Adam Cameron found and submitted tickets for myriad member function bugs - than to worry about functionality like this. Use tags in .cfm, script in .cfc, and move on.

4 Likes

I like @Sam_Jay’s suggestions, esp 3 (with curly brackets)

I might also suggest

[tags] <h1>hello world</h1> [/tags]

How about:

cftags  {
   <cfset example="123">
}

It kinda mirrors

<cfscript>
    example="123";
</cfscript>

To answer the question, I would say #1. The 3 tick marks seem like they’d run into less issues.

I’d rather see something like cftag. We have cfscript blocks to do the opposite, so a cftag block makes sense to me here.

<cfscript>
for(i=1; i<100; i++){
     cftag{
          <h1>Look Ma!  Tags in script</h1>
          <p>at #now()#</p>
     };
}
</cfscript>

To address the original JIRA issue, it listed 2 reasons for doing this: HTML templating and easier to do in tags.

For HTML templating, I’d say either just use tags or use savecontent in your script. I guess I don’t see how having a special tag block would really help much, other than being a little cleaner.

<cfscript>
for(i=1; i<100; i++){
     savecontent variable="myContent" {
          writeOutput("
               <h1>Look Ma!  Tags in script</h1>
               <p>at #now()#</p>
          ");
     };
}
</cfscript>

The second part: “some processing that is easier with tags”, why not identify those pain points and see about dealing with them instead?

it’s not only cleaner:

  1. you would have to escape every " symbol in your example

  2. you lose IDE syntax coloring as all your code is one string

  3. you need to call echo(myContent) or writeOutput(myContent) after your savecontent tag to actually emit the value

  4. writing the “tags” as proposed directly to the output stream is much much faster than the savecontent/variable/output, so that would add a major performance boost in such situations

there are many ways to handle that edge case, but it can be said for any tag. just like you need to escape a # inside a cfoutput, or you would have to “escape” or “break down” anything that starts with <cf as it will throw an error. e.g.

<h1>Hello <cfxxx></h1>

will throw an error, but I can instead do something like

<cfset string="cfxxx"><h1>Hello <#string#></h1>

when all of the characters are the same, as in the 3 tics, then it’s even easier:

#RepeatString('`', 3)#

There’s a few behaviours to consider in relation to tags in script;

  1. Immediate output
  2. Captured output
  3. 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 :slight_smile:

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 :wink:

Thanks Igal for kicking off the discussion again!

2 Likes

Do I dare tread here with my thoughts?

First of all, I concur with others that focusing on taking us, what I consider to be, backwards in terms of the language by even having tag islands in script is probably just a bad idea. For a language plagued with the impression that it’s not a real programming language because of tags, I don’t see how giving the 5 taggers another way to make a complete mess out of their code is wise on any level.

@justincarter I’m afraid your comparison to React makes little sense to me. React deals with the DOM and JSON, primarily, so it has a rendering pipeline that can output HTML and populate it with variables. You could, in theory, do the same thing in CFML (build a rendering pipeline), or, as others pointed out, use ones that already exist (aka ColdBox). That’s an entirely different animal from using CFML tags in cfscript.

That said, you go on to illustrate assigning a query to a variable with cfquery, when we already have queryExecute() that does the same thing. What purpose would this serve that queryExecute does not? Your last example can already be done with the use of ’ around that block of XML ( svg = '[ tags here ]'; would achieve the exact same thing.)

Mind you, I know these are but contrived examples… but this:

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

I have zero need for any tag based CFC’s, period. If you still need tags to build CFCs then you’re flat out just doing it wrong. I’m sorry, but that’s the reality. I haven’t had to build a tag based CFC in years. I still have tags, sparingly, inside of my views in some applications, but not a drop of it in my CFC’s unless I’m building a rendering pipeline, and then I use savecontent and writeOutput and move on.

When I think ‘language enhancements’ - tags are the last thing that comes to mind… unless it’s finding yet another way to get rid of them. If HTML were script based instead of tag based, we’d not even be having this discussion. That HTML is tag based means we should follow the lead of many before us, in many different languages, and build rendering pipelines as needed, or use the ones already available to us, like, I dunno… React?

3 Likes

This is not just for CFC’s. There are many places in CFM where it’s much cleaner and faster to write cfscript code.

The fact that someone does things differently doesn’t mean that he’s doing it wrong. Doesn’t mean that either one of you is doing it wrong. It means that you’re doing it differently.

Please see my comments above:

https://lucee.daemonite.io/t/syntax-for-tag-island-in-script/2347/16?u=21solutions