Now.js Framework Documentation
AppConfigManager
AppConfigManager
Overview
AppConfigManager is the theme and configuration management system in the Now.js Framework. It handles:
- Theme Switching: Light/Dark mode with system preference detection
- CSS Variables Management: Load and apply CSS custom properties from backend
- Template Data Binding: Passes full API payload to TemplateManager (ApiComponent-style)
- Security: Sanitizes CSS values to prevent XSS attacks
- Auto-Enhancement: Automatically enhances toggle buttons with
data-component="config" - Smooth Transitions: Anti-FOUC (Flash of Unstyled Content) with fade animations
When to use:
- Need centralized theme management across your application
- Want to load CSS variables dynamically from backend
- Need to bind configuration data to templates without writing JS
- Want smooth theme transitions without page flicker
Requirements
- Now.js Framework core
- Modern browser with ES6+ support
- Optional: EventManager (for event emission)
- Optional: TemplateManager (for data binding)
- Optional: HttpClient (for API requests)
Basic Usage
Initialization
// Initialize with Now framework
await Now.init({
config: {
enabled: true,
defaultTheme: 'light',
storageKey: 'app_theme',
systemPreference: true,
api: {
enabled: true,
configUrl: '/api/index/config/frontend-settings',
cacheResponse: true
}
}
});What happens:
- AppConfigManager loads theme preference from localStorage
- Falls back to system preference if no stored value
- Applies the theme to
document.documentElement - Loads configuration from API (if enabled)
- Applies CSS variables
- Processes template bindings
- Marks page as ready (removes loading state)
Manual Initialization
// If not using Now framework
await AppConfigManager.init({
enabled: true,
defaultTheme: 'dark',
api: {
enabled: true,
configUrl: '/api/config'
}
});API Response Format
The backend should return a JSON object with this structure:
{
"variables": {
"--color-primary": "#29336b",
"--color-secondary": "#6366f1",
"--logo-url": "/images/logo.png",
"--hero-bg": "/images/hero.jpg"
},
"web_title": "My Website",
"web_description": "Website description",
"company": {
"name": "Company Name",
"phone": "+1-234-567-8900",
"email": "info@example.com",
"address": "123 Main St"
},
"user": {
"id": 1,
"name": "John Doe",
"email": "john@example.com"
}
}Field Descriptions:
variables(object, optional) - CSS custom properties to apply- Any other keys - Available in template context for data binding
Note: The API response structure is flexible. AppConfigManager will pass all data to TemplateManager, allowing templates to access any backend keys.
Main Features
1. Theme Management
Toggle Theme
// Toggle between light and dark
AppConfigManager.toggle();
// Set specific theme
await AppConfigManager.setTheme('dark');
// Get current theme
const theme = AppConfigManager.getCurrentTheme();
console.log(theme); // 'light' or 'dark'Theme Persistence
Themes are automatically saved to localStorage:
// Stored with key from config.storageKey (default: 'app_theme')
// Retrieved automatically on next page loadSystem Preference Detection
// Enable in config
await Now.init({
config: {
systemPreference: true // Detect prefers-color-scheme
}
});
// AppConfigManager will:
// 1. Check localStorage first
// 2. If no stored theme, use system preference
// 3. Listen for system preference changes2. CSS Variables
Apply Variables
// Apply single variable
AppConfigManager.applyVariables({
'--color-primary': '#6366f1'
});
// Apply multiple variables
AppConfigManager.applyVariables({
'--color-primary': '#6366f1',
'--color-secondary': '#8b5cf6',
'--font-size-base': '16px',
'--spacing-unit': '8px'
});
// Apply image URLs (auto-wrapped with url())
AppConfigManager.applyVariables({
'--logo-url': '/images/logo.png',
'--hero-bg': './images/hero.jpg'
});Output:
:root {
--color-primary: #6366f1;
--color-secondary: #8b5cf6;
--logo-url: url('/images/logo.png');
--hero-bg: url('./images/hero.jpg');
}Clear Variables
// Remove all applied variables
AppConfigManager.clearVariables();Get Applied Variables
// Get object of currently applied variables
const vars = AppConfigManager.getAppliedVariables();
console.log(vars);
// { '--color-primary': '#6366f1', ... }3. Template Data Binding
AppConfigManager passes the full API response to TemplateManager, allowing templates to access backend data:
<!-- Header with dynamic data -->
<header>
<h1 data-text="web_title"></h1>
<p data-text="web_description"></p>
</header>
<!-- Footer with company info -->
<footer>
<div data-text="company.name"></div>
<a data-attr="href:'tel:' + company.phone" data-text="company.phone"></a>
<a data-attr="href:'mailto:' + company.email" data-text="company.email"></a>
</footer>
<!-- Conditional rendering based on user -->
<nav>
<li data-if="user === null">
<a href="/login">Login</a>
</li>
<li data-if="user !== null">
<a href="/profile" data-text="user.name"></a>
</li>
</nav>How it works:
- AppConfigManager loads config from API
- Creates context:
{ state: config, data: config, computed: {} } - Calls
TemplateManager.processDataDirectives(container, context) - Templates access data via dot notation (e.g.,
company.name)
4. Toggle Button Auto-Enhancement
HTML
<!-- Automatically enhanced -->
<button data-component="config">🌓 Toggle Theme</button>
<!-- Multiple toggles sync automatically -->
<button data-component="config" class="nav-toggle">Theme</button>
<button data-component="config" class="footer-toggle">🌙</button>What happens:
- ComponentManager calls
AppConfigManager.enhance(element) - Click handler added to toggle theme
data-theme-stateattribute added (e.g.,data-theme-state="light")- All toggle buttons update when theme changes
CSS for Toggle Buttons
/* Style based on current theme */
[data-component="config"][data-theme-state="light"]::before {
content: "🌙"; /* Show moon in light mode */
}
[data-component="config"][data-theme-state="dark"]::before {
content: "☀️"; /* Show sun in dark mode */
}
/* With icon fonts */
[data-component="config"]::before {
font-family: 'icomoon' !important;
}
[data-component="config"][data-theme-state="light"]::before {
content: "\e929"; /* icon-moon */
}
[data-component="config"][data-theme-state="dark"]::before {
content: "\e9d4"; /* icon-sun */
}5. Smooth Transitions (Anti-FOUC)
Required CSS
body {
opacity: 0;
transition: opacity 0.3s ease;
}
body.theme-ready {
opacity: 1;
}
body.theme-transitioning {
opacity: 0;
}
body.theme-loading {
opacity: 0;
}How it works:
- Page loads with
body { opacity: 0 } - AppConfigManager initializes, loads theme
- Adds
theme-readyclass → page fades in - On theme toggle:
- Adds
theme-transitioningclass → fade out - Applies new theme
- Adds
theme-readyclass → fade in
- Adds
Configure Transitions
await Now.init({
config: {
transition: {
enabled: true,
duration: 300, // Transition duration (ms)
hideOnSwitch: true, // Fade out during switch
loadingClass: 'theme-loading',
readyClass: 'theme-ready',
transitionClass: 'theme-transitioning'
}
}
});Advanced Examples
Example 1: Custom API Integration
// Custom endpoint with headers
await AppConfigManager.init({
enabled: true,
api: {
enabled: true,
configUrl: '/api/v2/config',
headers: {
'X-API-Key': 'your-api-key',
'Accept': 'application/json'
},
timeout: 10000,
cacheResponse: true
}
});
// Manual load with custom URL
try {
const config = await AppConfigManager.loadFromAPI('/api/custom-config');
console.log('Loaded:', config);
} catch (error) {
console.error('Failed to load config:', error);
}
// Refresh (clears cache)
const freshConfig = await AppConfigManager.refreshFromAPI();Example 2: Programmatic Theme Control
// Listen for theme changes
EventManager.on('theme:changed', ({ theme }) => {
console.log('Theme changed to:', theme);
// Update analytics
gtag('event', 'theme_change', { theme });
// Update external service
updateThemePreference(theme);
});
// Set theme based on user action
async function applyUserTheme(userPreference) {
if (userPreference === 'system') {
// Use system preference
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
await AppConfigManager.setTheme(prefersDark ? 'dark' : 'light');
} else {
await AppConfigManager.setTheme(userPreference);
}
}
// Toggle with custom transition
await AppConfigManager.setTheme('dark', {
transition: true // Force transition even if disabled
});
// Set without transition
await AppConfigManager.setTheme('light', {
transition: false // Skip transition
});Example 3: Dynamic Variables Update
// Update variables based on theme
EventManager.on('theme:changed', ({ theme }) => {
if (theme === 'dark') {
AppConfigManager.applyVariables({
'--logo-url': '/images/logo-dark.png',
'--hero-bg': '/images/hero-dark.jpg'
});
} else {
AppConfigManager.applyVariables({
'--logo-url': '/images/logo-light.png',
'--hero-bg': '/images/hero-light.jpg'
});
}
});
// Load theme-specific stylesheet
EventManager.on('theme:changed', ({ theme }) => {
const linkId = 'theme-specific-styles';
let link = document.getElementById(linkId);
if (!link) {
link = document.createElement('link');
link.id = linkId;
link.rel = 'stylesheet';
document.head.appendChild(link);
}
link.href = `/css/themes/${theme}.css`;
});Example 4: Multi-tenant Configuration
// Load tenant-specific config
const tenantId = getCurrentTenantId();
await AppConfigManager.init({
enabled: true,
api: {
enabled: true,
configUrl: `/api/tenants/${tenantId}/config`,
cacheResponse: false // Don't cache (tenant-specific)
}
});
// Each tenant gets:
// - Custom CSS variables (colors, logos, fonts)
// - Custom company info
// - Custom theme preferencesAPI Reference
Configuration Options
{
enabled: boolean, // Enable AppConfigManager (default: false)
defaultTheme: string, // Default theme: 'light' or 'dark' (default: 'light')
storageKey: string, // localStorage key (default: 'app_theme')
systemPreference: boolean, // Use system color scheme (default: false)
api: {
enabled: boolean, // Load config from API (default: false)
configUrl: string|null, // API endpoint URL (default: null)
headers: object, // Custom request headers (default: {})
timeout: number, // Request timeout in ms (default: 5000)
cacheResponse: boolean // Cache API responses (default: true)
},
transition: {
enabled: boolean, // Enable transitions (default: true)
duration: number, // Transition duration in ms (default: 300)
hideOnSwitch: boolean, // Fade out during switch (default: true)
loadingClass: string, // Loading state class (default: 'theme-loading')
readyClass: string, // Ready state class (default: 'theme-ready')
transitionClass: string // Transitioning class (default: 'theme-transitioning')
}
}Methods
init(options)
Initialize AppConfigManager
Parameters:
options(object) - Configuration options
Returns: Promise
Example:
await AppConfigManager.init({
enabled: true,
defaultTheme: 'dark'
});toggle()
Toggle between light and dark theme
Returns: void
Example:
AppConfigManager.toggle();setTheme(theme, options)
Set specific theme
Parameters:
theme(string) - Theme name: 'light' or 'dark'options(object, optional) - Optionstransition(boolean) - Whether to animate (default: true)
Returns: Promise
Example:
await AppConfigManager.setTheme('dark');
await AppConfigManager.setTheme('light', { transition: false });getCurrentTheme()
Get current active theme
Returns: string - Current theme name
Example:
const theme = AppConfigManager.getCurrentTheme();
console.log(theme); // 'light' or 'dark'applyVariables(variables)
Apply CSS custom properties
Parameters:
variables(object) - Key-value pairs of CSS variable names and values
Returns: object - Applied variables (after sanitization)
Example:
const applied = AppConfigManager.applyVariables({
'--color-primary': '#6366f1',
'--logo-url': '/images/logo.png'
});Security: Only CSS custom properties (--*) are allowed. Dangerous patterns are removed. Image URLs are validated and wrapped with url().
clearVariables()
Remove all applied CSS variables
Returns: void
Example:
AppConfigManager.clearVariables();getAppliedVariables()
Get currently applied CSS variables
Returns: object - Object of applied variables
Example:
const vars = AppConfigManager.getAppliedVariables();loadFromAPI(url)
Load configuration from backend API
Parameters:
url(string, optional) - API URL (uses config.api.configUrl if not provided)
Returns: Promise - Configuration object from API
Throws: Error if request fails or response is invalid
Example:
try {
const config = await AppConfigManager.loadFromAPI();
console.log('Config loaded:', config);
} catch (error) {
console.error('Load failed:', error);
}refreshFromAPI()
Reload configuration from API (clears cache first)
Returns: Promise - Fresh configuration
Example:
const config = await AppConfigManager.refreshFromAPI();enhance(element)
Enhance a toggle button element (called by ComponentManager)
Parameters:
element(HTMLElement) - Element to enhance
Returns: HTMLElement - The enhanced element
Example:
const button = document.querySelector('.theme-toggle');
AppConfigManager.enhance(button);destroy()
Destroy AppConfigManager and clean up all resources
Returns: void
Example:
// On app unmount
AppConfigManager.destroy();reset()
Reset AppConfigManager to default state without full destroy
Returns: void
Example:
AppConfigManager.reset();
await AppConfigManager.init(newConfig);Events
theme:initialized
Emitted when AppConfigManager initialization is complete
Payload:
{
theme: string // Current theme
}Example:
EventManager.on('theme:initialized', ({ theme }) => {
console.log('Initialized with theme:', theme);
});theme:changed
Emitted when theme changes
Payload:
{
theme: string // New theme
}Example:
EventManager.on('theme:changed', ({ theme }) => {
console.log('Theme changed to:', theme);
// Update UI, analytics, etc.
});theme:ready
Emitted when theme is ready to display (anti-FOUC)
Payload: (none)
Example:
EventManager.on('theme:ready', () => {
console.log('Theme ready - page visible');
});theme:variables-applied
Emitted when CSS variables are applied
Payload:
{
variables: object // Applied variables
}Example:
EventManager.on('theme:variables-applied', ({ variables }) => {
console.log('Variables applied:', variables);
});theme:variables-cleared
Emitted when CSS variables are cleared
Payload: (none)
Example:
EventManager.on('theme:variables-cleared', () => {
console.log('Variables cleared');
});theme:api-loaded
Emitted when configuration is loaded from API
Payload:
{
config: object, // Configuration data
fromCache: boolean // Whether loaded from cache
}Example:
EventManager.on('theme:api-loaded', ({ config, fromCache }) => {
console.log('Config loaded:', config);
console.log('From cache:', fromCache);
});theme:api-error
Emitted when API request fails
Payload:
{
error: string, // Error message
url: string // API URL
}Example:
EventManager.on('theme:api-error', ({ error, url }) => {
console.error('API error:', error);
console.error('URL:', url);
});theme:destroyed
Emitted when AppConfigManager is destroyed
Payload: (none)
Example:
EventManager.on('theme:destroyed', () => {
console.log('AppConfigManager destroyed');
});theme:reset
Emitted when AppConfigManager is reset
Payload: (none)
Example:
EventManager.on('theme:reset', () => {
console.log('AppConfigManager reset');
});Security
CSS Variable Validation
AppConfigManager includes strict security validation to prevent XSS attacks:
Property Validation
// ✅ Allowed - CSS custom properties only
AppConfigManager.applyVariables({
'--color-primary': '#6366f1',
'--font-size': '16px'
});
// ❌ Rejected - Not a CSS custom property
AppConfigManager.applyVariables({
'color': 'red', // Rejected
'background': 'blue' // Rejected
});Value Sanitization
Dangerous patterns are automatically removed:
// ❌ Dangerous patterns (automatically removed)
'javascript:alert(1)' // javascript: protocol
'expression(alert(1))' // IE expression()
'<script>alert(1)</script>' // script tags
'url(javascript:alert(1))' // javascript in url()URL Validation
Image URLs are validated and sanitized:
// ✅ Allowed URLs
'/images/logo.png' // Local absolute path
'./images/hero.jpg' // Local relative path
'https://example.com/image.png' // Same-origin absolute (if allowed)
// ❌ Rejected URLs
'../../../etc/passwd' // Path traversal
'http://evil.com/xss.jpg' // External domain (default policy)
'/images/file.svg' // SVG not allowed (default)
'/images/file.php' // Non-image extensionSecurity Configuration
AppConfigManager.security = {
allowedPropertyPattern: /^--[\w-]+$/, // CSS custom properties only
maxValueLength: 500, // Prevent DoS
dangerousPatterns: [
// Patterns to remove from values
],
url: {
enabled: true,
allowedExtensions: ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.avif', '.ico'],
blockPathTraversal: true,
stripQueryString: true,
allowedOrigins: [window.location.origin]
}
};Common Mistakes
❌ Mistake 1: Not enabling AppConfigManager
// ❌ Wrong - AppConfigManager not enabled
AppConfigManager.toggle(); // Does nothing
// ✅ Correct - Enable first
await Now.init({
config: { enabled: true }
});
AppConfigManager.toggle();❌ Mistake 2: Forgetting to await async methods
// ❌ Wrong - Not awaiting
AppConfigManager.setTheme('dark');
console.log(AppConfigManager.getCurrentTheme()); // May still be 'light'
// ✅ Correct - Await theme change
await AppConfigManager.setTheme('dark');
console.log(AppConfigManager.getCurrentTheme()); // 'dark'❌ Mistake 3: Using non-CSS custom properties
// ❌ Wrong - Regular CSS properties rejected
AppConfigManager.applyVariables({
'color': 'red',
'font-size': '16px'
});
// ✅ Correct - Use CSS custom properties
AppConfigManager.applyVariables({
'--color-primary': 'red',
'--font-size-base': '16px'
});❌ Mistake 4: Not setting up CSS for transitions
// ❌ Wrong - No CSS for anti-FOUC
// Page will flash unstyled content
// ✅ Correct - Add required CSSbody {
opacity: 0;
transition: opacity 0.3s ease;
}
body.theme-ready {
opacity: 1;
}❌ Mistake 5: Assuming variables are applied immediately
// ❌ Wrong - Variables not applied yet
AppConfigManager.applyVariables({ '--color': 'red' });
const computed = getComputedStyle(document.documentElement)
.getPropertyValue('--color'); // May be empty
// ✅ Correct - Variables applied synchronously to DOM
AppConfigManager.applyVariables({ '--color': 'red' });
// But CSS paint may be deferred by browser
requestAnimationFrame(() => {
const computed = getComputedStyle(document.documentElement)
.getPropertyValue('--color'); // 'red'
});Best Practices
✅ DO:
-
Enable AppConfigManager before use
await Now.init({ config: { enabled: true } }); -
Use CSS custom properties for theming
.button { background: var(--color-primary); color: var(--color-text); } -
Set up anti-FOUC CSS
body { opacity: 0; transition: opacity 0.3s ease; } body.theme-ready { opacity: 1; } -
Handle API errors gracefully
EventManager.on('theme:api-error', ({ error }) => { console.error('Config load failed:', error); // Fallback to defaults }); -
Clean up on app unmount
window.addEventListener('unload', () => { AppConfigManager.destroy(); });
❌ DON'T:
-
Don't toggle theme too frequently
// ❌ Bad - Causes flickering setInterval(() => AppConfigManager.toggle(), 100); -
Don't modify state directly
// ❌ Bad AppConfigManager.state.current = 'dark'; // ✅ Good await AppConfigManager.setTheme('dark'); -
Don't apply too many variables at once
// ❌ Bad - Performance impact for (let i = 0; i < 1000; i++) { AppConfigManager.applyVariables({ [`--var-${i}`]: 'value' }); } // ✅ Good - Batch apply const vars = {}; for (let i = 0; i < 100; i++) { vars[`--var-${i}`] = 'value'; } AppConfigManager.applyVariables(vars); -
Don't bypass security validation
// ❌ Bad - Trying to bypass validation document.documentElement.style.setProperty('color', 'red'); // ✅ Good - Use AppConfigManager API AppConfigManager.applyVariables({ '--color': 'red' });
Troubleshooting
Theme not persisting
Problem: Theme resets on page reload
Solution:
// Check storageKey is set
await Now.init({
config: {
storageKey: 'app_theme' // Must be set
}
});
// Check localStorage is available
try {
localStorage.setItem('test', '1');
localStorage.removeItem('test');
} catch (e) {
console.error('localStorage blocked:', e);
}API not loading
Problem: Configuration not loading from API
Solution:
// Check API is enabled
await Now.init({
config: {
api: {
enabled: true, // Must be true
configUrl: '/api/config' // Must be set
}
}
});
// Check URL security
// Only HTTPS same-origin or HTTP same-origin allowed by default
// External domains are blocked
// Listen for errors
EventManager.on('theme:api-error', ({ error, url }) => {
console.error('API error:', error, url);
});Variables not applying
Problem: CSS variables not visible in styles
Solution:
// 1. Check variables are valid CSS custom properties
AppConfigManager.applyVariables({
'--color': 'red' // ✅ Good
// 'color': 'red' // ❌ Bad
});
// 2. Check variables in DevTools
console.log(AppConfigManager.getAppliedVariables());
// 3. Check computed styles
const computed = getComputedStyle(document.documentElement);
console.log(computed.getPropertyValue('--color'));
// 4. Verify CSS is using variables
// body { color: var(--color); }Transition flickering
Problem: Page flickers during theme transitions
Solution:
/* Ensure CSS is set up correctly */
body {
opacity: 0;
transition: opacity 0.3s ease;
}
body.theme-ready {
opacity: 1;
}
body.theme-transitioning {
opacity: 0;
}// Adjust transition duration if needed
await Now.init({
config: {
transition: {
duration: 500 // Slower transition
}
}
});Related Documentation
- TemplateManager - Data binding and directives
- ApiComponent - Component-level API integration
- ComponentManager - Component registration
- EventManager - Event system
- HttpClient - HTTP client for API requests