Now.js Framework Documentation
TemplateManager
TemplateManager
Overview
TemplateManager is the template and data binding system in the Now.js Framework. It enables:
- Template Loading - Dynamic HTML template loading via AJAX
- Data Binding - Reactive binding of data to DOM elements
- JavaScript Initialization - Auto-initialization of JavaScript when templates load
- Component Lifecycle - Cleanup functions when templates are removed
When to use:
- Need to separate HTML templates from the main page
- Want reactive data binding in templates
- Need to initialize JavaScript when templates load
- Want clean and maintainable code structure
Why use it:
- ✅ 12 powerful data directives for binding
- ✅ Loads templates dynamically via AJAX
- ✅ Auto-initializes JavaScript functions
- ✅ Supports cleanup when templates are removed
- ✅ Built-in security (sanitization, validation)
Data Directives
TemplateManager provides 12 data directives for binding data to elements:
| Directive | Description | Documentation |
|---|---|---|
data-text |
Text content binding with formatters | data-text.md |
data-html |
HTML content binding with sanitization | data-html.md |
data-attr |
Attribute binding (href, src, etc.) | data-attr.md |
data-class |
CSS class binding (3 modes) | data-class.md |
data-style |
Inline style binding | data-style.md |
data-if |
Conditional rendering | data-if.md |
data-for |
List rendering | data-for.md |
data-on |
Event handling (20+ modifiers) | data-on.md |
data-model |
Two-way form binding | data-model.md |
data-checked |
Checkbox/radio binding | data-checked.md |
data-container |
Dynamic component loading | data-container.md |
data-script |
JavaScript initialization | data-script.md |
See Template Directives Index for quick reference.
Basic Usage
Template with Data Binding
<!-- profile.html -->
<main class="content" data-script="initProfile">
<h1 data-text="user.name"></h1>
<p data-text="user.email"></p>
<ul data-for="skill in user.skills">
<template>
<li data-text="skill"></li>
</template>
</ul>
<form data-form="editprofile">
<input type="text" name="name" data-model="user.name">
<button type="submit">Save</button>
</form>
</main>Initialization Script
// main.js - Global function
function initProfile(element, data) {
console.log('Profile loaded!', element);
console.log('Data from API:', data);
// Custom initialization
const input = element.querySelector('input[name="name"]');
input.addEventListener('focus', () => {
console.log('Name field focused');
});
// Return cleanup function (optional)
return () => {
console.log('Profile cleanup');
};
}How it works:
- RouterManager loads
profile.html - TemplateManager processes all data directives
- Detects
data-script="initProfile" - Calls
window.initProfile(element, data) - When navigating away, calls cleanup function
Main Features
1. Reactive Data Binding
Data changes automatically update the DOM:
<span data-text="count"></span>
<button data-on="click:increment">+</button>let count = 0;
function increment() {
count++;
// DOM updates automatically
}2. Template Expressions
Support for expressions in bindings:
<!-- Text with formatters -->
<span data-text="price | currency:'THB'"></span>
<!-- Computed class -->
<div data-class="count > 0 ? 'has-items' : 'empty'"></div>
<!-- Conditional rendering -->
<div data-if="user.isAdmin && permissions.edit">
Admin controls
</div>3. Event Handling
Declarative event binding with modifiers:
<!-- Basic click -->
<button data-on="click:save">Save</button>
<!-- With modifiers -->
<form data-on="submit.prevent:handleSubmit">
<input data-on="keydown.enter:search">
<div data-on="click.self:close">4. Auto-Initialization
Templates with data-script are automatically initialized:
<div class="dashboard" data-script="initDashboard">
<div id="chart"></div>
</div>function initDashboard(element, data) {
const chart = new Chart(element.querySelector('#chart'), {
type: 'bar',
data: data.chartData
});
return () => chart.destroy();
}5. Cleanup Functions
Return a function to clean up when template is removed:
function initTimer(element, data) {
const timer = setInterval(() => {
element.querySelector('#time').textContent = new Date().toLocaleTimeString();
}, 1000);
// Called when navigating away
return () => clearInterval(timer);
}Advanced Usage
Receiving Data from API
When used with RouterManager that has data-load-api:
// Router configuration
routes: {
'/dashboard': {
template: 'dashboard.html',
title: 'Dashboard',
'data-load-api': 'api/dashboard/stats'
}
}function initDashboard(element, data) {
// data contains API response
console.log(data); // { totalUsers: 150, activeUsers: 89, ... }
renderStats(data);
}Multiple Scripts
Templates can have multiple data-script attributes:
<div class="page">
<main data-script="initMainContent">...</main>
<aside data-script="initSidebar">...</aside>
<footer data-script="initFooter">...</footer>
</div>Functions are called in DOM order (top to bottom).
Nested Templates
Templates can contain other templates:
<div data-container="'components/' + currentComponent + '.html'"></div>See data-container.md for details.
Common Pitfalls
⚠️ 1. Function Must Be Global
// ❌ Won't work - Function in module scope
document.addEventListener('DOMContentLoaded', () => {
function initProfile(element, data) { }
});
// ✅ Correct - Global function
function initProfile(element, data) { }
// ✅ Correct - Explicit global
window.initProfile = function(element, data) { };⚠️ 2. Memory Leaks
// ❌ Bad - No cleanup
function initTimer(element, data) {
setInterval(() => { /* runs forever */ }, 1000);
}
// ✅ Good - With cleanup
function initTimer(element, data) {
const timer = setInterval(() => { /* ... */ }, 1000);
return () => clearInterval(timer);
}⚠️ 3. Check Elements Exist
// ❌ May error
function initForm(element, data) {
element.querySelector('#input').value = data.value;
}
// ✅ Check first
function initForm(element, data) {
const input = element.querySelector('#input');
if (input) input.value = data.value || '';
}Best Practices
1. Clear Function Names
// ✅ Good
function initUserProfileForm(element, data) { }
function initSalesChart(element, data) { }
function initProductGallery(element, data) { }2. Separate Helper Functions
function initDashboard(element, data) {
const cleanup1 = initStats(element, data.stats);
const cleanup2 = initChart(element, data.chart);
return () => {
cleanup1?.();
cleanup2?.();
};
}3. Event Delegation
function initList(element, data) {
const list = element.querySelector('#list');
const handleClick = (e) => {
if (e.target.matches('.btn-delete')) {
handleDelete(e.target.dataset.id);
}
};
list.addEventListener('click', handleClick);
return () => list.removeEventListener('click', handleClick);
}4. Error Handling
function initDataTable(element, data) {
try {
const table = element.querySelector('#table');
if (!table) throw new Error('Table not found');
// Initialize...
} catch (error) {
console.error('Init failed:', error);
element.innerHTML = '<p class="error">Failed to load</p>';
}
}Integration
With FormManager
<form data-form="editprofile" data-script="initProfileForm" data-load-api="api/users/get">
<!-- FormManager handles form, data-script adds custom logic -->
</form>With ComponentManager
<div data-component="sidebar" data-script="initSidebar"></div>With ApiComponent
<div data-api-component="userList" data-script="initUserList" data-api-url="api/users/list">
</div>Related Documentation
Directive Documentation
- Template Directives Index - Quick reference for all directives
- data-script - Detailed script initialization docs
Other Systems
- RouterManager - Routing and template loading
- FormManager - Form management
- ComponentManager - Component system
- ApiComponent - API-driven components