Now.js Framework Documentation
ResponseHandler
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.dataanddata.optionsfor 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 objectOr 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 objectcontext(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 objectcontext(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 namehandler(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 scopeargs(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 namedata(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 displayduration: 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 messageonConfirm: 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 modalclose/hide: Close modal
Properties:
content: HTML contentclassName: Modal CSS classbuttons: Array of buttonsoptions: 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 URLdelay: 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 innerHTMLtext: Set textContentvalue: Set value (for inputs)append: Append contentprepend: Prepend content
7. Remove
Remove element
{
"type": "remove",
"target": ".temp-message",
"animate": true
}Properties:
target: Selector or elementanimate: Show animation before removal
8. Class
Manage CSS classes
{
"type": "class",
"target": "#myDiv",
"class": "active highlight",
"method": "add"
}Methods:
add: Add classremove: Remove classtoggle: Toggle class
9. Attribute
Set/remove attribute
{
"type": "attribute",
"target": "#myInput",
"name": "disabled",
"value": true
}Properties:
name: Attribute namevalue: Value (usenullto 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"
}17. Eval (Not Recommended)
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}
}
]
}4. Hybrid Modal - Frontend-Driven (Recommended)
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:
- API Force - API explicitly overrides with
force: true(10% special cases) - API Suggest - API suggests template without force (optional)
- 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 pathtitle- Modal titleclassName- CSS class for modal stylingsize- 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:
- User clicks "Edit" → TableManager reads
data-row-actionsconfig - Sends action to API → API returns data + options
- ResponseHandler extracts
data.dataanddata.optionsautomatically - Loads template from frontend config → Binds data via TemplateManager
- 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.htmlNaming Convention:
- Use kebab-case:
edit-user.html,confirm-delete.html - Descriptive names:
user-advanced-search.html(notmodal1.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 time10. 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 complete2. 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