Now.js Framework Documentation

Now.js Framework Documentation

ResponseHandler

EN 05 Nov 2025 02:11

ResponseHandler

Overview

ResponseHandler is a centralized API response handler that reduces JavaScript code complexity by allowing the server to send "actions" along with responses, eliminating the need to write repetitive client-side code for each case.

Key Features:

  • Reduces code duplication - API controls display and behavior directly
  • Supports various action types (notification, modal, redirect, DOM manipulation)
  • Hybrid Modal Approach - Frontend-driven by default (90%), API force override when needed (10%)
  • Auto-extraction - Automatically extracts data.data and data.options for modals
  • Priority System - API force > API suggest > Frontend default
  • Extensible with custom action handlers
  • Async/await support
  • Conditional action execution
  • Security-first: eval disabled by default

Installation and Import

ResponseHandler is loaded with the Now.js Framework and is ready to use immediately via the window object:

// No import needed - ready to use immediately
console.log(window.ResponseHandler); // ResponseHandler object

Or ES6 import:

import ResponseHandler from './Now/js/ResponseHandler.js';

Configuration

Configuration Options

Option Type Default Description
debug Boolean false Enable/disable debug logging
defaultRedirectDelay Number 1000 Default redirect delay (ms)
defaultNotificationDuration Number 5000 Default notification duration (ms)
allowEval Boolean false Allow eval action (not recommended)
autoHandleFormResponses Boolean true Auto-handle form responses

Initialization

// Auto-initialize (uses defaults)
ResponseHandler.init();

// Initialize with custom config
ResponseHandler.init({
  debug: true,
  defaultRedirectDelay: 2000,
  defaultNotificationDuration: 3000
});

API Response Structure

Basic Format

{
  "success": true,
  "message": "Data saved successfully",
  "data": {...},
  "actions": [
    {
      "type": "notification",
      "level": "success",
      "message": "Data saved successfully"
    },
    {
      "type": "redirect",
      "url": "/dashboard",
      "delay": 1000
    }
  ]
}

Single Action Format

{
  "success": true,
  "type": "alert",
  "message": "Operation completed"
}

Action with Condition

{
  "type": "notification",
  "message": "You have new messages",
  "condition": true
}

Methods

init(options)

Initialize ResponseHandler

Parameters:

  • options (Object, optional): Configuration options

Returns: ResponseHandler

Example:

ResponseHandler.init({
  debug: true,
  defaultRedirectDelay: 1500
});

process(response, context)

Process API response and execute specified actions

Parameters:

  • response (Object): API response object
  • context (Object, optional): Context object for passing additional data to handlers

Returns: Promise<Object> - response object

Example:

// With fetch API
const response = await fetch('/api/save', {
  method: 'POST',
  body: JSON.stringify(data)
});

const json = await response.json();
await ResponseHandler.process(json);

// With context
await ResponseHandler.process(json, {
  tableId: 'users-table',
  formId: 'user-form'
});

executeAction(action, context)

Execute a single action

Parameters:

  • action (Object): Action object
  • context (Object, optional): Context object

Returns: Promise<void>

Example:

await ResponseHandler.executeAction({
  type: 'notification',
  level: 'success',
  message: 'Saved successfully'
});

registerHandler(type, handler)

Register a custom action handler

Parameters:

  • type (String): Action type name
  • handler (Function): Handler function (action, context) => {}

Returns: void

Example:

// Create custom action
ResponseHandler.registerHandler('playSound', (action) => {
  const audio = new Audio(action.soundUrl);
  audio.volume = action.volume || 1.0;
  audio.play();
});

// Use it
await ResponseHandler.executeAction({
  type: 'playSound',
  soundUrl: '/sounds/success.mp3',
  volume: 0.5
});

executeCallback(fn, args)

Execute callback function

Parameters:

  • fn (Function|String): Function or function name in window scope
  • args (Array, optional): Arguments to pass to the function

Returns: void

Example:

// Direct function call
ResponseHandler.executeCallback(() => {
  console.log('Done!');
});

// Call function by name
window.myCallback = (msg) => alert(msg);
ResponseHandler.executeCallback('myCallback', ['Hello!']);

emit(eventName, data)

