Small template engine based on Zope TAL, using data attributes
<div>
Hello there, <span data-tdal-replace="name">world</span>
</div>
{
"name": "General Kenobi"
}
Template Attribute Language for JavaScript
Small template engine based on Zope TAL, using data attributes.
Written in TypeScript, for Node.js and the browser.
Because I didn’t find a fast JavaScript template engine based on attributes. While Mustache is absolutely awesome, I find its syntax quite weird. Using attributes, the page can be designed with any WYSIWYG editor or previewed in the browser without the need for the actual rendering data.
You can use jTDAL directly in the browser or as an npm package.
<script type="module">
import jTDAL from 'https://unpkg.com/jtdal/jTDAL.min.js';
const templateEngine = new jTDAL( );
// Define a macro that contains a dynamic element
templateEngine.MacroAdd( 'macrofoo', '<span data-tdal-content="foo"></span>, ' );
// Template that uses the macro with replace AND adds another element
const template = `<span data-tdal-replace="MACRO:macrofoo"></span><span data-tdal-content="bar"></span>`;
const t = templateEngine.CompileToFunction(template);
const data = { foo: "Hello", bar: "World" };
const result = t(data);
document.getElementById( 'result' ).innerHTML = result;
// Output: Hello, World
</script>
Install jTDAL via npm:
npm install jtdal
Then use it in your project:
import jTDAL from 'jtdal';
// Create template engine
const templateEngine = new jTDAL( );
// Add a macro that contains a dynamic element
templateEngine.MacroAdd( 'macrofoo', '<span data-tdal-content="foo"></span>, ' );
// Template that uses the macro with replace AND adds another element
const template = `<span data-tdal-replace="MACRO:macrofoo"></span><span data-tdal-content="bar"></span>`;
const t = templateEngine.CompileToFunction(template);
const data = { foo: "Hello", bar: "World" };
const result = t(data);
console.log(result);
// Output: Hello, World
Constructor takes 2 parameters that controls trim and strip behaviour:
constructor( trim = true, strip = true )
Compile the template returning a callable function with an optional parameter data
.
Compile the template returning a string containing the function code. Usefull to precompile templates and save as javascript files.
A path expression indicates the position of the value in the data object. Given the following data:
data = { foo: "bar", foobar: { bar: "foo" } };
foobar/bar
resolves to foo
foo
resolves to bar
The syntax of a path expression is:
path/nonexistent | existing/path | never/reached
The first existing and truthy path is used as the result. If no paths match, false
is returned.
Truthy/Falsy evaluation:
false
, null
, undefined
, ""
(empty string), 0
, []
(empty array), {}
(empty object)!
for negation.STRING:
to define static strings or templates with placeholders.Special keywords:
TRUE
: Always returns true
(halts parsing)FALSE
: Always returns false
(halts parsing)GLOBAL
: search directly in the global context bypassing other contexts (es. GLOBAL/variableName
)REPEAT
: search directly in the repeat context bypassing other contexts (es. REPEAT/variableName
)A string is a path prefixed with STRING:
. It can contain placeholders in the form {path-expression|another-path|last-path-but-not-a-string}. The placeholders are replaced with the value of the path expression.
In strings is possible to use {?condition}
or {?!condition}
for conditional inclusions.
Each {?condition} must be closed with a {?/condition}. Same for {?!condition}.
STRING:This is a string with a {foo}{?bar} and a {bar}{/bar} placeholder.
The engine supports the following data-
attributes. These attributes are resolved in the specified order:
data-tdal-condition="path-expression-boolean-mod-allowed"
data-tdal-repeat="variable-name path-expression"
data-tdal-content="path-expression-string/macro-allowed
data-tdal-replace="path-expression-string/macro-allowed"
data-tdal-attributes="attribute path-expression-string-allowed[;;attribute path-expression-string-allowed]"
?
to the attribute name: attribute? path-expression
data-tdal-omittag="path-expression-boolean-mod-allowed"
In the documentation below, the attributes are described in the following order for better understanding:
Removes the tag while keeping its content if the path expression evaluates to true
.
Examples:
<!-- Always remove the tag -->
<span data-tdal-omittag="TRUE">Content</span>
<!-- Result: Content -->
<!-- Never remove the tag -->
<span data-tdal-omittag="FALSE">Content</span>
<!-- Result: <span>Content</span> -->
<!-- Remove tag if variable is truthy -->
<span data-tdal-omittag="hideWrapper">Content</span>
<!-- Remove tag if variable is falsy -->
<span data-tdal-omittag="!showWrapper">Content</span>
<!-- Remove tag based on nested path -->
<span data-tdal-omittag="config/removeWrappers">Content</span>
Adds or modifies multiple attributes based on the path expression. Each attribute-value pair is separated by ;;
. Existing attribute values are preserved if the expression evaluates to true
.
Boolean Flag Attributes:
If an attribute name ends with ?
, it will be added as a boolean attribute (without value) when truthy, or removed when falsy.
Examples:
<!-- Keep existing attribute value -->
<img src="default.jpg" data-tdal-attributes="src TRUE" />
<!-- Replace with dynamic value -->
<a href="#" data-tdal-attributes="href user/profileUrl">Profile</a>
<!-- Use string template -->
<img src="default.jpg" data-tdal-attributes="src STRING:https://example.com/images/{imageId}.jpg" />
<!-- Remove attribute -->
<input type="text" disabled data-tdal-attributes="disabled FALSE" />
<!-- Boolean flag attributes (HTML5 boolean attributes) -->
<input type="checkbox" data-tdal-attributes="checked? isChecked" />
<button data-tdal-attributes="disabled? !canSubmit">Submit</button>
<option data-tdal-attributes="selected? isDefault | FALSE">Default</option>
<!-- Multiple attributes including flags -->
<input data-tdal-attributes="type STRING:checkbox;;checked? user/preferences/newsletter;;disabled? !isEditable" />
<!-- Multiple attributes -->
<a data-tdal-attributes="href link/url;;class STRING:btn btn-{type};;title link/description">Link</a>
<!-- Fallback to keeping existing value -->
<img src="default.jpg" data-tdal-attributes="src dynamicUrl | TRUE" />
Replaces the tag’s content with the result of the path expression. If the expression is false, the content is removed.
Examples:
<!-- Simple content replacement -->
<span data-tdal-content="username">Default username</span>
<!-- With HTML structure (not escaped) -->
<div data-tdal-content="structure richTextContent">Default content</div>
<!-- String template -->
<h1 data-tdal-content="STRING:Welcome, {user/name}!">Welcome!</h1>
<!-- Conditional content -->
<div data-tdal-content="errorMessage | STRING:No errors">Error placeholder</div>
<!-- Using macro -->
<div data-tdal-content="MACRO:userCard">User info</div>
Replaces the tag and its contents with the result of the path expression. If the expression is false, the tag and its contents are removed.
Examples:
<!-- Replace entire element with value -->
<div data-tdal-replace="statusMessage">Status placeholder</div>
<!-- Replace with HTML (not escaped) -->
<div data-tdal-replace="structure htmlContent">Placeholder</div>
<!-- String template replacement -->
<span data-tdal-replace="STRING:<strong>{username}</strong>">Username</span>
<!-- Using macro -->
<div data-tdal-replace="MACRO:navigationMenu">Nav placeholder</div>
Removes the tag and its contents if the path expression evaluates to false
.
Examples:
<!-- Show if variable is truthy -->
<div data-tdal-condition="isLoggedIn">Welcome back!</div>
<!-- Show if variable is falsy -->
<div data-tdal-condition="!isGuest">Member content</div>
<!-- Always show -->
<div data-tdal-condition="TRUE">Always visible</div>
<!-- Never show -->
<div data-tdal-condition="FALSE">Never visible</div>
<!-- Check nested path -->
<div data-tdal-condition="user/permissions/canEdit">Edit button</div>
<!-- With fallback -->
<div data-tdal-condition="feature/enabled | config/defaultEnabled">Feature content</div>
Repeats the tag for each element in an array or object. While iterating, a REPEAT
object and the loop variable are available.
Examples:
<!-- Simple array iteration -->
<ul>
<li data-tdal-repeat="item items" data-tdal-content="item">Item</li>
</ul>
<!-- Object iteration with properties -->
<div data-tdal-repeat="user users">
<h3 data-tdal-content="user/name">Name</h3>
<p data-tdal-content="user/email">Email</p>
</div>
<!-- Using REPEAT variables -->
<tr data-tdal-repeat="row data" data-tdal-attributes="class STRING:{?REPEAT/row/odd}odd{?/REPEAT/row/odd}{?REPEAT/row/even}even{?/REPEAT/row/even}">
<td data-tdal-content="REPEAT/row/number">Index</td>
<td data-tdal-content="row/value">Value</td>
</tr>
<!-- Nested repeats -->
<div data-tdal-repeat="category categories">
<h2 data-tdal-content="category/name">Category</h2>
<ul>
<li data-tdal-repeat="product category/products" data-tdal-content="product/name">Product</li>
</ul>
</div>
The REPEAT
object provides:
index
: Current index (or key for objects)number
: Current iteration, starting from 1even
, odd
: Boolean flags for even/odd iterationsfirst
, last
: Boolean flags for the first/last elementlength
: Total items in the array or keys in the objectjTDAL support macros (equivalent of TAL’s METAL or partials in Mustache) in content and replace.
First you need to add your macro to the template:
const macro = `Hello, <span data-tdal-replace="name|STRING:World"></span>!`;
templateEngine.MacroAdd( "helloworld", macro );
Then you can call it from the template:
<span data-tdal-content="MACRO:helloworld">Something</span>
<span data-tdal-content="structure MACRO:helloworld">Something</span>
Replaces the tag’s content with the template foo
<div data-tdal-replace="MACRO:helloworld">Replaced content</div>
<div data-tdal-replace="structure MACRO:helloworld">Replaced content</div>
Replaces the tag with the template foo