Skip to content

Latest commit

 

History

History
731 lines (608 loc) · 17.2 KB

File metadata and controls

731 lines (608 loc) · 17.2 KB

HTMX Integration Guide for DTMS

Overview

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.

Current Status

  • 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

Integration in DTMS

Automatic Loading

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>

Manual Loading (Advanced)

For pages requiring explicit control:

<script src="/assets/js/lib/htmx.min.js"></script>

Usage Patterns in DTMS

1. Dynamic Module Loading

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>

2. Form Submission with Validation

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>

3. Real-time System Monitoring

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>

4. Module Installation Wizard

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>

HTMX Attributes Reference

Core Attributes

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"

Advanced Attributes

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"}'

Response Headers

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

DTMS-Specific Patterns

1. Module Health Checks

<!-- 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>

2. Configuration Hot-Reload

<!-- 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>

3. Batch Operations

<!-- 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>

Performance Optimization

1. Request Optimization

<!-- 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>

2. Efficient Content Swapping

<!-- 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>

3. Conditional Loading

<!-- 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>

Integration with DTMS APIs

Request Headers

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);
    }
}

Response Patterns

// 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>";
    }
}

Error Handling

Global Error Handling

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

Styling HTMX Elements

CSS for HTMX States

/* 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);
  }
}

Updates and Maintenance

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

Troubleshooting

Common Issues

  1. 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);
  2. Requests not being sent

    • Check hx-trigger conditions
    • Verify target elements exist
    • Check browser network tab for failed requests
  3. Content not swapping

    • Verify hx-target selector
    • Check hx-swap strategy
    • Ensure response content is valid HTML

Debug Mode

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

Extensions

DTMX v2.0.6 includes several useful extensions:

Response Targets Extension

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

WebSocket Extension

<div hx-ext="ws" ws-connect="/ws/live-updates">
  <div ws-send>
    <!-- Content to send via WebSocket -->
  </div>
</div>

Further Reading