Emit custom event

Parameters:

  • eventName (String): Event name
  • data (Any): Data to send

Returns: void

Example:

ResponseHandler.emit('data:updated', {
  id: 123,
  type: 'user'
});

Built-in Action Types

1. Notification

Display notification message

{
  "type": "notification",
  "level": "success",
  "message": "Data saved successfully",
  "duration": 3000
}

Properties:

  • level: "success" | "info" | "warning" | "error"
  • message: Message to display
  • duration: Display duration (ms)

2. Alert

Show browser alert dialog

{
  "type": "alert",
  "message": "Please fill in all required fields"
}

3. Confirm

Show confirm dialog and execute callback on OK

{
  "type": "confirm",
  "message": "Are you sure you want to delete?",
  "onConfirm": "deleteRecord"
}

Properties:

  • message: Confirmation message
  • onConfirm: Function or function name to call on confirm

4. Modal

Open/close modal dialog

{
  "type": "modal",
  "action": "show",
  "content": "<h2>Details</h2><p>Important information</p>",
  "className": "large-modal",
  "buttons": [
    {
      "text": "Close",
      "className": "button danger",
      "onclick": "modal.hide()"
    }
  ]
}

Actions:

  • show / open: Open modal
  • close / hide: Close modal

Properties:

  • content: HTML content
  • className: Modal CSS class
  • buttons: Array of buttons
  • options: GModal options

5. Redirect

Navigate to URL

{
  "type": "redirect",
  "url": "/dashboard",
  "delay": 1000,
  "target": "_self"
}

Special URLs:

  • "reload": Reload current page
  • "refresh": Refresh page
  • "back": Go back in history

Properties:

  • url: Destination URL
  • delay: Delay before redirect (ms)
  • target: "_self" | "_blank" | "_parent" | "_top"

6. Update

Update element content

{
  "type": "update",
  "target": "#result",
  "content": "<p>New data</p>",
  "method": "html"
}

Methods:

  • html: Set innerHTML
  • text: Set textContent
  • value: Set value (for inputs)
  • append: Append content
  • prepend: Prepend content

7. Remove

Remove element

{
  "type": "remove",
  "target": ".temp-message",
  "animate": true
}

Properties:

  • target: Selector or element
  • animate: Show animation before removal

8. Class

Manage CSS classes

{
  "type": "class",
  "target": "#myDiv",
  "class": "active highlight",
  "method": "add"
}

Methods:

  • add: Add class
  • remove: Remove class
  • toggle: Toggle class

9. Attribute

Set/remove attribute

{
  "type": "attribute",
  "target": "#myInput",
  "name": "disabled",
  "value": true
}

Properties:

  • name: Attribute name
  • value: Value (use null to remove attribute)

10. Event

Dispatch custom event

{
  "type": "event",
  "event": "data:updated",
  "target": "#dataTable",
  "data": {"id": 123},
  "bubbles": true
}

11. Callback

Execute callback function

{
  "type": "callback",
  "function": "window.refreshTable",
  "args": [123, "user"]
}

12. Download

Download file

{
  "type": "download",
  "url": "/export/data.xlsx",
  "filename": "report-2024.xlsx"
}

13. Clipboard

Copy text to clipboard

{
  "type": "clipboard",
  "text": "https://example.com/share/123",
  "notify": true,
  "message": "URL copied"
}

14. Scroll

Scroll to element

{
  "type": "scroll",
  "target": "#top",
  "behavior": "smooth",
  "block": "center"
}

15. Focus

Focus element

{
  "type": "focus",
  "target": "#emailInput",
  "select": true
}

Properties:

  • select: Select all text (for inputs)

16. Reload

Reload component

{
  "type": "reload",
  "target": "#users-table"
}

Execute JavaScript code (requires allowEval enabled)

{
  "type": "eval",
  "code": "console.log('Hello');"
}

⚠️ Warning: Use only when absolutely necessary and with strict input control

Usage Examples

1. Basic Data Save

API Response (PHP):

<?php
echo json_encode([
    'success' => true,
    'message' => 'Data saved successfully',
    'data' => ['id' => 123],
    'actions' => [
        [
            'type' => 'notification',
            'level' => 'success',
            'message' => 'User data saved successfully'
        ],
        [
            'type' => 'redirect',
            'url' => '/users',
            'delay' => 1500
        ]
    ]
]);

JavaScript:

document.querySelector('#saveBtn').addEventListener('click', async () => {
  const formData = new FormData(document.querySelector('#userForm'));

  const response = await fetch('/api/users/save', {
    method: 'POST',
    body: formData
  });

  const json = await response.json();
  await ResponseHandler.process(json);
  // Will automatically show notification and redirect
});

2. Form Validation

API Response:

{
  "success": false,
  "message": "Invalid data",
  "actions": [
    {
      "type": "notification",
      "level": "error",
      "message": "Please fill in all required fields"
    },
    {
      "type": "class",
      "target": "#emailInput",
      "class": "error",
      "method": "add"
    },
    {
      "type": "focus",
      "target": "#emailInput",
      "select": true
    }
  ]
}

3. Delete with Confirmation

JavaScript:

async function deleteUser(userId) {
  const response = await fetch(`/api/users/${userId}`, {
    method: 'DELETE'
  });

  const json = await response.json();
  await ResponseHandler.process(json);
}

API Response:

{
  "success": true,
  "actions": [
    {
      "type": "notification",
      "level": "success",
      "message": "Data deleted"
    },
    {
      "type": "remove",
      "target": "#user-row-123",
      "animate": true
    },
    {
      "type": "event",
      "event": "user:deleted",
      "data": {"id": 123}
    }
  ]
}

The Hybrid Modal Approach separates concerns: the frontend defines UI structure (templates, titles, styling), while the backend provides data only. This reduces coupling and makes maintenance easier.

Priority System:

  1. API Force - API explicitly overrides with force: true (10% special cases)
  2. API Suggest - API suggests template without force (optional)
  3. Frontend Default - Frontend config from HTML data-row-actions (90% use cases)

Step 1: Define Modal Config in HTML

Configure modal directly in the table button's data-row-actions:

<button data-row-actions='{
  "edit": {
    "modal": {
      "template": "editprofile",
      "title": "Edit User Profile",
      "className": "large-modal"
    }
  }
}'>Actions</button>

Properties:

  • template - Template filename (from /templates/modals/{template}.html)
  • templateUrl - Or full template URL path
  • title - Modal title
  • className - CSS class for modal styling
  • size - Modal size: small, medium, large

Step 2: Create Template File

Create /templates/modals/editprofile.html:

<form id="edit-profile-form" class="modal-form">
  <!-- Data binding with data-model -->
  <div class="form-group">
    <label>Name</label>
    <input type="text" data-model="name" required />
  </div>

  <div class="form-group">
    <label>Email</label>
    <input type="email" data-model="email" required />
  </div>

  <div class="form-group">
    <label>Phone</label>
    <input type="tel" data-model="phone" />
  </div>

  <!-- Dynamic select from options -->
  <div class="form-group">
    <label>Province</label>
    <select data-model="provinceID">
      <option value="">Select province</option>
      <option
        data-for="province in options.provinces"
        data-attr="value:province.id,selected:provinceID==province.id"
        data-text="province.name">
      </option>
    </select>
  </div>

  <div class="modal-footer">
    <button type="button" class="button" onclick="Modal.hide()">Cancel</button>
    <button type="submit" class="button primary">Save</button>
  </div>
</form>

<script>
document.getElementById('edit-profile-form').addEventListener('submit', async (e) => {
  e.preventDefault();

  const modal = StateManager.getModule('modal');
  const formData = modal.getData();

  const response = await fetch('/api/v1/users/edit', {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify(formData)
  });

  const result = await response.json();
  await ResponseHandler.process(result);
});
</script>

Step 3: Backend Returns Simple Data

PHP API endpoint just returns data and options:

// modules/v1/controllers/users.php
public function action() {
    $userId = $_POST['id'];
    $user = $this->db->table('users')->where(['id', $userId])->first();

    $provinces = $this->db->table('provinces')
        ->orderBy('name')
        ->execute()
        ->fetchAll();

    // Simple response - no template specification
    echo json_encode([
        'success' => true,
        'data' => $user,           // Auto-extracted to modal state
        'options' => [             // Auto-extracted to modal state
            'provinces' => $provinces
        ]
    ]);
}

