HTMX enables modern web interactions with minimal JavaScript by extending HTML with attributes for AJAX requests, WebSocket connections, and server-sent events. DTMS includes HTMX v2.0.6 (latest) with automatic update management.
- Version: 2.0.6 (Latest as of August 2025)
- File Size: 49.8KB (minified)
- Integrity: SHA384 verified
- Last Updated: 2025-08-23T13:38:56+08:00
- Loading Strategy: CDN-with-fallback
- Local Path:
/public/assets/js/lib/htmx.min.js - CDN URL:
https://unpkg.com/htmx.org@2.0.6/dist/htmx.min.js
HTMX is managed by the DTMS Library Manager and automatically loaded when HTMX attributes are detected:
<!-- Automatic loading via DTMS lib manager -->
<script src="/assets/js/lib/dtms-lib-manager.js"></script>
<script>
// HTMX will be loaded automatically when hx-* attributes are detected
DTMSLibManager.loadAll();
</script>For pages requiring explicit control:
<script src="/assets/js/lib/htmx.min.js"></script>Perfect for DTMS admin interfaces and plugin management:
<!-- Module List with Dynamic Loading -->
<div id="module-container">
<!-- Initial load -->
<div
hx-get="/core/api/v2/plugins"
hx-trigger="load"
hx-target="#module-list"
hx-indicator="#loading-spinner"
>
<div id="loading-spinner" class="spinner">Loading plugins...</div>
<div id="module-list"></div>
</div>
<!-- Refresh button -->
<button
hx-get="/core/api/v2/plugins"
hx-target="#module-list"
hx-indicator="#loading-spinner"
>
🔄 Refresh Plugins
</button>
</div>Enhanced forms for DTMS configuration and module management:
<!-- Plugin Configuration Form -->
<form
hx-post="/core/api/v2/plugins"
hx-target="#form-result"
hx-indicator="#form-spinner"
hx-swap="innerHTML"
hx-on::after-request="handleFormResponse(event)"
>
<div class="form-group">
<label>Plugin Name</label>
<input
name="name"
required
hx-post="/core/api/v2/plugins/validate"
hx-trigger="blur"
hx-target="next .validation-message"
hx-include="[name='name']"
/>
<div class="validation-message"></div>
</div>
<div class="form-group">
<label>Description</label>
<textarea name="description"></textarea>
</div>
<!-- Dynamic configuration fields -->
<div
id="config-fields"
hx-get="/core/api/v2/plugins/config-template"
hx-trigger="change from:#plugin-type"
hx-include="[name='type']"
></div>
<button type="submit">
<span class="htmx-indicator" id="form-spinner">Saving...</span>
Create Plugin
</button>
<div id="form-result"></div>
</form>
<script>
function handleFormResponse(event) {
if (event.detail.xhr.status === 200) {
// Success - refresh module list
htmx.trigger("#module-container", "refresh");
// Reset form
event.target.reset();
}
}
</script>HTMX-powered live updates for DTMS health monitoring:
<!-- System Health Dashboard -->
<div class="health-dashboard">
<!-- Auto-refreshing status cards -->
<div
id="system-status"
hx-get="/core/api/system/status"
hx-trigger="every 30s"
hx-swap="innerHTML"
hx-target="this"
>
<!-- Initial status will be loaded -->
<div class="loading">Checking system status...</div>
</div>
<!-- Service-specific health checks -->
<div class="service-checks">
<template id="service-template">
<div
class="service-card"
hx-get="/core/api/services/{service}/health"
hx-trigger="every 60s"
hx-target="find .health-status"
>
<h3>{service-name}</h3>
<div class="health-status">Checking...</div>
<button
hx-post="/core/api/services/{service}/restart"
hx-confirm="Restart this service?"
hx-target="closest .service-card"
class="restart-btn"
>
Restart
</button>
</div>
</template>
</div>
<!-- Log viewer with live updates -->
<div class="log-viewer">
<h3>Live Logs</h3>
<div
id="log-stream"
hx-get="/core/api/logs/stream"
hx-trigger="every 10s"
hx-swap="beforeend"
hx-target="this"
></div>
<button
hx-delete="/core/api/logs"
hx-confirm="Clear all logs?"
hx-target="#log-stream"
hx-swap="innerHTML"
>
Clear Logs
</button>
</div>
</div>Step-by-step installation process with progress tracking:
<!-- Installation Wizard -->
<div
id="install-wizard"
hx-ext="response-targets"
hx-target-error="#error-display"
>
<!-- Step 1: Module Selection -->
<div id="step-1" class="wizard-step active">
<h3>Select Module Type</h3>
<div
hx-get="/core/api/v2/modules/types"
hx-trigger="load"
hx-target="#module-types"
>
<div id="module-types"></div>
</div>
<button
hx-get="/core/api/v2/modules/configure"
hx-target="#step-2"
hx-swap="outerHTML"
hx-include="#module-types [name='type']:checked"
hx-on::after-request="showStep(2)"
>
Next: Configure
</button>
</div>
<!-- Step 2: Configuration (loaded dynamically) -->
<div id="step-2" class="wizard-step"></div>
<!-- Step 3: Installation Progress -->
<div id="step-3" class="wizard-step">
<h3>Installing Module</h3>
<div class="progress-container">
<div id="progress-bar" class="progress-bar"></div>
<div id="progress-status">Ready to install...</div>
</div>
<!-- Installation with progress polling -->
<button
id="install-btn"
hx-post="/core/api/v2/modules/install"
hx-target="#progress-status"
hx-trigger="click"
hx-on::after-request="pollInstallProgress()"
hx-include="#install-wizard input, #install-wizard select"
>
Install Module
</button>
</div>
<div id="error-display" class="error-message"></div>
</div>
<script>
let progressInterval;
function showStep(step) {
document
.querySelectorAll(".wizard-step")
.forEach((s) => s.classList.remove("active"));
document.querySelector(`#step-${step}`).classList.add("active");
}
function pollInstallProgress() {
const installId = event.detail.xhr.responseJSON?.install_id;
if (!installId) return;
progressInterval = setInterval(() => {
htmx
.ajax("GET", `/core/api/v2/modules/install/${installId}/progress`, {
target: "#progress-status",
swap: "innerHTML",
})
.then((response) => {
const data = JSON.parse(response);
if (data.status === "complete" || data.status === "error") {
clearInterval(progressInterval);
if (data.status === "complete") {
showStep(4); // Success step
}
}
// Update progress bar
document.querySelector("#progress-bar").style.width = `${
data.progress || 0
}%`;
});
}, 1000);
}
</script>| Attribute | Purpose | Example |
|---|---|---|
hx-get |
GET request | hx-get="/core/api/modules" |
hx-post |
POST request | hx-post="/core/api/modules" |
hx-put |
PUT request | hx-put="/core/api/modules/123" |
hx-delete |
DELETE request | hx-delete="/core/api/modules/123" |
hx-trigger |
When to send request | hx-trigger="click, keyup delay:500ms" |
hx-target |
Where to put response | hx-target="#results" |
hx-swap |
How to swap content | hx-swap="innerHTML" |
hx-include |
Include additional data | hx-include="#form-data" |
hx-indicator |
Show loading state | hx-indicator="#loading" |
| Attribute | Purpose | Example |
|---|---|---|
hx-push-url |
Update browser URL | hx-push-url="true" |
hx-confirm |
Confirmation dialog | hx-confirm="Are you sure?" |
hx-ext |
Load extensions | hx-ext="response-targets" |
hx-boost |
Enhance regular links | hx-boost="true" |
hx-sync |
Synchronize requests | hx-sync="this:drop" |
hx-vals |
Include JSON data | hx-vals='{"key": "value"}' |
DTMS APIs can use these headers to control HTMX behavior:
// PHP examples for DTMS controllers
header('HX-Trigger: moduleInstalled'); // Trigger event
header('HX-Redirect: /admin/modules'); // Redirect
header('HX-Refresh: true'); // Refresh page
header('HX-Push-Url: /admin/modules/new'); // Update URL<!-- Health status with automatic recovery -->
<div
class="module-health"
hx-get="/core/api/modules/{id}/health"
hx-trigger="every 30s"
hx-target="this"
hx-swap="outerHTML"
hx-on::response-error="handleHealthError(event)"
>
<span class="status-indicator {status}"></span>
<span>{module-name}</span>
<!-- Auto-restart on failure -->
<button
hx-post="/core/api/modules/{id}/restart"
hx-trigger="click"
hx-confirm="Restart module?"
style="display: none;"
class="auto-restart"
>
Auto-restart
</button>
</div>
<script>
function handleHealthError(event) {
// Show restart button on health check failure
event.target.querySelector(".auto-restart").style.display = "inline-block";
}
</script><!-- Configuration editor with live preview -->
<div class="config-editor">
<textarea
name="config"
hx-post="/core/api/modules/{id}/validate-config"
hx-trigger="keyup changed delay:1s"
hx-target="#config-preview"
hx-swap="innerHTML"
></textarea>
<div id="config-preview" class="config-preview">
<!-- Live validation results -->
</div>
<button
hx-post="/core/api/modules/{id}/apply-config"
hx-include="[name='config']"
hx-target="#apply-result"
hx-confirm="Apply configuration changes?"
>
Apply Configuration
</button>
<div id="apply-result"></div>
</div><!-- Bulk module management -->
<form id="bulk-operations">
<div class="module-list">
<!-- Checkbox for each module -->
<label class="module-item">
<input type="checkbox" name="modules[]" value="module1" />
<span>Module 1</span>
<span
class="status"
hx-get="/core/api/modules/module1/status"
hx-trigger="every 60s"
>
</span>
</label>
<!-- More modules... -->
</div>
<!-- Batch action buttons -->
<div class="batch-actions">
<button
hx-post="/core/api/modules/batch/start"
hx-include="#bulk-operations"
hx-target="#batch-result"
>
Start Selected
</button>
<button
hx-post="/core/api/modules/batch/stop"
hx-include="#bulk-operations"
hx-target="#batch-result"
>
Stop Selected
</button>
<button
hx-post="/core/api/modules/batch/update"
hx-include="#bulk-operations"
hx-target="#batch-result"
hx-confirm="Update all selected modules?"
>
Update Selected
</button>
</div>
<div id="batch-result"></div>
</form><!-- Debounce search requests -->
<input
type="search"
placeholder="Search modules..."
hx-get="/core/api/search/modules"
hx-trigger="keyup changed delay:500ms"
hx-target="#search-results"
hx-indicator="#search-spinner"
/>
<!-- Sync requests to prevent race conditions -->
<div
hx-get="/core/api/modules/status"
hx-trigger="every 10s"
hx-sync="this:drop"
></div><!-- Use appropriate swap strategies -->
<div
hx-get="/core/api/logs/latest"
hx-trigger="every 5s"
hx-swap="beforeend"
<!--
Append
new
logs
--
>
hx-target="#log-container">
</div>
<div
hx-get="/core/api/status"
hx-trigger="every 30s"
hx-swap="innerHTML"
<!--
Replace
status
--
>
hx-target="#status-display">
</div><!-- Load content only when visible -->
<div
hx-get="/core/api/modules/details"
hx-trigger="intersect once"
hx-target="this"
>
Click to load details...
</div>
<!-- Load on user interaction -->
<div
hx-get="/core/api/modules/advanced"
hx-trigger="click once"
hx-target="this"
>
<button>Show Advanced Options</button>
</div>DTMS APIs automatically handle HTMX requests:
// In DTMS controllers
function dtms_handle_module_status($baseDir, $route) {
// Check if this is an HTMX request
$isHTMX = isset($_SERVER['HTTP_HX_REQUEST']);
$status = getModuleStatus();
if ($isHTMX) {
// Return HTML fragment for HTMX
echo "<span class='status-{$status['level']}'>{$status['message']}</span>";
} else {
// Return JSON for regular AJAX
dtms_jsonResponse($baseDir, $route, $status);
}
}// Trigger events after successful operations
function dtms_handle_module_install($baseDir, $route) {
// ... installation logic
if ($success) {
header('HX-Trigger: moduleInstalled, refreshModuleList');
echo "<div class='success'>Module installed successfully!</div>";
} else {
header('HX-Trigger: installationFailed');
echo "<div class='error'>Installation failed: {$error}</div>";
}
}<body hx-ext="response-targets">
<!-- Global error container -->
<div id="global-errors" hx-target-error="this"></div>
<!-- Main content -->
<div id="main-content"></div>
</body>
<script>
// Handle HTMX errors globally
document.body.addEventListener("htmx:responseError", function (event) {
console.error("HTMX Error:", event.detail);
// Show user-friendly error message
const errorDiv = document.querySelector("#global-errors");
errorDiv.innerHTML = `
<div class="error-toast">
<strong>Operation Failed</strong>
<p>Please try again or contact support if the problem persists.</p>
<button onclick="this.parentElement.remove()">Dismiss</button>
</div>
`;
});
</script>/* Loading indicators */
.htmx-request .htmx-indicator {
display: inline-block;
}
.htmx-indicator {
display: none;
}
/* Request states */
.htmx-request {
opacity: 0.7;
cursor: wait;
}
/* Smooth transitions */
.htmx-swapping {
opacity: 0;
transition: opacity 200ms ease-out;
}
.htmx-settling {
opacity: 1;
transition: opacity 200ms ease-in;
}
/* DTMS-specific states */
.module-card.htmx-request {
border-left: 3px solid #3b82f6;
background-color: #f8fafc;
}
.status-indicator.htmx-request::before {
content: "🔄";
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}HTMX is automatically maintained by DTMS Library Manager:
- Automatic Updates: Available through admin interface at
/admin/libraries.html - Version Checking:
./scripts/update-libraries.sh check - Batch Updates:
./scripts/update-libraries.sh update-all - API Integration:
POST /core/api/libraries/update
-
HTMX not responding
# Check if HTMX is loaded curl -I http://dtms.localhost/assets/js/lib/htmx.min.js # Verify in browser console console.log(window.htmx);
-
Requests not being sent
- Check
hx-triggerconditions - Verify target elements exist
- Check browser network tab for failed requests
- Check
-
Content not swapping
- Verify
hx-targetselector - Check
hx-swapstrategy - Ensure response content is valid HTML
- Verify
<script>
// Enable HTMX debug logging
htmx.logAll();
// Custom request logging
document.body.addEventListener("htmx:beforeRequest", function (event) {
console.log("HTMX Request:", event.detail);
});
document.body.addEventListener("htmx:afterRequest", function (event) {
console.log("HTMX Response:", event.detail);
});
</script>DTMX v2.0.6 includes several useful extensions:
<div hx-ext="response-targets">
<button
hx-post="/api/action"
hx-target-200="#success-div"
hx-target-400="#error-div"
hx-target-500="#server-error-div"
>
Submit
</button>
</div><div hx-ext="ws" ws-connect="/ws/live-updates">
<div ws-send>
<!-- Content to send via WebSocket -->
</div>
</div>