New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MAJOR FEAT] New syntax for textual template modes #395

Closed
danielfernandez opened this Issue Sep 18, 2015 · 4 comments

Comments

Projects
None yet
2 participants
@danielfernandez
Member

danielfernandez commented Sep 18, 2015

Thymeleaf 3.0 includes three new textual template modes: TEXT, JAVASCRIPT and CSS. As their names imply, JAVASCRIPT and CSS are meant to be used for processing JavaScript and CSS templates, and the first one (TEXT) can be used for processing a template in mostly any format, including mere plain text or even HTML files that we don't want to treat as such.

These new template modes do not use the same syntax as the markup ones (HTML and XML). The reason is, in a textual template mode markup HTML/XML tags are not relevant because HTML/XML is not parsed as such, so code like this would be possible:

   <img src="<th:block th:text="@{/images/logo.png}"/>" alt="logo" />

...but it would be a nightmare for a parser to try to tell the difference between a tag it has to process (<th:block>) and another one, containing the former, which it doesn't have to process at all (<img>).

The Thymeleaf textual syntax

So Thymeleaf 3.0 adopts a new syntax for textual template modes, in which elements (i.e. processable tags) are declared as [#name ...] instead of <name ...>). A quick example based on the code snippet above:

   <img src="[#th:block th:text="@{/images/logo.png}"/]" alt="logo" />

...which is completely equivalent to the more concise:

   <img src="[# th:text="@{/images/logo.png}"/]" alt="logo" />

So we have now a way of expressing elements, without using HTML tags. Note how the syntax is similar in concept to the markup one, as we still have both open+close elements (i.e. elements with a body) and standalone elements:

  [#th:block th:each="item : ${items}"]
    - [#th:block th:utext="${item}" /]
  [/th:block]

...or, again, the completely equivalent (which we will use from here on):

  [# th:each="item : ${items}"]
    - [# th:utext="${item}" /]
  [/]

Note that the textual syntax requires full element balance (no unclosed tags) and quoted attributes. So to say, it's more XML-style than HTML-style.

Inlined Output expressions

Inlined output expressions (see #394) are also available in textual template modes, in both their escaped ([[...]]) and unescaped ([(...)]) form. So we could have more concise versions of the examples above:

  <img src="[# th:text="@{/images/logo.png}"/]" alt="logo" />
  ...
  [# th:each="item : ${items}"]
    - [# th:utext="${item}" /]
  [/]

Which, using inlined output expressions, could look like:

  <img src="[[@{/images/logo.png}]]" alt="logo" />
  ...
  [# th:each="item : ${items}"]
    - [(${item})]
  [/]

Escaped output

All these three new textual template modes allow the use of both th:text and th:utext attributes, or the equivalent inlined [[...]] and [(...)]. But what does escaping mean in these template modes?. The behaviour is different depending on the specific mode:

Escaping in TEXT

Escaped output in TEXT will be HTML-escaped. This makes it consistent with the behaviour of inlined [[...]] expressions in th:inline="text" blocks in Thymeleaf 2.1. So this:

<a href="[[@{/product(id=${p.id},type=${p.type})}]]">Product details</a>

...will do its job and adequately HTML-escape the attribute (note the &amp;):

<a href="/product?id=45&amp;type=normal">Product details</a>

Note however that this HTML-escaping will be applied in TEXT mode even when the template being processed does not represent an HTML document, so for most real text uses of this mode (e.g. text emails), unescaped expressions (th:utext or [(...)]) are usually a better option.

Escaping in JAVASCRIPT

Escaped output in JAVASCRIPT mode will convert its output into escaped JavaScript literals, surrounded by single quotes. So if we have a msg variable with value This is a 'textual' template mode, and we use it in a JAVASCRIPT template like:

   var a = [# th:text="${msg}"/];

...or the inlined equivalent:

   var a = [[${msg}]];

We will obtain:

   var a = 'This is a \'textual\' template mode';

Note how the surrounding quotes have been generated by Thymeleaf, and the text inside has been JavaScript-escaped. Also note how this is completely consistent with the way inlined output expressions worked inside th:inline="javascript" blocks in Thymeleaf 2.1.

Escaping in CSS

Escaped output in CSS mode will convert its output into escaped CSS identifiers. So if we have an elemClass variable with value content+main, and we want to use it in a CSS template in order to apply some styles to the elements with such class:

   .[# th:text="${elemClass}"/] {
      background-color: blue;
   }

...or the inlined equivalent:

   .[[${elemClass}]] {
      background-color: blue;
   }

We will obtain:

   .content\+main {
      background-color: blue;
   }

We can observe how the + symbol has been adequately escaped in accordance with CSS escaping rules.

Escaped element attributes

In order to avoid interactions with parts of the template that might be processed in other modes (e.g. text-mode inlining inside an HTML template), Thymeleaf 3.0 allows the attributes in elements in its textual syntax to be escaped. So:

  • Attributes in TEXT template mode will be HTML-unescaped.
  • Attributes in JAVASCRIPT template mode will be JavaScript-unescaped.
  • Attributes in CSS template mode will be CSS-unescaped.

So this would be perfectly OK in a TEXT-mode template (note the &gt;):

  [# th:if="${120&lt;user.age}"]
     Congratulations!
  [/]

Of course that &lt; would make no sense in a real text template, but it is a good idea if we are processing an HTML template with a th:inline="text" block containing the code above and we want to make sure our browser doesn't take that <user.age for the name of an open tag when statically opening the file as a prototype.

Extensibility

One of the advantages of this syntax is that it is just as extensible as the markup one. The user can still define his/her own dialects with custom elements and attributes, apply a prefix to them (optionally), and then use them in textual template modes:

  [#myorg:dosomething myorg:importantattr="211"]some text[/myorg:dosomething]

Note that, as with the markup template modes, the Standard Dialects include just one processable element ([#th:block ...], which can also be used with an empty name [# ...]) and a set of processable attributes (th:text, th:utext, th:if, th:unless, th:each, etc.).

Parser-level and prototype-only comment blocks

Thymeleaf 2.1 already included parser-level and prototype-only comment blocks. Parser-level comments delimited template fragments that wouldn't even be parsed, effectively allowing for template comments that would never be processed or appear in output (similar to JSP <%-- comments --%>):

  ...
  <!--/* This is a parser-level comment, that will never show up */-->
  <p>This is some text</p>
  ...

Prototype-only comments, on the other side, were fragments of template that would be considered comments only when displayed statically by a browser (i.e. without Thymeleaf processing), but would be considered normal input by Thymeleaf for parsing and processing:

  ...
  <table>
    <!--/*/ <th:block th:each="p : ${products}"> /*/-->
      <tr>
        <td th:text="${p.name}">The name</td>
        <td th:text="${p.price}">The price</td>
      </tr>
      <tr>
        <td colspan="2" th:text="${p.description}">The long description</td>
      </tr>
    <!--/*/ </th:block> /*/-->
  </table>
  ...

But these syntaxes are markup-oriented (they are based on <!-- ... --> syntax), so they aren't completely adequate for non-markup templates.

Thymeleaf 2.1's JavaScript inlining (th:inline="javascript") defined specialized syntaxes for both kinds of comments, when appearing inside JavaScript-inlined blocks:

  • /*[- ... -]*/ for parser-level comments.
  • /*[+ ... +]*/ for prototype-only comments.

Thymeleaf 3.0 not only supports these syntaxes, but in fact extends them to all textual template modes in order to achieve better consistence with their corresponding inlining modes:

  ...
  /*[- This is a parser-level comment, that will never show up -]*/
  This is some text, or some javascript, or some CSS perhaps.
  ...

Note however that prototype-only comments are supported only for JAVASCRIPT and CSS (this kind of coments would make no sense in TEXT mode):

  ...

  /*[+  alert('Hello, ' + [[${session.user.name}]] + ', this is actually working');  +]*/

  var f = function() {
  ...

Text templates for plain-text email

A typical example of using the TEXT template mode could be a template for sending plain-text email:

Dear [(${customer.name})],

This is the list of our products:
[# th:each="p : ${products}"]
   - [(${p.name})]. Price: [(${#numbers.formatdecimal(p.price,1,2)})] EUR/kg
[/]
Thanks,
  The Thymeleaf Shop

Which, once executed would give us something like

Dear John Apple,

This is the list of our products:

   - Oranges. Price: 0,90 EUR/kg
   - Apples. Price: 0,75 EUR/kg
   - Bananas. Price: 1,04 EUR/kg

Thanks,
  The Thymeleaf Shop

Natural scripting (JavaScript) and styling (CSS)

In Thymeleaf 2.1's javascript inlining syntax, using output expressions inside comments allowed for a certain degree of natural scripting, this is, scripts that worked OK both when open statically in a browser and when executed through the Thymeleaf template engine. For example, this:

<script th:inline="javascript">
    ...
    var username = /*[[${session.user.name}]]*/ 'Sebastian';
    ...
</script>

...which is valid JavaScript, and once executed could look like:

<script>
    ...
    var username = 'John Apricot';
    ...
</script>

Thymeleaf 3.0 adopts and extends this syntax, so that whenever an output expression is found wrapped inside a comment in JAVASCRIPT or CSS template mode, it will remove the rest of the line until it finds any of ;,)}], a line feed or a //... single-line comment block.

Moreover, this operation is now performed at parsing time so that it won't have any impact at all on the engine's performance --when using the template cache--. The parser will effectively replace the snippet above with:

<script th:inline="javascript">
    ...
    var username = [# th:text="${session.user.name}"/];
    ...
</script>

Note this works not only with escaped output expressions (/*[[...]]*/), but also with their unescaped equivalents (/*[(...)]*/).

Applying comments to any element

Going further, in order to allow for a greater level of natural scripting/styling, Thymeleaf 3.0 not only allows wrapping output expressions inside comments in JAVASCRIPT and CSS, but actually any element can be wrapped too:

  /*[# th:if="${user.admin}"]*/
     alert('Welcome admin');
  /*[/]*/

That alert in the code above will be therefore be shown when the template is open statically --because it is perfectly valid JavaScript--, and also when the template is run if the user is an admin. It is completely equivalent to:

  [# th:if="${user.admin}"]
     alert('Welcome admin');
  [/]

...which is actually the code to which the initial version is converted during template parsing.

Note however that wrapping elements in comments does not clean the lines they live in as inlined output expressions do. That behaviour is reserved for inlined output expressions only.

So Thymeleaf 3.0 allows the development of complex JavaScript scripts and CSS style sheets in the form of natural templates, valid both as a prototype and as a working template.

Summary

  • Three textual template modes: TEXT, JAVASCRIPT and CSS.
  • New syntax for elements in textual template modes: [# ...] ... [/].
  • Inlined output expressions allowed, both escaped ([[...]]) and unescaped ([(...)]).
  • Intelligent escaping of JavaScript (as literals) and CSS (as identifiers).
  • Parser-level (/*[- ... -]*/) and prototype-only (/*[+ ... +]*/) comment blocks.
  • Natural templates applied to JAVASCRIPT scripts and CSS style sheets by means of wrapping elements and/or output expressions inside comments (/*[# ...]*/).
@danielfernandez

This comment has been minimized.

Show comment
Hide comment
@danielfernandez

danielfernandez Sep 18, 2015

Member

Already in 3.0.0-SNAPSHOT

Member

danielfernandez commented Sep 18, 2015

Already in 3.0.0-SNAPSHOT

@QiaoG

This comment has been minimized.

Show comment
Hide comment
@QiaoG

QiaoG Jun 13, 2016

logo can not parse to <img src="/???/images/log.png" ...
why?

QiaoG commented Jun 13, 2016

logo can not parse to <img src="/???/images/log.png" ...
why?

@QiaoG

This comment has been minimized.

Show comment
Hide comment
@QiaoG

QiaoG Jun 13, 2016

src="[[@{/images/logo.png}]]" can not parse to src="/???/images/log.png" ... why?

QiaoG commented Jun 13, 2016

src="[[@{/images/logo.png}]]" can not parse to src="/???/images/log.png" ... why?

@danielfernandez

This comment has been minimized.

Show comment
Hide comment
@danielfernandez

danielfernandez Jun 13, 2016

Member

@QiaoG are you using the TEXT template mode for that tempalte?

Member

danielfernandez commented Jun 13, 2016

@QiaoG are you using the TEXT template mode for that tempalte?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment