Now.js Framework Documentation

Now.js Framework Documentation

AppConfigManager

EN 11 Feb 2026 13:48

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:

  1. AppConfigManager loads theme preference from localStorage
  2. Falls back to system preference if no stored value
  3. Applies the theme to document.documentElement
  4. Loads configuration from API (if enabled)
  5. Applies CSS variables
  6. Processes template bindings
  7. 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 load

System 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 changes

2. 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:

  1. AppConfigManager loads config from API
  2. Creates context: { state: config, data: config, computed: {} }
  3. Calls TemplateManager.processDataDirectives(container, context)
  4. 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:

  1. ComponentManager calls AppConfigManager.enhance(element)
  2. Click handler added to toggle theme
  3. data-theme-state attribute added (e.g., data-theme-state="light")
  4. 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:

  1. Page loads with body { opacity: 0 }
  2. AppConfigManager initializes, loads theme
  3. Adds theme-ready class → page fades in
  4. On theme toggle:
    • Adds theme-transitioning class → fade out
    • Applies new theme
    • Adds theme-ready class → fade in

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 preferences

API 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) - Options
    • transition (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 extension

Security 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 CSS
body {
  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:

  1. Enable AppConfigManager before use

    await Now.init({ config: { enabled: true } });
  2. Use CSS custom properties for theming

    .button {
     background: var(--color-primary);
     color: var(--color-text);
    }
  3. Set up anti-FOUC CSS

    body {
     opacity: 0;
     transition: opacity 0.3s ease;
    }
    body.theme-ready {
     opacity: 1;
    }
  4. Handle API errors gracefully

    EventManager.on('theme:api-error', ({ error }) => {
     console.error('Config load failed:', error);
     // Fallback to defaults
    });
  5. Clean up on app unmount

    window.addEventListener('unload', () => {
     AppConfigManager.destroy();
    });

❌ DON'T:

  1. Don't toggle theme too frequently

    // ❌ Bad - Causes flickering
    setInterval(() => AppConfigManager.toggle(), 100);
  2. Don't modify state directly

    // ❌ Bad
    AppConfigManager.state.current = 'dark';
    
    // ✅ Good
    await AppConfigManager.setTheme('dark');
  3. 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);
  4. 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
    }
  }
});