Getting Started

Learn how to use Template-M's function-based DOM templating in your projects.

Installation

Include Template-M in your HTML file:

<script src="/lib/template-m.js"></script>

That's it! Template-M has no dependencies and works directly in the browser.

Core Concepts

Templates are Functions

In Template-M, templates are defined using the data-fn attribute and act as reusable functions. These template functions are extracted from the HTML and can be applied to data.

JSON Objects are Function Applications

When you pass JSON data to Template-M, objects with a template property become function applications - they specify which template function to use.

Three Core Attributes

Your First Template

Let's start with a simple example:

<!-- Define a template -->
<template id="greeting">
  <div>
    <h1 data-tme="> $name"></h1>
  </div>
</template>

<!-- Target element -->
<div id="output"></div>

<script>
  const data = { name: "World" };
  templateM("greeting", data, "output");
</script>

Result: <div><h1>World</h1></div>

The data-tme="> $name" attribute means "replace the children (>) of this element with the value of $name".

Element vs Children Replacement

The data-tme attribute uses two operators:

> - Replace Children

Replaces the element's children with the data value:

<template id="example">
  <h1 data-tme="> $title">Default</h1>
</template>

<script>
  templateM("example", { title: "Hello" });
</script>

Result:

<h1>Hello</h1>

< - Replace Element

Replaces the entire element with the data value:

<template id="example">
  <div>
    <span data-tme="< $content">Default</span>
  </div>
</template>

<script>
  templateM("example", { content: "Hello" });
</script>

Result:

<div>Hello</div>

The <span> element is replaced with just the text "Hello".

Template Functions

Define reusable template functions with data-fn:

<template id="movie">
  <div>
    <h2 data-tme="> $title"></h2>
    <p data-tme="> $metadata"></p>

    <!-- Define a "genre" function -->
    <div data-fn="genre">
      <span>Genre: </span>
      <strong data-tme="> $name"></strong>
    </div>
  </div>
</template>

<script>
  const data = {
    title: "The Master",
    metadata: {
      name: "Drama",
      template: "genre"  // Apply the "genre" function
    }
  };

  templateM("movie", data, "output");
</script>

Result:

<div>
  <h2>The Master</h2>
  <p>
    <div>
      <span>Genre: </span>
      <strong>Drama</strong>
    </div>
  </p>
</div>

The metadata object is a function application - its template property specifies which template function to use. The genre function is applied and its result is inserted where $metadata appears.

Working with Arrays

Template-M automatically handles arrays by using the singular form of the property name as the template:

<template id="repo-list">
  <ul>
    <li data-fn="repo:$repos" data-tme="> $name"></li>
  </ul>
</template>

<script>
  const data = {
    repos: [
      { name: "template-m" },
      { name: "example-app" },
      { name: "docs" }
    ]
  };

  templateM("repo-list", data, "output");
</script>

Result:

<ul>
  <li>template-m</li>
  <li>example-app</li>
  <li>docs</li>
</ul>

The data-fn="repo:$repos" syntax means:

Accessing Parent Properties

Use $^propertyName to access properties from parent objects:

<template id="page">
  <div>
    <h1 data-tme="> $pageTitle"></h1>

    <div data-fn="user:$user">
      <p>Name: <span data-tme="> $name"></span></p>
      <p>Page: <span data-tme="> $^pageTitle"></span></p>
    </div>
  </div>
</template>

<script>
  const data = {
    pageTitle: "User Dashboard",
    user: { name: "Alice" }
  };

  templateM("page", data, "output");
</script>

Result:

<div>
  <h1>User Dashboard</h1>
  <div>
    <p>Name: <span>Alice</span></p>
    <p>Page: <span>User Dashboard</span></p>
  </div>
</div>

The $^pageTitle accesses the parent object's pageTitle property. Use $^^property for grandparent access, and so on.

Conditional Rendering

Combine < (element replacement) with comparisons for conditional rendering:

<template id="menu">
  <ul>
    <li data-tme="< $selected=home">Home</li>
    <li data-tme="< $selected=about">About</li>
    <li data-tme="< $selected=contact">Contact</li>
  </ul>
</template>

<script>
  templateM("menu", { selected: "about" }, "output");
</script>

Result:

<ul>
  <li>About</li>
</ul>

Only the "About" item is rendered. Elements where the comparison is false are replaced with an empty DocumentFragment (removed).

You can also use boolean values:

