Now.js Framework Documentation
TemplateManager
TemplateManager
ภาพรวม
TemplateManager คือระบบ template และ data binding ใน Now.js Framework ประกอบด้วย:
- Template Loading - โหลด HTML template แบบ dynamic ผ่าน AJAX
- Data Binding - ผูกข้อมูลกับ DOM elements แบบ reactive
- JavaScript Initialization - เริ่มต้น JavaScript อัตโนมัติเมื่อ template โหลด
- Component Lifecycle - จัดการ cleanup เมื่อ template ถูกลบ
ใช้เมื่อ:
- ต้องการแยก HTML templates ออกจากหน้าหลัก
- ต้องการ reactive data binding ใน templates
- ต้องการ initialize JavaScript เมื่อ template โหลด
- ต้องการโครงสร้างโค้ดที่สะอาดและดูแลง่าย
ทำไมต้องใช้:
- ✅ ชุด data directives ที่ครอบคลุมทั้ง binding, lifecycle hooks และ declarative animations
- ✅ โหลด templates แบบ dynamic ผ่าน AJAX
- ✅ Auto-initialize JavaScript functions
- ✅ รองรับ cleanup เมื่อ templates ถูกลบ
- ✅ มีความปลอดภัยในตัว (sanitization, validation)
Template Directives
TemplateManager รองรับ directive มากกว่าชุด core เดิม 12 ตัว นอกจาก text, attribute, loop และ event bindings แล้วยังมี option binding, complex data rebinding, การแสดงไฟล์เดิม, lifecycle hooks และ declarative animations ด้วย
| Directive | คำอธิบาย | เอกสาร |
|---|---|---|
data-text |
ผูกเนื้อหาข้อความพร้อม formatters | data-text.md |
data-html |
ผูก HTML content พร้อม sanitization | data-html.md |
data-attr |
ผูก attributes (href, src ฯลฯ) | data-attr.md |
data-class |
ผูก CSS class (3 โหมด) | data-class.md |
data-style |
ผูก inline style | data-style.md |
data-if |
แสดงผลแบบมีเงื่อนไข | data-if.md |
data-for |
แสดงผลรายการ | data-for.md |
data-on |
จัดการ event (20+ modifiers) | data-on.md |
data-model |
Two-way binding ฟอร์ม | data-model.md |
data-checked |
ผูก checkbox/radio | data-checked.md |
data-options-key |
เติม options ให้ select/tag/autocomplete จาก context.options |
data-options-key.md |
data-bind |
ผูกข้อมูลเชิงโครงสร้างให้ TableManager / LineItemsManager |
data-bind.md |
data-files |
แสดงไฟล์เดิมบน file input | data-files.md |
data-container |
โหลด component แบบ dynamic | data-container.md |
data-script |
เริ่มต้น JavaScript | data-script.md |
data-on-load |
รัน logic หลัง block ถูก render พร้อมแล้ว | data-on-load.md |
data-show, data-enter, data-leave, data-transition |
animation directives แบบ declarative ที่ขับเคลื่อนด้วย AnimationManager | data-animation.md |
ดู ดัชนี Template Directives สำหรับการอ้างอิงด่วน
การใช้งานพื้นฐาน
Template กับ 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">บันทึก</button>
</form>
</main>Initialization Script
// main.js - Global function
function initProfile(element, data) {
console.log('โหลด Profile แล้ว!', element);
console.log('ข้อมูลจาก API:', data);
// Custom initialization
const input = element.querySelector('input[name="name"]');
input.addEventListener('focus', () => {
console.log('ฟิลด์ชื่อถูก focus');
});
// Return cleanup function (ไม่บังคับ)
return () => {
console.log('Cleanup Profile');
};
}การทำงาน:
- RouterManager โหลด
profile.html - TemplateManager ประมวลผล data directives ทั้งหมด
- ตรวจพบ
data-script="initProfile" - เรียก
window.initProfile(element, data) - เมื่อ navigate ออก เรียก cleanup function
ฟีเจอร์หลัก
1. Reactive Data Binding
การเปลี่ยนแปลงข้อมูลอัพเดท DOM อัตโนมัติ:
<span data-text="count"></span>
<button data-on="click:increment">+</button>let count = 0;
function increment() {
count++;
// DOM อัพเดทอัตโนมัติ
}2. Template Expressions
รองรับ expressions ใน bindings:
<!-- Text กับ 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">
ส่วนควบคุมแอดมิน
</div>arguments ของ formatter ใช้รูปแบบ string ที่คั่นด้วย : ตรงๆ เช่นควรใช้ currency:THB:th-TH:4 แทนรูปแบบที่มี quote ครอบ
3. Event Handling
Event binding แบบ declarative พร้อม modifiers:
<!-- Basic click -->
<button data-on="click:save">บันทึก</button>
<!-- กับ modifiers -->
<form data-on="submit.prevent:handleSubmit">
<input data-on="keydown.enter:search">
<div data-on="click.self:close">4. Auto-Initialization
Templates ที่มี data-script จะถูก initialize อัตโนมัติ:
<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 function เพื่อ cleanup เมื่อ template ถูกลบ:
function initTimer(element, data) {
const timer = setInterval(() => {
element.querySelector('#time').textContent = new Date().toLocaleTimeString();
}, 1000);
// ถูกเรียกเมื่อ navigate ออก
return () => clearInterval(timer);
}การใช้งานขั้นสูง
รับข้อมูลจาก API
เมื่อใช้กับ RouterManager ที่มี data-load-api:
// Router configuration
routes: {
'/dashboard': {
template: 'dashboard.html',
title: 'Dashboard',
'data-load-api': 'api/dashboard/stats'
}
}function initDashboard(element, data) {
// data มี API response
console.log(data); // { totalUsers: 150, activeUsers: 89, ... }
renderStats(data);
}หลาย Scripts
Templates สามารถมีหลาย data-script attributes:
<div class="page">
<main data-script="initMainContent">...</main>
<aside data-script="initSidebar">...</aside>
<footer data-script="initFooter">...</footer>
</div>Functions ถูกเรียกตามลำดับ DOM (บนลงล่าง)
Nested Templates
Templates สามารถมี templates อื่นๆ ได้:
<div data-container="'components/' + currentComponent + '.html'"></div>ดู data-container.md สำหรับรายละเอียด
ข้อควรระวัง
⚠️ 1. Function ต้องเป็น Global
// ❌ ไม่ทำงาน - Function อยู่ใน module scope
document.addEventListener('DOMContentLoaded', () => {
function initProfile(element, data) { }
});
// ✅ ถูก - Global function
function initProfile(element, data) { }
// ✅ ถูก - กำหนด global ชัดเจน
window.initProfile = function(element, data) { };⚠️ 2. Memory Leaks
// ❌ ไม่ดี - ไม่มี cleanup
function initTimer(element, data) {
setInterval(() => { /* ทำงานตลอดไป */ }, 1000);
}
// ✅ ดี - มี cleanup
function initTimer(element, data) {
const timer = setInterval(() => { /* ... */ }, 1000);
return () => clearInterval(timer);
}⚠️ 3. ตรวจสอบ Element ก่อน
// ❌ อาจ error
function initForm(element, data) {
element.querySelector('#input').value = data.value;
}
// ✅ ตรวจสอบก่อน
function initForm(element, data) {
const input = element.querySelector('#input');
if (input) input.value = data.value || '';
}⚠️ 4. Template path ที่อยู่นอก paths.templates — ใช้ ../ ไม่ได้
Now.resolvePath() ตัด ../ ทั้งหมดออก เพื่อความปลอดภัย
ไม่สามารถออกจาก paths.templates ด้วย relative traversal ได้
// ❌ ../ ถูกตัดออก → resolve ไปเส้นทางผิด
template: `${currentDir}../Widgets/facebook/index.html`
// ผลลัพธ์: /nowjs-gcms/admin/templates/Widgets/facebook/index.html ← ผิดให้ใช้ http:// URL แบบเต็ม แทน — TemplateManager จะตรวจพบและข้าม resolvePath ทั้งหมด:
// ✅ คำนวณ widgetsDir ครั้งเดียวใน main.js
const widgetsDir =
window.location.origin +
currentDir.replace(/[^/]+\/$/, '') + // ตัด "admin/" → project root
'Widgets/';
// widgetsDir = 'http://localhost/nowjs-gcms/Widgets/'
// จากนั้นใช้ใน route definition
routes: {
'/widgets/:module': {
template: `${widgetsDir}:module/index.html`,
// fetch: http://localhost/nowjs-gcms/Widgets/facebook/index.html ✅
}
}ความปลอดภัยยังคงอยู่: validateRequest() ตรวจสอบ allowedOrigins (default = window.location.origin)
ดังนั้น URL template ข้าม domain จะถูกบล็อกเสมอ
⚠️ 5. isValidPath ปฏิเสธ absolute URL (พฤติกรรมเดิม)
ก่อนการแก้ไข isValidPath จะคืนค่า false สำหรับ path ที่ไม่ขึ้นต้นด้วย /
ดังนั้น http:// template URL จะ throw "Invalid template path format" แม้ว่าจะแก้ bug else if prepend แล้วก็ตาม
ปัจจุบัน isValidPath รองรับ absolute URL ผ่าน branch เฉพาะที่เรียก isValidUrl() และตรวจสอบ extension ของไฟล์:
// ภายใน (TemplateManager.isValidPath):
if (/^https?:\/\//i.test(path)) {
return this.isValidUrl(path) &&
this.config.security.allowedExtensions.some(ext => path.toLowerCase().endsWith(ext));
}ไม่ต้องทำอะไรเพิ่มเติมจาก application code — จัดการให้อัตโนมัติแล้ว
แนวปฏิบัติที่ดี
1. ชื่อ Function ที่ชัดเจน
// ✅ ดี
function initUserProfileForm(element, data) { }
function initSalesChart(element, data) { }
function initProductGallery(element, data) { }2. แยก 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');
// Initialize...
} catch (error) {
console.error('Init ล้มเหลว:', error);
element.innerHTML = '<p class="error">โหลดล้มเหลว</p>';
}
}การผสานระบบ
กับ FormManager
<form data-form="editprofile" data-script="initProfileForm" data-load-api="api/users/get">
<!-- FormManager จัดการฟอร์ม, data-script เพิ่ม logic เอง -->
</form>กับ ComponentManager
<div data-component="sidebar" data-script="initSidebar"></div>กับ ApiComponent
<div data-api-component="userList" data-script="initUserList" data-api-url="api/users/list">
</div>เอกสารที่เกี่ยวข้อง
เอกสาร Directive
- ดัชนี Template Directives - อ้างอิงด่วนสำหรับทุก directives
- data-script - รายละเอียดการ initialize script
ระบบอื่นๆ
- RouterManager - Routing และการโหลด template
- FormManager - การจัดการฟอร์ม
- ComponentManager - ระบบ Component
- ApiComponent - Components ที่ขับเคลื่อนด้วย API