AppConfigManager
ภาพรวม
AppConfigManager คือระบบจัดการ theme และ site configuration ใน Now.js Framework รองรับ:
- Theme Management: Light/Dark mode switching พร้อม system preference
- Site Metadata: จัดการข้อมูล title, description, logo, contact, social
- CSS Variables: โหลดและจัดการ CSS custom properties จาก backend
- Auto-enhanced components: Toggle buttons (
data-component="theme" หรือ data-component="config")
- data-on-load: Execute script หลังโหลด config เสร็จ
- Smooth transitions: Anti-FOUC พร้อม fade animations
- Backend API integration: โหลด config ครั้งเดียวได้ทั้ง theme + site metadata
การใช้งานพื้นฐาน
Initialization
await Now.init({
config: {
enabled: true,
defaultTheme: 'light',
storageKey: 'app_theme',
systemPreference: true,
// โหลด theme + site metadata จาก API
api: {
enabled: true,
configUrl: '/api/config',
cacheResponse: true
}
}
});
{
"variables": {
"--color-primary": "#29336b",
"--logo-url": "/images/logo.png"
},
"site": {
"title": "ร้านหนังสือสุวิมล",
"description": "หนังสือเก่า สะสม และหายาก",
"logo": "/images/logo.png",
"favicon": "/images/favicon.ico",
"lang": "th",
"contact": {
"email": "info@example.com",
"phone": "02-xxx-xxxx",
"address": "123 ถนนพระราม 4"
},
"social": {
"facebook": "https://facebook.com/...",
"line": "@example",
"twitter": "@example"
}
}
}
Toggle Theme
AppConfigManager.toggle(); // สลับ light ↔ dark
await AppConfigManager.setTheme('dark'); // ตั้งค่าเฉพาะเจาะจง
const theme = AppConfigManager.getCurrentTheme();
// ดึงข้อมูลทั้งหมด
const site = AppConfigManager.getSiteMetadata();
console.log(site.title); // 'ร้านหนังสือสุวิมล'
console.log(site.description); // 'หนังสือเก่า สะสม และหายาก'
// ดึงแบบ specific (รองรับ dot notation)
const title = AppConfigManager.getSite('title');
const email = AppConfigManager.getSite('contact.email');
const facebook = AppConfigManager.getSite('social.facebook');
// ใช้ผ่าน window.siteConfig (auto-exported)
console.log(window.siteConfig.title);
console.log(window.siteConfig.contact.email);
// Apply ข้อมูลใหม่ทั้งหมด
AppConfigManager.applySiteMetadata({
title: 'ชื่อใหม่',
description: 'คำอธิบายใหม่',
logo: '/new-logo.png'
});
// Update แบบ partial (merge กับข้อมูลเดิม)
AppConfigManager.updateSiteMetadata({
title: 'ชื่อใหม่',
contact: {
phone: '02-999-9999' // อัพเดทแค่ phone
}
});
ใช้ใน Template
<!-- ใช้ผ่าน window.siteConfig -->
<header>
<img data-attr="src:siteConfig.logo" alt="Logo">
<h1 data-text="siteConfig.title"></h1>
<p data-text="siteConfig.description"></p>
</header>
<footer>
<p>Email: <span data-text="siteConfig.contact.email"></span></p>
<p>Tel: <span data-text="siteConfig.contact.phone"></span></p>
<a data-attr="href:siteConfig.social.facebook">Facebook</a>
<a data-attr="href:siteConfig.social.line">Line</a>
</footer>
data-on-load Support
Execute script หลัง config โหลดเสร็จ (เหมือน ApiComponent):
<body data-on-load="console.log('Config loaded!', site, theme, config)">
<!--
Available variables:
- site: site metadata object
- theme: current theme ('light' or 'dark')
- config: full config { site, theme, variables }
-->
</body>
ตัวอย่างการใช้งาน:
<body data-on-load="
document.title = site.title;
updateHeaderLogo(site.logo);
initSocialLinks(site.social);
">
HTML
<!-- Auto-enhanced - ไม่ต้องเขียน JS -->
<button data-component="theme">🌓</button>
<!-- หรือใช้ชื่อใหม่ -->
<button data-component="config">🌓</button>
AppConfigManager จะ enhance ปุ่มอัตโนมัติ:
- ✅ เพิ่ม click handler
- ✅ Sync
data-theme-state attribute
- ✅ ทุกปุ่มจะ sync เมื่อ theme เปลี่ยน
/* Icon font setup */
[data-component="theme"]:before,
[data-component="config"]:before {
font-family: 'icomoon' !important;
}
/* แสดง icon ตาม theme */
[data-component="theme"][data-theme-state="light"]:before,
[data-component="config"][data-theme-state="light"]:before {
content: "\e929"; /* icon-night */
}
[data-component="theme"][data-theme-state="dark"]:before,
[data-component="config"][data-theme-state="dark"]:before {
content: "\e9d4"; /* icon-day */
}
หรือใช้ CSS classes:
[data-theme-state="light"] .icon-light { display: none; }
[data-theme-state="dark"] .icon-dark { display: none; }
- ✅ ปุ่มทั้งหมดจะซิงค์เมื่อมีการเปลี่ยนธีม
CSS สำหรับปุ่มสลับธีม
/* Icon font setup */
[data-component="theme"]:before {
font-family: 'icomoon' !important;
}
/* Show icon based on theme */
[data-component="theme"][data-theme-state="light"]:before {
content: "\e929"; /* icon-night */
}
[data-component="theme"][data-theme-state="dark"]:before {
content: "\e9d4"; /* icon-day */
}
หรือจะใช้คลาสของ CSS แทน:
[data-theme-state="light"] .icon-light { display: none; }
[data-theme-state="dark"] .icon-dark { display: none; }
การตั้งค่าเต็มรูปแบบ
await Now.init({
config: {
enabled: true,
defaultTheme: 'light',
storageKey: 'app_theme',
systemPreference: true,
// Smooth Transitions
transition: {
enabled: true,
duration: 300,
hideOnSwitch: true,
loadingClass: 'theme-loading',
readyClass: 'theme-ready',
transitionClass: 'theme-transitioning'
},
// Backend API - โหลด theme + site metadata
api: {
enabled: true,
configUrl: '/api/config',
headers: {},
timeout: 5000,
cacheResponse: true
}
}
});
Backend API Integration
โหลด Config จาก API
// โหลดอัตโนมัติตอน init (ถ้า api.enabled = true)
await Now.init({ config: { api: { enabled: true, configUrl: '/api/config' } } });
// หรือโหลดเอง
await AppConfigManager.loadFromAPI('/api/config');
// Refresh (ล้าง cache + โหลดใหม่)
await AppConfigManager.refreshFromAPI();
{
"variables": {
"--color-primary": "#6366f1",
"--color-background": "#0f172a",
"--logo-url": "/images/logo.png",
"--hero-bg": "/images/hero.jpg"
},
"site": {
"title": "My Site",
"description": "Site description",
"logo": "/images/logo.png",
"favicon": "/images/favicon.ico",
"lang": "th",
"contact": {
"email": "info@example.com",
"phone": "02-xxx-xxxx"
},
"social": {
"facebook": "https://facebook.com/...",
"line": "@example"
}
}
}
หมายเหตุ:
variables - CSS custom properties (optional)
site - Site metadata (optional)
- API ไม่ควบคุม light/dark mode - ผู้ใช้ควบคุมเอง
CSS Variables
Apply Variables
AppConfigManager.applyVariables({
'--color-primary': '#6366f1',
'--color-background': '#0f172a',
'--hero-bg': '/images/hero.jpg' // Auto-wrap เป็น url()
});
Security
- อนุญาตเฉพาะ CSS Custom Properties (
--*)
- ลบ patterns อันตราย (
javascript:, expression(), <script>)
- URLs: รองรับ local paths หรือ absolute URLs จากโดเมนเดียวกัน
- โดเมนภายนอกถูกบล็อกโดย default
- Extensions ต้องเป็นรูปภาพ (ไม่มี SVG)
Smooth Transitions
CSS ที่ต้องใช้
body {
opacity: 0;
transition: opacity 0.3s ease;
}
body.theme-ready {
opacity: 1;
}
body.theme-transitioning {
opacity: 0;
}
วิธีทำงาน
body เริ่มต้นที่ opacity: 0
- AppConfigManager โหลด theme
- เพิ่ม class
theme-ready → fade in
- เมื่อ switch → fade out → apply → fade in
API Reference
Theme Methods
| Method |
คำอธิบาย |
|---|
toggle() |
สลับ theme (light ↔ dark) |
setTheme(theme) |
ตั้งค่า theme ('light' หรือ 'dark') |
getCurrentTheme() |
รับ theme ปัจจุบัน |
| Method |
คำอธิบาย |
|---|
getSiteMetadata() |
รับ site metadata ทั้งหมด |
getSite(key) |
รับค่าเดียว (รองรับ dot notation) |
applySiteMetadata(data) |
Apply metadata ใหม่ |
updateSiteMetadata(updates) |
Update แบบ partial (merge) |
exportToWindow() |
Export to window.siteConfig |
CSS Variables Methods
| Method |
คำอธิบาย |
|---|
applyVariables(vars) |
Apply CSS Variables |
clearVariables() |
ล้าง CSS Variables |
getAppliedVariables() |
ดึง variables ที่ใช้ |
API Methods
| Method |
คำอธิบาย |
|---|
loadFromAPI(url?) |
โหลดจาก API |
refreshFromAPI() |
โหลดใหม่ (ล้าง cache) |
Lifecycle Methods
| Method |
คำอธิบาย |
|---|
init(options) |
Initialize manager |
destroy() |
Cleanup ทั้งหมด |
reset() |
Reset state |
Events
Theme Events
| Event |
เมื่อเกิด |
Payload |
|---|
theme:initialized |
Init เสร็จ |
{ theme } |
theme:changed |
Theme เปลี่ยน |
{ theme } |
theme:ready |
พร้อมแสดงผล |
- |
Config/Site Events
| Event |
เมื่อเกิด |
Payload |
|---|
config:site-loaded |
โหลด site metadata สำเร็จ |
{ site } |
config:site-updated |
Update metadata สำเร็จ |
{ site, updates } |
config:exported |
Export to window สำเร็จ |
{ siteConfig } |
config:on-load-executed |
data-on-load executed |
{ body, script } |
config:on-load-error |
data-on-load error |
{ error, script } |
Variables Events
| Event |
เมื่อเกิด |
Payload |
|---|
theme:variables-applied |
Apply variables เสร็จ |
{ variables } |
theme:variables-cleared |
ล้าง variables เสร็จ |
- |
API Events
| Event |
เมื่อเกิด |
Payload |
|---|
theme:api-loaded |
โหลดจาก API สำเร็จ |
{ config, fromCache } |
theme:api-error |
API error |
{ error, url } |
Lifecycle Events
| Event |
เมื่อเกิด |
|---|
theme:destroyed |
Cleanup เสร็จ |
theme:reset |
Reset เสร็จ |
// ตัวอย่างการใช้งาน
EventManager.on('theme:changed', ({ theme }) => {
console.log('เปลี่ยนเป็น:', theme);
});
EventManager.on('config:site-loaded', ({ site }) => {
console.log('Site loaded:', site.title);
document.title = site.title;
});
EventManager.on('config:site-updated', ({ site, updates }) => {
console.log('Updated:', updates);
});
Cleanup
// Cleanup สมบูรณ์ (unmount app)
AppConfigManager.destroy();
// Reset เบาๆ (re-init)
AppConfigManager.reset();
await AppConfigManager.init({ ... });
ข้อควรระวัง
⚠️ 1. Enable ก่อนใช้
// ❌ ยังไม่ enable
AppConfigManager.toggle();
// ✅ Enable ก่อน
await Now.init({ config: { enabled: true } });
AppConfigManager.toggle();
⚠️ 2. ใช้ CSS Variables
/* ❌ ใช้ค่าคงที่ */
body { background: white; }
/* ✅ ใช้ variables */
body { background: var(--color-background); }
// ✅ ถูกต้อง - site เป็น object
{
"variables": {...},
"site": { "title": "...", "contact": {...} }
}
// ❌ ผิด - site เป็น array
{
"site": [...]
}
⚠️ 4. Global vs Page-specific Data
// ✅ Global data → AppConfigManager
// - Site title, logo, description
// - Contact info, social links
// - Theme CSS variables
// ✅ Page-specific data → apiComponent
// - Product list/detail
// - User profile
// - Order history
⚠️ 5. เปิดผ่าน HTTP/HTTPS
// ❌ file:// protocol ใช้ API ไม่ได้
file:///path/to/index.html
// ✅ http(s):// protocol
http://localhost/...
https://example.com/...
⚠️ 6. Backward Compatibility
// ✅ ทั้งสองแบบใช้ได้
window.AppConfigManager
window.ThemeManager // Alias
Now.init({ config: {...} }) // แนะนำ
Now.init({ theme: {...} }) // ยังใช้ได้
Use Cases
1. Multi-language Site
const lang = AppConfigManager.getSite('lang'); // 'th' or 'en'
// เปลี่ยนภาษา
AppConfigManager.updateSiteMetadata({
lang: 'en',
title: 'Bookstore',
description: 'Old & Rare Books'
});
2. Dynamic SEO
EventManager.on('config:site-loaded', ({ site }) => {
// Auto-update เมื่อโหลด
document.title = site.title;
const metaDesc = document.querySelector('meta[name="description"]');
if (metaDesc) metaDesc.content = site.description;
});
3. White-label / Multi-tenant
// แต่ละ tenant มี config ต่างกัน
await AppConfigManager.init({
api: {
enabled: true,
configUrl: `/api/config?tenant=${tenantId}`
}
});
// tenant A: logo A, colors A, contact A
// tenant B: logo B, colors B, contact B
4. Theme-based Branding
EventManager.on('theme:changed', ({ theme }) => {
const logo = theme === 'dark'
? '/images/logo-dark.png'
: '/images/logo-light.png';
AppConfigManager.updateSiteMetadata({ logo });
});
ก่อน (แยก API)
/api/theme → 150ms (CSS variables)
/api/site/info → 120ms (header)
/api/site/info → 120ms (footer, cached)
────────────────────────────────
Total: 270ms + 2-3 HTTP requests
หลัง (Combined API)
/api/config → 180ms (theme + site)
────────────────────────────────
Total: 180ms + 1 HTTP request
Savings: 90ms + 1-2 requests ⚡
เอกสารที่เกี่ยวข้อง