Syntax for HTML Island in Script

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

And this is why we have <cfscript>

I disagree. Building CFCs in tags is the wrong approach for any number of reasons. Readability, for example. Being unable to program in any other language because you refused to use anything but tags is a big reason alone. Lines of code and less typing. I could go on. Tag based CFC’s aren’t just ‘a different way of doing the same thing’, they’re ‘the wrong way to do the same thing’. I’m sorry fi you feel otherwise, but that’s my opinion and I have good reasons for advocating that my fellow CFML developers drop tags like it’s 1999.

I already did. Do you have evidence to support your assertion that outputting tags directly is more performant than the savecontent/writeoutput/echo cycle? What kind of major performance boost do you imagine this would be?

IDE syntax highlighting is something that could be preserved by building a rendering pipeline using templates, or using React, or Angular, or… you get the idea. That said, I would have no problem with a html{} function, if it were limited only to outputting non-evaluated (e.g. HTML/XML) tags. I could see a use for that in rendering templates, but for CFML tags it’s a no bueno from me for the reasons I’ve already stated.

That’s exactly my point. I use mostly cfscript and sometimes there is a need to add a block of markup. So instead of closing the </cfscript>, adding the markup, and then opening a new block of <cfscript>, a “tag island” would allow you to add that markup easily.

I realize that, and that is great. All I said was that the fact that you disagree doesn’t mean that you’re right and anyone who does it differently is doing it wrong.

Major performance boost. Performance is one of the top goals of Lucee, so we test stuff like this all the time, but even without testing it I can tell you that it is much slower to add savecontent which will create a new buffer, set it to a variable, and then another method call to read back that variable and write it to the response stream.

Each tag or function call adds a lot of overhead. Take for example the following snippet:

<cfscript>
	iter = 1000000;

	function add(a){ return arguments.a + 1; }

	timer label="function" {
		for (i=0; i<iter; i++)
			b = add(i, 1);
	}

	timer label="inline" {
		for (i=0; i<iter; i++)
			b = i + 1;
	}
</cfscript>

On my machine inline runs about 3 to 5 times faster than function, and that’s for a single function call without doing anything else.

Why add a whole framework for something so simple? And then deal with two languages (JavaScript) instead of one?

This is exactly the point! If you had started with that then it would have saved me much typing :wink:

The idea is not to start writing CFML tags in that “tag island”, it’s mostly to write HTML markup. But it can make life easier also when using some tags like <cfmail>, and <cfquery>.

Or if you want to render HTML markup with some values that are interpreted by Lucee, e.g. <td>#name#</td>, so it doesn’t make sense to add it and then have to write <td><cfset echo(name)></td>

Of course, all thoughts are welcome!

My points in regards to JSX are fairly straight forward;

  1. The “end result” of JSX is an object, as I already acknowledged
  2. It shows that this style of syntax has a widely accepted precedent
  3. You can either place JSX inline with script, or place it into a separate file; it’s up to the developer, sometimes inline is actually best

The query example was just that; a way to point out that a quite readable and usable syntax could exist, and might work with existing built-ins. You wouldn’t have to use it, but IMO there’s also nothing wrong with others preferring it :wink: If you have any kind of large, dynamic query, queryExecute might be more verbose in comparison, and slightly less readable, but for most simple queries it’s fantastic, no doubt!

The custom tags example, I think, is totally reasonable. You might not want to hand craft complex SVG, but you may have some other machinery that helps you build it in a simple way. Why would you repeat complex templating that is likely to change when you can abstract it? This concept can be extended to any type of output or returnable objects, we don’t have to fixate on the concrete example of SVG (or on HTML in the case of the other examples). Custom tags are perfectly fine for any of these scenarios, and they provide a construct that simply doesn’t work well in the current script equivalents (the ACF implementation is bad).

React developers might disagree. I think the point is everyone wants to use script everywhere, except in a few specific cases when tags could do the job better :smiley:

Aye, I’m still keen for script based files that are not components!

1 Like

This isn’t about me being right or wrong. I’m not scratching my ego here. It’s about there being a right way and a wrong way to build maintainable applications.

It’s the difference between using super glue or bubble gum to hold a broken vase together. Bubble gum will get the job done in the short term, but it isn’t the right way to put the vase back together if you expect it to stand the test of time.

Another apples and oranges comparison. Last time I checked I’m not iterating over 1M records and outputting HTML. I’m not arguing that you might shave a ms or two by not using savecontent, but in the general use case 1 or 2 ms isn’t worth investing time into this functionality IMHO. And, again, there are better, well defined, solutions to the problem - you just don’t want to use them, which is fine, but investing LAS time into this functionality really just isn’t high on my list of wants. I’ll probably never use it, personally.