JSON Response:

{
  "success": true,
  "data": {
    "id": 1,
    "name": "John Doe",
    "email": "john@example.com",
    "phone": "0812345678",
    "provinceID": 10
  },
  "options": {
    "provinces": [
      {"id": 10, "name": "Bangkok"},
      {"id": 11, "name": "Chiang Mai"}
    ]
  }
}

Flow:

  1. User clicks "Edit" → TableManager reads data-row-actions config
  2. Sends action to API → API returns data + options
  3. ResponseHandler extracts data.data and data.options automatically
  4. Loads template from frontend config → Binds data via TemplateManager
  5. Shows modal with populated form

Benefits:

  • ✅ Frontend controls UI (template, title, styling)
  • ✅ Backend only provides data
  • ✅ Easy maintenance - change UI without touching PHP
  • ✅ Frontend developer can work independently
  • ✅ Auto-extraction reduces boilerplate

5. Modal - API Force Override (Role-Based UI)

When you need the API to force a specific template (10% special cases):

Use Cases:

  • Role-based templates (Admin sees different form than User)
  • Feature flags / A/B testing
  • Dynamic workflows
  • Emergency overrides

PHP Response:

// Force different template based on role
$template = $user->isAdmin ? 'admin-edit-profile' : 'user-edit-profile';

echo json_encode([
    'success' => true,
    'actions' => [
        [
            'type' => 'modal',
            'template' => $template,
            'force' => true,           // Force override frontend config
            'data' => $userData,
            'options' => $dropdowns,
            'title' => 'Edit Profile'
        ]
    ]
]);

Priority: API force wins over frontend config

6. Modal - HTML Content (Backward Compatible)

For simple modals or backward compatibility, use html instead of template:

API Response:

{
  "success": true,
  "actions": [
    {
      "type": "modal",
      "html": "<div class='user-detail'><h2>John Doe</h2><p>Email: john@example.com</p></div>",
      "title": "User Details",
      "className": "user-modal",
      "buttons": [
        {
          "text": "Edit",
          "className": "button primary",
          "onclick": "editUser(123)"
        },
        {
          "text": "Close",
          "className": "button",
          "onclick": "Modal.hide()"
        }
      ]
    }
  ]
}

Use When:

  • Simple static content
  • Backward compatibility with old code
  • No data binding needed
  • Dynamic HTML generated by backend

7. Multiple Sequential Actions

API Response:

{
  "success": true,
  "actions": [
    {
      "type": "notification",
      "level": "success",
      "message": "Upload successful"
    },
    {
      "type": "update",
      "target": "#fileList",
      "content": "<li>document.pdf</li>",
      "method": "append"
    },
    {
      "type": "class",
      "target": "#uploadBtn",
      "class": "loading",
      "method": "remove"
    },
    {
      "type": "event",
      "event": "file:uploaded",
      "data": {"filename": "document.pdf"}
    }
  ]
}

6. Custom Action Handler

JavaScript:

// Register custom action
ResponseHandler.registerHandler('animateProgress', (action) => {
  const progressBar = document.querySelector(action.target);
  const targetValue = action.value;

  let current = 0;
  const interval = setInterval(() => {
    current += 5;
    progressBar.style.width = `${current}%`;
    progressBar.textContent = `${current}%`;

    if (current >= targetValue) {
      clearInterval(interval);
    }
  }, 50);
});

// Use it
await ResponseHandler.executeAction({
  type: 'animateProgress',
  target: '#progress',
  value: 75
});

API Response:

{
  "success": true,
  "actions": [
    {
      "type": "animateProgress",
      "target": "#uploadProgress",
      "value": 100
    },
    {
      "type": "notification",
      "level": "success",
      "message": "Upload complete"
    }
  ]
}

Best Practices for Hybrid Modal

1. Use Frontend-Driven as Default (90%)

Reasons:

  • Separation of concerns: UI logic from business logic
  • Easier maintenance (change UI without touching backend)
  • Frontend developers can work independently
  • Reduces coupling between frontend and backend

Example:

