Complete documentation for Template-M. This page covers both Render Mode (the core templating system) and Demo Mode (the interactive demo features).
Render Mode is the core templating system implemented in template-m.js. It transforms JSON data into DOM elements using HTML templates with special attributes.
The main entry point for rendering templates. This function combines template extraction, rendering, and DOM replacement.
Rendering function that extracts templates and applies data, but does not modify the DOM. Returns a DocumentFragment that can be used programmatically.
This function is used internally by templateM(), but can be called directly when you want the rendered result without automatic DOM replacement.
The data-fn attribute defines extractable template functions within your main template:
<template id="my-template">
<div>
<ul>
<li data-fn="repo:$repos" data-tme="> $name">Item 1</li>
</ul>
</div>
</template>
In this example, data-fn="repo:$repos" does two things:
<li> element as a template function named "repo"$repos will be insertedYou can control what becomes part of the function template - whether it includes the element with the data-fn attribute itself or only its children:
data-fn="name" - The function template includes the element itself (default behavior)data-fn="< name" - Explicitly includes the element itself in the function templatedata-fn="> name" - The function template includes only the element's children, excluding the element with the data-fn attribute (creates a DocumentFragment)<template id="files-template">
<pre data-tme="> $files"></pre>
<div data-fn="> file">
<dt data-tme="> $name"></dt>
<dd data-tme="> $value"></dd>
</div>
</template>
With data-fn="> file", the function template is a DocumentFragment containing both the <dt> and <dd> elements, but not the <div> wrapper.
Data objects specify which template to use via the template property:
{
"repos": [
{"name": "Blueberry", "template": "repo"},
{"name": "Apple", "template": "repo"}
]
}
When a property value is an array, Template-M automatically uses the singular form of the property name as the template name:
{
"repos": [
{"name": "Blueberry"}, // uses "repo" template
{"name": "Apple"} // uses "repo" template
]
}
The property name "repos" becomes "repo" (removes trailing 's'). This automatic singularization only works for simple words ending in 's' - it won't work correctly for irregular plurals like "geese", words ending in "xes" like "boxes", or non-count nouns like "news". For these cases, use explicit template names in your data objects.
When you omit the :$variable part, the function is extracted but no slot is created:
<template>
<ul>
<div class="container" data-tme="> $repos"></div>
<li data-fn="repo" data-tme="> $name">Item 1</li>
</ul>
</template>
The <li data-fn="repo"> is removed from the template but stored as the "repo" function template. Any data object that specifies "repo" as its template name will use this function.
Use slash notation to create a function with multiple parts that are combined into a single DocumentFragment:
<template>
<div>
<div data-fn="user/1:$user" data-tme="> $name">name</div>
<div data-fn="user/2" data-tme="> $alignment">alignment</div>
<div data-fn="user/3" data-tme="> $age">age</div>
<div data-fn="user/4" data-tme="> $iq">iq</div>
</div>
</template>
{
"user": {
"name": "Harry",
"age": 31,
"iq": 133,
"alignment": "Chaotic Neutral"
}
}
All four user/* parts are combined into a single DocumentFragment, creating a layout with four separate <div> elements.
Result:
<div>Harry</div>
<div>Chaotic Neutral</div>
<div>31</div>
<div>133</div>
Use $^propertyName to access properties from parent objects:
<template>
<div>
<nav data-fn="user:$user">
<h1 data-tme="> $name"></h1>
<h1 data-tme="> $^title"></h1>
</nav>
</div>
</template>
{
"title": "Don't look up",
"user": {"name": "banana"}
}
The $^title refers to the title property in the parent object. Use $^^ for grandparent, $^^^ for great-grandparent, etc.
Result:
<nav>
<h1>banana</h1>
<h1>Don't look up</h1>
</nav>
Objects in an array can specify different templates:
{
"items": [
{"name": "Simple Item", "template": "simple"},
{"name": "Detailed Item", "count": 42, "template": "detailed"},
{"name": "Another Simple", "template": "simple"}
]
}
The data-tme attribute controls element-level manipulation. It always starts with < (replace element) or > (replace children).
Replace the entire element with data:
<pre data-tme="< $firstName">Default text</pre>
{"firstName": "Victor"}
Result: The <pre> element is replaced with the text "Victor".
Replace the element's children with data:
<h4>header</h4>
<pre data-tme="> $firstName">Default text</pre>
{"firstName": "Victor"}
Result:
<h4>header</h4>
<pre>Victor</pre>
Variables start with $ and refer to properties in the data object:
$name - Refers to data.name$^name - Refers to data.parent.name$^^name - Refers to data.parent.parent.nameUse = to compare values and conditionally render:
<template>
<ul>
<li data-tme="< $selected=apple">Item Apple</li>
<li data-tme="< $selected=banana">Item Banana</li>
<li data-tme="< $selected=cherry">Item Cherry</li>
</ul>
</template>
{"selected": "banana"}
Result: Only "Item Banana" is shown. Other elements are removed (replaced with empty DocumentFragment).
<ul>
<li>Item Banana</li>
</ul>
When using data-tme="< $variable", the value type matters:
null or false - Element is removed (replaced with empty DocumentFragment)true - No effect (element remains as-is)When the expression after < or > is empty, the variable evaluates to an empty string which is treated as missing/null, causing the element to be removed:
<span data-tme="<">This element stays</span>
{}
Result: The element is removed (replaced with empty DocumentFragment) because the missing variable evaluates to null.
Use semicolons to perform multiple operations:
<span data-tme="< $show1;> $content1"></span>
{"show1": true, "content1": "SURPRISE"}
First checks $show1 (keeps element if true), then replaces children with $content1.
Result:
<span>SURPRISE</span>
The data-tma attribute sets HTML attributes on elements.
<input type="text" data-tma="$firstName:placeholder">
{"firstName": "Enter your first name"}
Sets placeholder="Enter your first name" on the input.
Result:
<input type="text" placeholder="Enter your first name">
Use semicolons to set multiple attributes:
<input type="text" data-tma="$firstName:placeholder;$inputStyle:style">
{
"firstName": "Enter your first name",
"inputStyle": "border: solid 3px red"
}
Sets both placeholder and style attributes.
Result:
<input type="text" placeholder="Enter your first name" style="border: solid 3px red">
Use a special :props flag to set multiple attributes from an object:
<input type="text" data-tma="$inputProperties">
{
"inputProperties": {
":props": true,
"placeholder": "Enter your first name",
"style": "border: solid 3px red"
}
}
All properties (except those starting with :) are set as attributes.
The property className is automatically mapped to the class attribute:
<div data-tma="$divProperties">Content</div>
{
"divProperties": {
":props": true,
"className": "my-class another-class",
"id": "test-div"
}
}
Sets class="my-class another-class" and id="test-div".
Use .className syntax to add/remove classes based on boolean values:
<div data-tma="$isFirstActive:.active">Active Item</div>
<div data-tma="$isSecondActive:.active">Inactive Item</div>
{"isFirstActive": true, "isSecondActive": false}
Adds the active class to the first div, removes it from the second.
Result:
<div class="active">Active Item</div>
<div>Inactive Item</div>
Compare two values to conditionally add a class:
<li data-fn="repo:$repos" data-tma="$selectedRepo=$name:.test-title" data-tme="> $name"></li>
{
"repos": [
{"selectedRepo": "Apple", "name": "Blueberry"},
{"selectedRepo": "Apple", "name": "Apple"}
]
}
The test-title class is added only to the second item where selectedRepo equals name.
Result:
<li>Blueberry</li>
<li class="test-title">Apple</li>
Embed variable values in strings for event handlers:
<input type="text" data-tma="console.log($title, $name, $age, $married, $address):onclick">
{
"title": "Don't look up",
"name": "John",
"age": 48,
"address": null,
"married": true
}
Sets onclick="console.log(\"Don't look up\", \"John\", 48, true, null)". Variables are JSON-stringified and interpolated.
$value:attrName - Set attribute from variable$value:.className - Toggle class based on boolean$val1=$val2:.className - Toggle class based on comparison$props - Set multiple attributes from props objectliteral($var1, $var2):attrName - String interpolationDemo Mode is an interactive navigation system implemented in template-m-demo.js. It provides tab-based view switching with CSS class management.
Demo Mode uses CSS classes and data attributes to create an interactive multi-view interface without JavaScript frameworks.
data-tmd-viewdata-tmd-selectordata-tmd-view-controller and data-tmd-selector-controller<div data-tmd-view="users">
<h2>Users View</h2>
<!-- content -->
</div>
Defines a view named "users".
<button data-tmd-selector="users">Show Users</button>
Defines a selector that activates the "users" view when clicked.
Views can be nested. The findTmdMappings() function automatically detects parent-child relationships:
<div data-tmd-view="dashboard">
<div data-tmd-view="users">
<!-- nested view -->
</div>
</div>
Scans the document and builds a parent-child map of views:
const mapping = findTmdMappings();
// Returns: { "users": "dashboard", "dashboard": "root" }
Used to understand view hierarchy for navigation.
Dynamically generates CSS to show/hide views or selectors:
addStyles(["home", "users", "settings"], {
isView: true,
cssStyles: "display: none",
complement: false
});
Activates a view and all its parent views:
tmdTabClicked("users");
This sets:
data-tmd-selector-controller="users" on the selector's parentdata-tmd-view-controller="users" on the view's parentIt also recursively activates parent views.
tmdClass(el) - Gets the tmd-* class from an elementtmdClassChild(el) - Gets the non-select, non-selected tmd-* classtmdClassSelected(el) - Gets the tmd-*-selected classtmdClassParent(el) - Gets the tmd-select-* classfindAncestor(el, pred) - Finds ancestor matching predicatetmdFindInAncestors(el, f) - Searches ancestors with functionTypical usage involves:
data-tmd-* attributesfindTmdMappings() to build the hierarchyaddStyles() to set up visibility rulestmdTabClicked()// Initialize
window.tmdChildToParent = findTmdMappings();
const viewNames = Object.keys(window.tmdChildToParent);
addStyles(viewNames, {
isView: true,
cssStyles: "display: none",
complement: false
});
// Attach handlers
document.querySelectorAll('[data-tmd-selector]').forEach(el => {
el.addEventListener('click', () => {
tmdTabClicked(el.dataset.tmdSelector);
});
});
Demo Mode uses a specific CSS class naming pattern:
tmd-* - General demo classestmd-select-* - Parent/container classestmd-*-selected - Selected state classesThese classes work with the controller attributes to manage visibility and state.
<!-- Selectors -->
<div>
<button data-tmd-selector="home">Home</button>
<button data-tmd-selector="about">About</button>
</div>
<!-- Views -->
<div>
<div data-tmd-view="home">
<h2>Home Content</h2>
</div>
<div data-tmd-view="about">
<h2>About Content</h2>
</div>
</div>
<script>
// Initialize demo mode
window.tmdChildToParent = findTmdMappings();
addStyles(["home", "about"], {
isView: true,
cssStyles: "display: none",
complement: false
});
// Attach click handlers
document.querySelectorAll('[data-tmd-selector]').forEach(el => {
el.addEventListener('click', () => {
tmdTabClicked(el.dataset.tmdSelector);
});
});
// Show initial view
tmdTabClicked("home");
</script>
For more information:
test/template-m-tests.html for comprehensive test caseslib/template-m.js for implementation detailslib/template-m-demo.js for demo mode implementation