Now.js Framework Documentation

Now.js Framework Documentation

TemplateManager

EN 11 Dec 2025 11:57

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:

  1. RouterManager loads profile.html
  2. TemplateManager processes all data directives
  3. Detects data-script="initProfile"
  4. Calls window.initProfile(element, data)
  5. 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>

Directive Documentation

Other Systems