<!-- Define everything in HTML -->
<button data-row-actions='{
  "edit": {
    "modal": {
      "template": "editprofile",
      "title": "Edit Profile",
      "className": "large-modal"
    }
  },
  "delete": {
    "modal": {
      "template": "confirm-delete",
      "title": "Confirm Deletion"
    }
  }
}'>Actions</button>
// API only returns data
echo json_encode([
    'success' => true,
    'data' => $userData,
    'options' => $dropdowns
]);

2. Use API Force Override Only for Special Cases (10%)

Appropriate Use Cases:

  • Role-based UI (Admin sees different template than User)
  • Feature flags / A/B testing
  • Dynamic workflows (different steps based on state)
  • Emergency overrides (real-time system shutdown)

Example:

// Special case: role-based
$template = $isAdmin ? 'admin-edit-profile' : 'user-edit-profile';

echo json_encode([
    'success' => true,
    'actions' => [
        [
            'type' => 'modal',
            'template' => $template,  // Force override
            'force' => true,
            'data' => $userData
        ]
    ]
]);

3. Use Auto-Extraction for Simple Cases

Advantages:

  • Reduces boilerplate code
  • Cleaner API responses
  • Works immediately without configuration

Example:

// Simple response - auto extract
echo json_encode([
    'success' => true,
    'data' => [
        'id' => 1,
        'name' => 'John',
        'email' => 'john@example.com'
    ],
    'options' => [
        'roles' => ['admin', 'user', 'guest'],
        'status' => ['active', 'inactive']
    ]
]);
// Frontend config - system auto-extracts data.data and data.options
<button data-row-actions='{
  "edit": {
    "modal": {
      "template": "editprofile"
    }
  }
}'>Edit</button>

4. Use Explicit Data for Complex Cases

Use Cases:

  • Nested data structures (nested objects/arrays)
  • Need to transform data before sending to modal
  • Data from multiple sources
  • Need precise control over structure

Example:

// Complex data structure
echo json_encode([
    'success' => true,
    'actions' => [
        [
            'type' => 'modal',
            'template' => 'advanced-editor',
            'data' => [
                'user' => $userData,
                'permissions' => $userPermissions,
                'history' => $recentActivity,
                'metadata' => [
                    'lastLogin' => $lastLogin,
                    'createdBy' => $creator
                ]
            ],
            'options' => [
                'roles' => $availableRoles,
                'departments' => $departments,
                'groups' => $groups
            ]
        ]
    ]
]);

5. Handle Errors Properly

Recommended Pattern:

try {
    // Validation
    if (empty($data['email'])) {
        throw new Exception('Email is required');
    }

    // Business logic
    $result = $model->save($data);

    echo json_encode([
        'success' => true,
        'actions' => [
            ['type' => 'notification', 'level' => 'success', 'message' => 'Saved successfully'],
            ['type' => 'closeModal'],
            ['type' => 'reload', 'target' => '#users-table']
        ]
    ]);

} catch (Exception $e) {
    echo json_encode([
        'success' => false,
        'actions' => [
            [
                'type' => 'modal',
                'html' => '<div class="error-modal">' . htmlspecialchars($e->getMessage()) . '</div>',
                'title' => 'Error Occurred',
                'className' => 'error-modal'
            ]
        ]
    ]);
}

6. Template Organization

Recommended Structure:

templates/
├── modals/
│   ├── crud/              # CRUD operations
│   │   ├── edit-user.html
│   │   ├── create-user.html
│   │   └── delete-confirm.html
│   ├── forms/             # Complex forms
│   │   ├── advanced-search.html
│   │   └── bulk-import.html
│   ├── viewers/           # View-only modals
│   │   ├── user-profile.html
│   │   └── activity-log.html
│   └── common/            # Reusable components
│       ├── confirm.html
│       └── alert.html

Naming Convention:

  • Use kebab-case: edit-user.html, confirm-delete.html
  • Descriptive names: user-advanced-search.html (not modal1.html)
  • Group by feature: product-edit.html, product-create.html, product-delete.html

7. Data Binding Best Practices

✅ Good:

<!-- Use data attributes for dynamic content -->
<input type="text" data-model="name" />
<span data-text="email"></span>
<img data-attr="src:avatarUrl" alt="Avatar" />