Because ColdFusion isn’t a hammer and applications aren’t nails.Those frameworks deal exactly with the problems posed by having to dynamically render HTML from data and are well suited for that purpose. Why would I want to reinvent a wheel in ColdFusion that’s already handled, quite nicely, by other frameworks?

Sorry, I cringe every time you mention <cfquery> or <cfmail> being used inside a tag island. While the idea, for you, may not to be to start writing CFML tags inside the island… I guarantee you that if you do this, that is exactly what many CFML developers will do. This would seem to me to exacerbate one problem to simply swat at another one.

Again, if you want an html{} function that evaluates dynamic content inside of markup while maintaining IDE coloring, I’m all for it. If you want to be able to write any CFML tags in cfscript, then I’m completely against that.

2 Likes

Yes, as a rendering pipeline. Again, this is different from what is being requested here. Completely.

This:

function Repeat(props) {
  let items = [];
  for (let i = 0; i < props.numTimes; i++) {
    items.push(props.children(i));
  }
  return <div>{items}</div>;
}

function ListOfTenThings() {
  return (
    <Repeat numTimes={10}>
      {(index) => <div key={index}>This is item {index} in the list</div>}
    </Repeat>
  );
}

is not this:

tags{
<div>
    <cfloop from="1" to="10" index="ix">
        <div>This is item #ix# in the list</div>
    </cfloop>
</div>
}

The tags are placeholders for the rendering pipeline that evaluates the JSX tag and renders the appropriate HTML in it’s place.

And my argument is that if you open this door most developers will abuse the functionality in every conceivable way possible. The intent may be that it is used sparingly, in lieu of using a rendering pipeline and templates, or in lieu of a (possibly) more verbose and less readable query, for example, but in reality it is far more likely to be widely abused and result in even more horrid spaghetti code to be held up as examples of the shoddy quality of work CFML allows.

1 Like

The intent would be to re-enable some practical, useful, and powerful constructs in script based files. It’s ok if you don’t see it as a win, but I don’t think the hyperbole is helping your argument?

Tag based components are still a thing. You can write one right now. Do you think a developer is of less worth if they choose to make use of some custom tags which are used as a DSL in a tag based component? Why not enable this functionality in a script based component?

Not everything is black and white! :slight_smile:

1 Like

If we are enabling scripting of tag islands just because of failures in cfmail and cfquery implementations that is a bad thing.

The base components of those tags are badly created. I have had to re-implement the Mail component as:

  var email = new EMail()
                            .to(to.getPrimaryEmailFormatted())
                            .from(emailConfig.fromEmail)
                            .subject(emailinfo.subject)
                            .body( emailinfo.text )
                            .html( emailinfo.html )
                            .send();

which is miles better than the current implementation.

My point being is provide good use cases for code islands please :slight_smile:

3 Likes

Talk about “apples and oranges”, good thing you didn’t use “welding vs. spit”. This is an irrelevant analogy as there is no indication that the other way is “bubble gum” as you put it.

Performance measuring is a relative. If something takes 10 to 20 times longer then it’s much much slower. Every performance or benchmark test is done in a loop so I’m really surprised that now you are questioning the method of measuring and comparing.

If you have 1000 concurrent requests then 2ms become 2s and your application is somewhere between slow and unresponsive. If you don’t build applications for scalability then perhaps that doesn’t make a difference to you.

When I add a feature or fix a bug myself then I do it on my own time, so this is not LAS time, and it is not paid for by your support dollars if you are a supporter of the project.

TBH, more time has probably been spent on this thread than implementing the feature.

It is not hyperbole to suggest that CFML has had an albatross around it’s neck for over a decade because of it’s tag based nature and ease of abuse. That’s just a fact. Giving CFMl developers the opportunity to write CFML tags inside of script is going to exacerbate that ease of abuse. That is also a fact. The history of most CFML developers (those outside of our little circle here, mostly) is that they abuse the language, primarily because they don’t really understand programming. That is also a fact. I worked in DC with 5 taggers most of my career and I can absolutely guarantee that if you allow them to write tags in script, they will abuse it. That’s not hyperbole, it’s experience.

Sadly, they are still a thing for some. And I could write one right now, but I won’t.

I am not here to evaluate the worth of any developer. I’m suggesting that tag based CFCs are the wrong approach. Allowing CFML tags in script because the custom tag implementation isn’t well suited to script based CFCs is also the wrong approach. The right approach is to fix the custom tag implementation.

I’m not suggesting it is, I’m suggesting that it’s the wrong approach to the problems you’re trying to solve.

Which is great if you already have the “html” markup ready and passed as an argument. In that case you don not need a Component – simply pass the attribute, or a body param.

The need arises if you want a small snippet of html while writing your script code, without loading it from an external source (db, file).

So instead of putting it all in a string and escaping quotes and having no chance at syntax coloring, you add some markup in a tag island.