<template id="greeting">
  <div>
    <p data-tme="< $isLoggedIn">Welcome back!</p>
  </div>
</template>

<script>
  templateM("greeting", { isLoggedIn: true }, "output");
</script>

Result:

<div>
  <p>Welcome back!</p>
</div>

If isLoggedIn is false or null, the <p> element would be removed.

Setting Attributes

Use data-tma to set element attributes:

Basic Attribute Setting

<template id="input-form">
  <input type="text" data-tma="$placeholder:placeholder">
</template>

<script>
  const data = { placeholder: "Enter your name" };
  templateM("input-form", data, "output");
</script>

Result:

<input type="text" placeholder="Enter your name">

Multiple Attributes

Separate multiple attributes with semicolons:

<template id="styled-input">
  <input data-tma="$placeholder:placeholder;$customStyle:style">
</template>

<script>
  const data = {
    placeholder: "Enter your name",
    customStyle: "border: 2px solid blue"
  };
  templateM("styled-input", data, "output");
</script>

Result:

<input placeholder="Enter your name" style="border: 2px solid blue">

Class Toggling

Use .className to add/remove classes based on boolean values:

<template id="item">
  <div data-tma="$isActive:.active">Item</div>
</template>

<script>
  templateM("item", { isActive: true }, "output");
</script>

Result:

<div class="active">Item</div>

If isActive is false, the class would not be added.

Event Handlers with Interpolation

<template id="button">
  <button data-tma="handleClick($id, $name):onclick">Click Me</button>
</template>

<script>
  const data = { id: 42, name: "Example" };
  templateM("button", data, "output");
</script>

Result:

<button onclick="handleClick(42, "Example")">Click Me</button>

Variables are JSON-stringified and interpolated into the handler string.

Advanced Features

Props Objects

Set multiple attributes from an object with :props:

<template id="styled-div">
  <div data-tma="$properties">Content</div>
</template>

<script>
  const data = {
    properties: {
      ":props": true,
      "id": "my-div",
      "className": "container active",
      "data-value": "123"
    }
  };
  templateM("styled-div", data, "output");
</script>

Result:

<div id="my-div" class="container active" data-value="123">Content</div>

Multipart Functions

Combine multiple template parts into a single function using / notation:

<template id="user-card">
  <div>
    <div data-fn="user/1:$user" data-tme="> $name">Name</div>
    <div data-fn="user/2" data-tme="> $email">Email</div>
    <div data-fn="user/3" data-tme="> $role">Role</div>
  </div>
</template>

<script>
  const data = {
    user: { name: "Alice", email: "alice@example.com", role: "Admin" }
  };
  templateM("user-card", data, "output");
</script>

Result:

<div>
  <div>Alice</div>
  <div>alice@example.com</div>
  <div>Admin</div>
</div>

All parts (user/1, user/2, user/3) are combined into a single DocumentFragment for the user function.

Fragment-Only Templates

Use > prefix in data-fn to create templates that only include children:

<template id="file-list">
  <dl data-tme="> $files"></dl>

  <div data-fn="> file">
    <dt data-tme="> $name"></dt>
    <dd data-tme="> $value"></dd>
  </div>
</template>

<script>
  const data = {
    files: [
      { name: "readme.txt", value: "Documentation" },
      { name: "index.js", value: "Main file" }
    ]
  };
  templateM("file-list", data, "output");
</script>

Result:

<dl>
  <dt>readme.txt</dt>
  <dd>Documentation</dd>
  <dt>index.js</dt>
  <dd>Main file</dd>
</dl>

The wrapper <div> is not included - only its children (<dt> and <dd>) are part of the template.

Functions Without Slots

Define a function without placing it in the template:

<template id="list">
  <ul>
    <div data-tme="> $repos"></div>
    <li data-fn="repo" data-tme="> $name"></li>
  </ul>
</template>

<script>
  const data = {
    repos: [{ name: "A" }, { name: "B" }]
  };
  templateM("list", data, "output");
</script>

Result:

<ul>
  <div>
    <li>A</li>
    <li>B</li>
  </div>
</ul>

The <li> with data-fn is extracted as a function template but doesn't create a slot in the main template. The array is rendered using that template and inserted where $repos appears.

API Reference

templateM(templateId, data, targetElementId)

Renders a template and replaces the target element.

templateMRender(templateId, data)

Renders a template and returns a DocumentFragment (without replacing any element).

Next Steps