<!-- Loop with conditionals -->
<select data-model="roleID">
  <option
    data-for="role in options.roles"
    data-attr="value:role.id,selected:roleID==role.id"
    data-text="role.name">
  </option>
</select>

❌ Avoid:

<!-- Don't inline values directly -->
<input type="text" value="<?= $name ?>" />
<span><?= $email ?></span>

<!-- Don't mix server-side and client-side rendering -->
<select>
  <?php foreach ($roles as $role): ?>
    <option value="<?= $role['id'] ?>"><?= $role['name'] ?></option>
  <?php endforeach; ?>
</select>

8. State Management

Use StateManager for modal state:

// In template
const modal = StateManager.getModule('modal');

// Listen to changes
modal.watch('data.status', (newStatus) => {
  // Update UI based on status
});

// Update state
modal.set('data.notes', newNotes);

Advantages:

  • Reactive updates (change state, UI updates automatically)
  • Easy debugging (view all state)
  • Easy implementation of Undo/Redo

9. Performance Optimization

Lazy Load Templates:

// Set template path instead of loading immediately
ResponseHandler.config.templateBasePath = '/templates/modals/';

// Template loads only when modal opens
<button data-row-actions='{
  "edit": {
    "modal": {
      "template": "heavy-editor"  // Loads only when needed
    }
  }
}'>Edit</button>

Cache Templates:

// TemplateManager caches automatically
// Opening modal 2nd+ time is faster than first time

10. Security Considerations

Validate on Both Client and Server:

// Client validation
async function submitForm(data) {
  if (!data.email || !data.email.includes('@')) {
    NotificationManager.error('Please enter a valid email');
    return;
  }

  const response = await fetch('/api/save', {...});
  await ResponseHandler.process(response);
}
// Server validation (required!)
if (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
    throw new Exception('Invalid email format');
}

Sanitize Output:

<!-- In template use data attributes (auto-escaped) -->
<div data-text="userInput"></div>

<!-- If HTML needed, specify explicitly -->
<div data-html="trustedHtml"></div>

Don't:

// ❌ Don't use eval
{
  "type": "eval",
  "code": userInput  // Dangerous!
}

// ❌ Don't use innerHTML with user input
element.innerHTML = userInput;

Common Pitfalls (Hybrid Modal)

1. Forgot to Set modalConfig

Symptom: Modal doesn't open or opens without data

Cause:

<!-- ❌ Missing modal config -->
<button data-row-actions='{"edit": {}}'>Edit</button>

Fix:

<!-- ✅ Specify modal config -->
<button data-row-actions='{
  "edit": {
    "modal": {
      "template": "editprofile"
    }
  }
}'>Edit</button>

2. Wrong Template Path

Symptom: Console error "Template not found"

Cause:

// ❌ Wrong template name or file doesn't exist
{
  "modal": {
    "template": "edit-profile"  // Actual file: editprofile.html
  }
}

Fix:

// ✅ Check file name
{
  "modal": {
    "template": "editprofile"  // Matches /templates/modals/editprofile.html
  }
}

// Or use full path
{
  "modal": {
    "templateUrl": "/templates/modals/editprofile.html"
  }
}

3. Data Structure Mismatch

Symptom: Form displays but has no data

Cause:

// ❌ Structure doesn't match auto-extraction
echo json_encode([
    'success' => true,
    'user' => $userData,      // Not 'data'
    'dropdowns' => $options   // Not 'options'
]);

Fix:

// ✅ Option 1: Use auto-extraction structure
echo json_encode([
    'success' => true,
    'data' => $userData,
    'options' => $options
]);

// ✅ Option 2: Specify data explicitly
echo json_encode([
    'success' => true,
    'actions' => [
        [
            'type' => 'modal',
            'template' => 'editprofile',
            'data' => $userData,
            'options' => $options
        ]
    ]
]);

4. Modal Doesn't Close After Submit

Symptom: Modal stays open after submit

Cause:

// ❌ Forgot closeModal action
echo json_encode([
    'success' => true,
    'actions' => [
        ['type' => 'notification', 'message' => 'Saved successfully']
        // Missing closeModal
    ]
]);

