AppConfigManager
Overview
AppConfigManager is the theme and site configuration management system in Now.js Framework. It supports:
- Theme Management: Light/Dark mode switching with system preference detection
- Site Metadata: Manage title, description, logo, contact, social information
- CSS Variables: Load and manage CSS custom properties from backend
- Auto-enhanced components: Toggle buttons (
data-component="theme" or data-component="config")
- data-on-load: Execute scripts after config is loaded
- Smooth transitions: Anti-FOUC with fade animations
- Backend API integration: Load both theme + site metadata in a single request
Basic Usage
Initialization
await Now.init({
config: {
enabled: true,
defaultTheme: 'light',
storageKey: 'app_theme',
systemPreference: true,
// Load theme + site metadata from API
api: {
enabled: true,
configUrl: '/api/config',
cacheResponse: true
}
}
});
{
"variables": {
"--color-primary": "#29336b",
"--logo-url": "/images/logo.png"
},
"site": {
"title": "Bookstore",
"description": "Old & Rare Books",
"logo": "/images/logo.png",
"favicon": "/images/favicon.ico",
"lang": "en",
"contact": {
"email": "info@example.com",
"phone": "+1-xxx-xxxx",
"address": "123 Main St"
},
"social": {
"facebook": "https://facebook.com/...",
"line": "@example",
"twitter": "@example"
}
}
}
Toggle Theme
AppConfigManager.toggle(); // Switch light ↔ dark
await AppConfigManager.setTheme('dark'); // Set specific theme
const theme = AppConfigManager.getCurrentTheme();
// Get all metadata
const site = AppConfigManager.getSiteMetadata();
console.log(site.title); // 'Bookstore'
console.log(site.description); // 'Old & Rare Books'
// Get specific value (dot notation supported)
const title = AppConfigManager.getSite('title');
const email = AppConfigManager.getSite('contact.email');
const facebook = AppConfigManager.getSite('social.facebook');
// Use via window.siteConfig (auto-exported)
console.log(window.siteConfig.title);
console.log(window.siteConfig.contact.email);
// Apply new metadata (replace all)
AppConfigManager.applySiteMetadata({
title: 'New Title',
description: 'New description',
logo: '/new-logo.png'
});
// Partial update (merge with existing)
AppConfigManager.updateSiteMetadata({
title: 'New Title',
contact: {
phone: '+1-999-9999' // Update phone only
}
});
Use in Templates
<!-- Use via 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.twitter">Twitter</a>
</footer>
data-on-load Support
Execute script after config is loaded (similar to 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>
Example usage:
<body data-on-load="
document.title = site.title;
updateHeaderLogo(site.logo);
initSocialLinks(site.social);
">
HTML
<!-- Auto-enhanced - no JS required -->
<button data-component="theme">🌓</button>
<!-- Or use new name -->
<button data-component="config">🌓</button>
AppConfigManager will auto-enhance the button:
- ✅ Adds click handler
- ✅ Syncs
data-theme-state attribute
- ✅ All buttons sync when theme changes
/* Icon font setup */
[data-component="theme"]:before,
[data-component="config"]:before {
font-family: 'icomoon' !important;
}
/* Show icon based on 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 */
}
Or use CSS classes:
[data-theme-state="light"] .icon-light { display: none; }
[data-theme-state="dark"] .icon-dark { display: none; }
- ✅ All buttons sync when theme changes
/* 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 */
}
Or use CSS classes:
[data-theme-state="light"] .icon-light { display: none; }
[data-theme-state="dark"] .icon-dark { display: none; }
Full Configuration
await Now.init({
config: { // or use 'theme' (backward compatible)
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 - load theme + site metadata
api: {
enabled: true,
configUrl: '/api/config',
headers: {},
timeout: 5000,
cacheResponse: true
}
}
});
Backend API Integration
Load Config from API
// Auto-load on init (if api.enabled = true)
await Now.init({ config: { api: { enabled: true, configUrl: '/api/config' } } });
// Or load manually
await AppConfigManager.loadFromAPI('/api/config');
// Refresh (clear cache + reload)
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": "en",
"contact": {
"email": "info@example.com",
"phone": "+1-xxx-xxxx"
},
"social": {
"facebook": "https://facebook.com/...",
"twitter": "@example"
}
}
}
Note:
variables - CSS custom properties (optional)
site - Site metadata (optional)
- API does NOT control light/dark mode - user controls that
CSS Variables
Apply Variables
AppConfigManager.applyVariables({
'--color-primary': '#6366f1',
'--color-background': '#0f172a',
'--hero-bg': '/images/hero.jpg' // Auto-wraps as url()
});
Security
- Only CSS Custom Properties (
--*) allowed
- Dangerous patterns removed (
javascript:, expression(), <script>)
- URLs: local paths or same-origin absolute URLs allowed
- External domains blocked by default
- Extensions must be images (no SVG)
Smooth Transitions
Required CSS
body {
opacity: 0;
transition: opacity 0.3s ease;
}
body.theme-ready {
opacity: 1;
}
body.theme-transitioning {
opacity: 0;
}
How It Works
body starts with opacity: 0
- AppConfigManager loads theme
- Adds
theme-ready class → fade in
- On switch → fade out → apply → fade in
API Reference
Theme Methods
| Method |
Description |
|---|
toggle() |
Toggle theme (light ↔ dark) |
setTheme(theme) |
Set theme ('light' or 'dark') |
getCurrentTheme() |
Get current theme |
| Method |
Description |
|---|
getSiteMetadata() |
Get all site metadata |
getSite(key) |
Get specific value (dot notation supported) |
applySiteMetadata(data) |
Apply new metadata |
updateSiteMetadata(updates) |
Partial update (merge) |
exportToWindow() |
Export to window.siteConfig |
CSS Variables Methods
| Method |
Description |
|---|
applyVariables(vars) |
Apply CSS Variables |
clearVariables() |
Remove CSS Variables |
getAppliedVariables() |
Get applied variables |
API Methods
| Method |
Description |
|---|
loadFromAPI(url?) |
Load from API |
refreshFromAPI() |
Reload (clear cache) |
Lifecycle Methods
| Method |
Description |
|---|
init(options) |
Initialize manager |
destroy() |
Full cleanup |
reset() |
Reset state |
Events
Theme Events
| Event |
When Triggered |
Payload |
|---|
theme:initialized |
Init complete |
{ theme } |
theme:changed |
Theme changed |
{ theme } |
theme:ready |
Ready to display |
- |
Config/Site Events
| Event |
When Triggered |
Payload |
|---|
config:site-loaded |
Site metadata loaded |
{ site } |
config:site-updated |
Metadata updated |
{ site, updates } |
config:exported |
Exported 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 |
When Triggered |
Payload |
|---|
theme:variables-applied |
Variables applied |
{ variables } |
theme:variables-cleared |
Variables cleared |
- |
API Events
| Event |
When Triggered |
Payload |
|---|
theme:api-loaded |
API load success |
{ config, fromCache } |
theme:api-error |
API error |
{ error, url } |
Lifecycle Events
| Event |
When Triggered |
|---|
theme:destroyed |
Cleanup complete |
theme:reset |
Reset complete |
// Usage examples
EventManager.on('theme:changed', ({ theme }) => {
console.log('Changed to:', 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
// Full cleanup (unmount app)
AppConfigManager.destroy();
// Light reset (re-init)
AppConfigManager.reset();
await AppConfigManager.init({ ... });
Common Pitfalls
⚠️ 1. Enable Before Use
// ❌ Not enabled
AppConfigManager.toggle();
// ✅ Enable first
await Now.init({ config: { enabled: true } });
AppConfigManager.toggle();
⚠️ 2. Use CSS Variables
/* ❌ Using fixed values */
body { background: white; }
/* ✅ Use variables */
body { background: var(--color-background); }
// ✅ Correct - site is an object
{
"variables": {...},
"site": { "title": "...", "contact": {...} }
}
// ❌ Wrong - site is an 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. Use HTTP/HTTPS Protocol
// ❌ file:// protocol can't use API
file:///path/to/index.html
// ✅ http(s):// protocol
http://localhost/...
https://example.com/...
⚠️ 6. Backward Compatibility
// ✅ Both work
window.AppConfigManager
window.ThemeManager // Alias
Now.init({ config: {...} }) // Recommended
Now.init({ theme: {...} }) // Still works
Use Cases
1. Multi-language Site
const lang = AppConfigManager.getSite('lang'); // 'en' or 'th'
// Change language
AppConfigManager.updateSiteMetadata({
lang: 'en',
title: 'Bookstore',
description: 'Old & Rare Books'
});
2. Dynamic SEO
EventManager.on('config:site-loaded', ({ site }) => {
// Auto-update on load
document.title = site.title;
const metaDesc = document.querySelector('meta[name="description"]');
if (metaDesc) metaDesc.content = site.description;
});
3. White-label / Multi-tenant
// Different config per tenant
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 });
});
Before (Separate APIs)
/api/theme → 150ms (CSS variables)
/api/site/info → 120ms (header)
/api/site/info → 120ms (footer, cached)
────────────────────────────────
Total: 270ms + 2-3 HTTP requests
After (Combined API)
/api/config → 180ms (theme + site)
────────────────────────────────
Total: 180ms + 1 HTTP request
Savings: 90ms + 1-2 requests ⚡