@stefanobalocco/jtdal
v8.0.0
Published
Small template engine based on Zope TAL, using data attributes
Readme
<div>
Hello there, <span data-tdal-replace="name">world</span>
</div>{
"name": "General Kenobi"
}jTDAL
Template Attribute Language for JavaScript
Small template engine based on Zope TAL, using data attributes.
Runs on Node.js and in browsers. Written in TypeScript.
- 0 dependencies
- Less than 4 KB gzipped
- Designer-friendly — templates work in any WYSIWYG editor or browser preview
- No custom syntax to break HTML validation
- Compile templates to optimized JavaScript functions
Why another template engine?
I wanted a fast, attribute-based JavaScript template engine and couldn't find one. Mustache is great, but its syntax always felt foreign to me. With data attributes, templates stay valid HTML — you can design them in any WYSIWYG editor or preview them in a browser without rendering data.
Changelog available at the end of the README.
Installation and Usage
Browser
<script type="module">
import jTDAL from 'https://unpkg.com/@stefanobalocco/jtdal/dist/jTDAL.min.js';
const templateEngine = new jTDAL();
const template = '<span data-tdal-content="message">placeholder</span>';
const render = templateEngine.CompileToFunction(template);
const result = render({ message: "Hello World" });
document.getElementById('result').innerHTML = result;
</script>Node.js
Install via npm:
npm install @stefanobalocco/jtdalUse it in your project:
import jTDAL from '@stefanobalocco/jtdal';
const templateEngine = new jTDAL();
const template = '<span data-tdal-content="message">placeholder</span>';
const render = templateEngine.CompileToFunction(template);
const result = render({ message: "Hello World" });
console.log(result);
// Output: <span>Hello World</span>Constructor
constructor(trim = true, strip = true)trim— Trim leading and trailing whitespace from the rendered outputstrip— Remove HTML comments from the template before compilation
API
CompileToFunction(template)
Compiles a template string into a callable function that accepts a data object. Propagates template parse errors and any errors raised while constructing the generated function.
const render = templateEngine.CompileToFunction(template);
const html = render({ name: "Alice", items: [1, 2, 3] });CompileToString(template)
Compiles a template string into a string containing the generated function code. Useful for precompiling templates and saving them as JavaScript files. Propagates template parse errors.
const code = templateEngine.CompileToString(template);
// Returns a string like: "function(d){...}"Template Language
Path Expressions
A path expression identifies a value in the data object using forward-slash notation.
Given this data:
const data = {
logged: true,
user: { name: "Alice", permissions: { canEdit: false } }
};Paths resolve as follows:
loggedresolves totrueuser/nameresolves to"Alice"user/permissions/canEditresolves tofalse
Fallback Paths
Use the pipe | character to provide fallback values. The first truthy resolved value is used:
missing/path | fallback/path | last/resortA present value that is falsy falls through to the next fallback. Values such as 0, false, "", null, undefined, and NaN therefore do not stop the chain.
MACRO: expressions do not accept | fallbacks.
Falsy Values
Whenever jTDAL evaluates an expression in a boolean context, it follows JavaScript truthiness and additionally treats empty arrays and empty objects as false. Boolean contexts include data-tdal-condition, data-tdal-omittag, boolean attributes, and ! negation.
All other values follow JavaScript truthiness. Empty arrays and objects are falsy only in boolean contexts; content and replace fallback evaluation uses JavaScript truthiness, so they do not select a fallback.
Path Modifiers
Boolean Negation:
Prefix with ! to negate a boolean:
!isDisabledUppercase Keywords:
TRUE— Always returnstrue(stops fallback evaluation)FALSE— Always returnsfalse(stops fallback evaluation)GLOBAL— Search in global context, bypassing local scopes (e.g.GLOBAL/variableName)REPEAT— Search in the repeat context, bypassing local scopes (e.g.REPEAT/item/number)
Attributes
The template processor applies attributes in this order:
data-tdal-condition— Conditional renderingdata-tdal-repeat— Loop iterationdata-tdal-content— Replace tag contentdata-tdal-replace— Replace entire tagdata-tdal-attributes— Dynamic attributesdata-tdal-omittag— Remove tag wrapper
data-tdal-condition
Renders the tag only if the expression evaluates to true; removes it otherwise. The expression can be a path with optional ! negation or a chain of fallbacks separated by |.
Examples:
<!-- Show if isLoggedIn is truthy -->
<div data-tdal-condition="isLoggedIn">Welcome back!</div>
<!-- Show if isGuest is falsy -->
<div data-tdal-condition="!isGuest">Member content</div>
<!-- Check nested path with fallback -->
<div data-tdal-condition="user/permissions/canView | config/defaultVisible">
Content
</div>
<!-- Always show -->
<div data-tdal-condition="TRUE">Always visible</div>data-tdal-repeat
Repeats the element for each item in an array or each key in an object. Expects two arguments separated by a space: a name for the loop variable and a path to the collection. A REPEAT object is available to child expressions during iteration.
Syntax:
<li data-tdal-repeat="variable collection/path">...</li>The REPEAT object provides:
index— Current index or object keynumber— Iteration count, starting from 1even— True on even iterations (2, 4, 6...)odd— True on odd iterations (1, 3, 5...)first— True for the first elementlast— True for the last elementlength— Total number of items
Examples:
<!-- Simple array iteration -->
<ul>
<li data-tdal-repeat="item items" data-tdal-content="item">Item</li>
</ul>
<!-- Object iteration -->
<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 rows" data-tdal-attributes="data-index REPEAT/row/index">
<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>data-tdal-content
Replaces the tag's inner content with the result of the expression. The expression can be a path with optional ! negation or a MACRO: reference (see Fallback Paths). Prefix with structure to insert raw HTML without escaping. If the expression evaluates to false, the content is removed.
Examples:
<!-- Simple content replacement -->
<span data-tdal-content="username">Default username</span>
<!-- With fallback -->
<div data-tdal-content="errorMessage | defaultMessage">Error</div>
<!-- Using a macro -->
<div data-tdal-content="MACRO:userCard">User info</div>
<!-- HTML content (not escaped) -->
<div data-tdal-content="structure richTextContent">Default</div>data-tdal-replace
Replaces the entire tag and its contents with the result of the expression. Accepts the same expressions as data-tdal-content. If the expression evaluates to false, the tag and its contents are removed.
Examples:
<!-- Replace tag with value -->
<span data-tdal-replace="status">Status</span>
<!-- Using a macro -->
<div data-tdal-replace="MACRO:navigationMenu">Nav</div>
<!-- HTML content (not escaped) -->
<div data-tdal-replace="structure htmlContent">Placeholder</div>data-tdal-attributes
Sets or modifies tag attributes. Each pair follows the format attribute expression, separated by ;;. Expressions can be a path with optional ! negation and | fallbacks. Append ? to the attribute name for HTML5 boolean attributes: present when truthy, absent when falsy.
Examples:
<!-- Dynamic single attribute -->
<a href="#" data-tdal-attributes="href user/profileUrl">Profile</a>
<!-- Multiple attributes -->
<a data-tdal-attributes="href link/url;;class link/class">Link</a>
<!-- Boolean flag attributes -->
<input type="checkbox" data-tdal-attributes="checked? user/isActive" />
<button data-tdal-attributes="disabled? !canSubmit">Submit</button>
<option data-tdal-attributes="selected? isDefault">Default</option>
<!-- Mixed attributes -->
<input data-tdal-attributes="type inputType;;checked? isEnabled;;disabled? isLocked" />
<!-- Fallback to preserve existing value -->
<img src="default.jpg" data-tdal-attributes="src dynamicUrl | TRUE" />data-tdal-omittag
Removes the tag wrapper while keeping its content when the expression evaluates to true. The expression can be a path with optional ! negation or a chain of fallbacks separated by |.
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>Macros
Macros are reusable template fragments (equivalent to TAL's METAL or Mustache partials). Register them with MacroAdd, then reference them using MACRO:name in data-tdal-content or data-tdal-replace.
Register a Macro
const templateEngine = new jTDAL();
const macro = `Hello, <span data-tdal-replace="name"></span>!`;
templateEngine.MacroAdd("helloworld", macro);MacroAdd returns no value. An invalid macro name throws MacroAdd: Invalid macro name; a template parse failure is propagated. A macro whose registration fails is not registered and does not replace an existing macro of the same name.
Use in Content
Replaces the tag's inner content with the rendered macro:
<span data-tdal-content="MACRO:helloworld">Placeholder</span>
<span data-tdal-content="structure MACRO:helloworld">Placeholder</span>Use in Replace
Replaces the entire tag with the rendered macro:
<div data-tdal-replace="MACRO:helloworld">Placeholder</div>
<div data-tdal-replace="structure MACRO:helloworld">Placeholder</div>Macro Context
Macros inherit the data context of the calling template:
<div data-tdal-repeat="name names"><div data-tdal-replace="MACRO:helloworld">Hello, Macro!</div></div>In this example, the helloworld macro reads name within each loop iteration. With names: ["Alice", "Bob"], this renders:
<div>Hello, Alice!</div><div>Hello, Bob!</div>Changelog
v8.x
- Removed
STRING:expression support. - Switched generated templates from string concatenation to template literals.
- Reworked repeat code generation.
v7.x
- Replaced recursive parsing with a streaming stack-based parser.
- Added stricter validation and template, script, and comment handling.
v6.x
- Reorganized the project into
src,dist, andtests. - Replaced the custom build script with tsBuild.
v5.x
- Moved
trimandstripconfiguration to the constructor. - Removed
trimandstripparameters from compile and macro methods.
v4.x
- Replaced the static namespace API with
jTDALinstances. - Added
MACRO:expressions andMacroAdd.
v3.x
- Moved the package to ESM.
v2.x
- Removed the jQuery dependency.
- Added compiled-template APIs.
v1.x
- Initial jQuery-based release.