Fix:

// ✅ Add closeModal action
echo json_encode([
    'success' => true,
    'actions' => [
        ['type' => 'notification', 'level' => 'success', 'message' => 'Saved successfully'],
        ['type' => 'closeModal'],
        ['type' => 'reload', 'target' => '#users-table']
    ]
]);

5. Context Not Passed

Symptom: modalConfig doesn't work

Cause:

// ❌ Send request without context
await HTTPManager.post('/api/save', data);

Fix:

// ✅ TableManager handles context automatically
// But if calling manually, must pass context
const modalConfig = {
  template: 'editprofile',
  title: 'Edit Profile'
};

const response = await HTTPManager.post('/api/save', data);
await ResponseHandler.process(response, { modalConfig });

6. Wrong force Flag Usage

Symptom: Frontend config overridden unexpectedly

Cause:

// ❌ Using force unnecessarily
echo json_encode([
    'success' => true,
    'actions' => [
        [
            'type' => 'modal',
            'template' => 'editprofile',
            'force' => true  // Makes frontend config not work
        ]
    ]
]);

Fix:

// ✅ Use force only when necessary
echo json_encode([
    'success' => true,
    'data' => $userData,
    'options' => $options
    // No actions - let frontend config work
]);

// Or use suggest instead
echo json_encode([
    'success' => true,
    'actions' => [
        [
            'type' => 'modal',
            'template' => 'editprofile',  // Suggest (no force)
            'data' => $userData
        ]
    ]
]);

State Properties

Property Type Description
state.initialized Boolean Initialization status
state.actionHandlers Map Collection of all action handlers

Example:

// Check if initialized
if (ResponseHandler.state.initialized) {
  console.log('Ready to use');
}

// List all handlers
console.log(ResponseHandler.state.actionHandlers.keys());

Integration

With HTTPManager

const result = await HTTPManager.post('/api/save', data);
await ResponseHandler.process(result);

With TableManager

const table = TableManager.create({
  source: '/api/users',
  onActionComplete: async (response) => {
    await ResponseHandler.process(response);
  }
});

With Form Submit

document.querySelector('form').addEventListener('submit', async (e) => {
  e.preventDefault();

  const formData = new FormData(e.target);
  const response = await fetch('/api/submit', {
    method: 'POST',
    body: formData
  });

  const json = await response.json();
  await ResponseHandler.process(json, {
    form: e.target
  });
});

Best Practices

1. Use Actions Instead of Repetitive Code

❌ Not Recommended:

const response = await fetch('/api/save', ...);
const json = await response.json();

if (json.success) {
  NotificationManager.success('Saved successfully');
  setTimeout(() => {
    window.location.href = '/users';
  }, 1000);
} else {
  NotificationManager.error(json.message);
}

✅ Recommended:

const response = await fetch('/api/save', ...);
const json = await response.json();
await ResponseHandler.process(json);
// API Response
echo json_encode([
    'success' => true,
    'actions' => [
        ['type' => 'notification', 'level' => 'success', 'message' => 'Saved successfully'],
        ['type' => 'redirect', 'url' => '/users', 'delay' => 1000]
    ]
]);

2. Use Context for Shared Data

await ResponseHandler.process(response, {
  tableId: 'users-table',
  formId: 'user-form',
  currentUserId: 123
});

3. Create Custom Actions for Special Behaviors

// Action to refresh table
ResponseHandler.registerHandler('refreshTable', async (action, context) => {
  const tableId = action.tableId || context.tableId;
  const table = document.querySelector(`#${tableId}`);

  if (table && table.tableManager) {
    await table.tableManager.reload();
  }
});

4. Use Conditions to Control Execution

{
  "type": "redirect",
  "url": "/admin",
  "condition": false  // Skip this action
}

5. Order Actions Logically

{
  "actions": [
    {"type": "notification", "message": "Saving..."},
    {"type": "update", "target": "#result", "content": "..."},
    {"type": "notification", "level": "success", "message": "Saved"},
    {"type": "redirect", "url": "/list", "delay": 1000}
  ]
}

Common Pitfalls

1. Forgetting await

❌ Wrong:

ResponseHandler.process(response);  // Doesn't wait
console.log('Done');  // Executes immediately

✅ Correct:

await ResponseHandler.process(response);
console.log('Done');  // Executes after actions complete

2. Wrong Action Type

// ❌ No action type "noti"
{
  "type": "noti",
  "message": "Hello"
}

// ✅ Use "notification"
{
  "type": "notification",
  "message": "Hello"
}

3. Missing target for DOM Actions

// ❌ Missing target
{
  "type": "update",
  "content": "Hello"
}

// ✅ With target
{
  "type": "update",
  "target": "#result",
  "content": "Hello"
}

4. Using eval Without Enabling It

// Must enable first (not recommended)
ResponseHandler.init({
  allowEval: true
});

Performance

1. Use Batch Actions

Instead of calling executeAction multiple times, use process with actions array:

// ✅ Better
await ResponseHandler.process({
  actions: [action1, action2, action3]
});

// ❌ Slower
await ResponseHandler.executeAction(action1);
await ResponseHandler.executeAction(action2);
await ResponseHandler.executeAction(action3);

2. Use Appropriate Delays

{
  "type": "redirect",
  "url": "/dashboard",
  "delay": 1000  // Give time to read notification
}

3. Cleanup When Not Used

// If auto-init not needed
ResponseHandler.config.autoHandleFormResponses = false;

Security

1. Disable eval (Default)

// ✅ Safe - eval is disabled
ResponseHandler.config.allowEval = false;

2. Validate Actions on API Side

// API should filter allowed actions
$allowedActions = ['notification', 'redirect', 'modal'];

foreach ($actions as $action) {
    if (!in_array($action['type'], $allowedActions)) {
        // Don't send unsafe actions
        continue;
    }
}

3. Sanitize Content

// For modal content or update content
$content = htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8');

4. Use CSP Headers

Content-Security-Policy: script-src 'self' 'unsafe-inline'

5. Validate Callback Functions

ResponseHandler.registerHandler('callback', (action) => {
  const allowedFunctions = ['refreshTable', 'closeModal'];

  if (!allowedFunctions.includes(action.function)) {
    console.warn('Callback not allowed:', action.function);
    return;
  }

  // Continue...
});

Browser Compatibility

Browser Version Support
Chrome 60+ ✅ Full
Firefox 55+ ✅ Full
Safari 12+ ✅ Full
Edge 79+ ✅ Full
IE Not Supported

Required Features:

  • ES6 (async/await, arrow functions, Map)
  • Fetch API
  • CustomEvent API
  • Clipboard API (for clipboard action)

Polyfills:

<!-- For older browsers -->
<script src="https://cdn.polyfill.io/v3/polyfill.min.js"></script>

API Reference

Configuration Properties

{
  debug: Boolean,
  defaultRedirectDelay: Number,
  defaultNotificationDuration: Number,
  allowEval: Boolean,
  autoHandleFormResponses: Boolean
}

Methods

Method Parameters Returns Description
init(options) options?: Object ResponseHandler Initialize handler
process(response, context) response: Object, context?: Object Promise<Object> Process response
executeAction(action, context) action: Object, context?: Object Promise<void> Execute single action
registerHandler(type, handler) type: String, handler: Function void Register handler
executeCallback(fn, args) fn: Function\|String, args?: Array void Execute callback
emit(eventName, data) eventName: String, data?: Any void Emit event

Action Types

Type Description Main Properties
notification Show notification level, message, duration
alert Show alert dialog message
confirm Show confirm dialog message, onConfirm
modal Open/close modal action, content, buttons
redirect Navigate to URL url, delay, target
update Update element target, content, method
remove Remove element target, animate
class Manage CSS classes target, class, method
attribute Set attribute target, name, value
event Dispatch event event, target, data
callback Execute function function, args
download Download file url, filename
clipboard Copy to clipboard text, notify
scroll Scroll to element target, behavior
focus Focus element target, select
reload Reload component target
eval Run code (not recommended) code

Summary

ResponseHandler helps:

  • Reduce code duplication - Let API control UI behavior
  • Standardization - Same API response format across project
  • Flexibility - Create custom actions
  • Security - Security-first design
  • Maintainability - Change behavior on server, no JavaScript changes needed