FathurDev

TutorialJavascriptHTMX

Published on 2024-11-04

Advanced HTMX for Web Developers

HTMX is a great library that allows you to build modern web applications without the complexities often associated with JavaScript frameworks.

In this post, I’ll be sharing some advanced HTMX techniques and tips that I’ve discovered along the way.

Attaching Fields to an HTMX Request Payload

When sending a request to the server with HTMX, you can attach additional fields to the request payload by using the hx-include attribute.

<input id="first_name" type="text" name="first_name" />
<input id="email" type="text" name="email" />

<button
  hx-post="/api/action"
  hx-include="#first_name, #email"
>
  Submit
</button>

Inline HTMX events

HTMX has a dedicated documentation page on HTMX events, but it currently lacks examples for inline events.

To use inline events in HTMX, simply apply the hx-on attribute and use snake case for the event name. For instance, beforeRequest becomes before-request.

Here’s an example based on the documentation found at HTMX Events:

EventInline Event (hx-on)
htmx:beforeRequesthx-on:htmx:before-request
htmx:configRequesthx-on:htmx:config-request
htmx:beforeOnLoadhx-on:htmx:before-on-load
htmx:afterOnLoadhx-on:htmx:after-on-load
htmx:beforeSwaphx-on:htmx:before-swap
htmx:afterSwaphx-on:htmx:after-swap
htmx:beforeSettlehx-on:htmx:before-settle
htmx:afterSettlehx-on:htmx:after-settle
htmx:responseErrorhx-on:htmx:response-error
htmx:sendErrorhx-on:htmx:send-error
htmx:invalidResponsehx-on:htmx:invalid-response
htmx:triggeredhx-on:htmx:triggered
htmx:loadhx-on:htmx:load

Example usage

<button
  hx-post="/api/action"
  hx-on:htmx:before-request="return confirm('Are you sure?')"
  hx-on:htmx:after-request="alert('Deleted!')"
>
  Delete
</button>

Event Delegation

A common pattern with HTMX is to return a partial HTML file in response to an HTMX request.

For example, if you have a partial template called form.html, you can return this partial with previously entered fields and field-specific error messages if backend validation fails.

<div>
  <!-- include form.html -->
  <form 
    hx-post="/api/action"
    hx-on:htmx:after-request="alert('Request finished!')"
    hx-on:htmx:after-swap="alert('Dom Updated!')"
    >
    
  </form>
  <!-- end include form.html -->
<div>

The issue with the example above is that the htmx-after-request and htmx-after-swap event won’t work because the original HTML is replaced by the new response HTML.

To resolve this, you can use event delegation by moving the event listener to a parent element. This ensures that the event will continue to work, even after the content is replaced.

<div
  hx-on:htmx:after-request="alert('Request finished!')"
  hx-on:htmx:after-swap="alert('Dom Updated!')"
  >
  <!-- include form.html -->
  <form 
    hx-post="/api/action"
    >
    
  </form>
  <!-- end include form.html -->
<div>

Multiple HTMX Event Callbacks

You can initialize multiple HTMX event callbacks by separating each callback name with a comma.

<button
  hx-post="/api/action"
  hx-on:htmx:after-request="setIsFormSubmitted(true), addClass('#myForm', 'active')"
>
  Delete
</button>

Making JavaScript Callbacks Accessible to HTMX

To make your callback function accessible to an HTMX event, assign it to a window object.

window.setIsFormSubmitted = function (state) {
  // do something
};

Common JavaScript Callbacks

Here are some common JavaScript callbacks that may be useful in your project.

window.setDisabled = function (element_id) {
  let element = $(element_id);

  element.prop("disabled", true);
};

window.setReadOnly = function (element_id) {
  let element = $(element_id);

  element.prop("readonly", true);
};

window.hideElement = function (element_id) {
  let element = $(element_id);
  element.hide();
};

window.showElement = function (element_id) {
  let element = $(element_id);
  element.show();
};

window.addClass = function (selector, className) {
  $(selector).addClass(className);
};

window.removeClass = function (selector, className) {
  $(selector).removeClass(className);
};

window.setFocus = function (selector) {
  $(selector).focus();
};

window.isEmpty = function (variable) {
  variable = $.trim(variable);

  return variable === undefined || variable === null || variable === "";
};

window.isFieldEmpty = function (id) {
  let value = $(id).val();

  return isEmpty(value);
};

Automatic CSRF Token for HTMX Requests

Here’s how to automatically add a CSRF token to HTMX requests. Simply define this in your main JavaScript file:

// get CSRF token for Django

let token_el = document.getElementsByName("csrfmiddlewaretoken");
let csrf_token = token_el[0].value;

document.body.addEventListener("htmx:configRequest", (event) => {
  event.detail.headers["X-CSRFToken"] = csrf_token;
});

Manually Triggering HTMX Requests

You can manually trigger an HTMX request using a custom JavaScript event. Here’s an example:

<form  
  hx-post="/api/action"
  hx-trigger="submitKeywordSearch from:body"
>
</form>

In your JavaScript file, you can trigger an HTMX request based on user actions, like clicking a button. Here’s an example:

let event_payload = {};

document.body.dispatchEvent(new CustomEvent("submitKeywordSearch", event_payload));

This approach lets you initiate HTMX requests directly from JavaScript when a specific event occurs.

Preventing HTMX Triggers with Custom Validation

You can prevent an HTMX trigger by using custom validation. Simply declare the condition within the trigger.

For example, this form will not be submitted if search_query is empty (isFieldEmpty is a custom function):

<form  
  hx-post="/api/search"
  hx-trigger="submit[!isFieldEmpty('#search_query')]"
>
  <input type="text" name="search_query" id="search_query" />
  <button type="submit">Submit</button>
</form>

Conditional polling with HTMX

Let's say you want to poll an API every 20 seconds to check the status of user submission, but you want to stop polling when the submission is ready.

Initial HTMX polling

<div 
    hx-get="/api/submissions"
    hx-trigger="every 20s [FETCHING_SUBMISSION]"
    hx-on:htmx:after-request="fetchingSubmissionStatusAfterRequest(event)"
>
</div>

Initial JS state, since FETCHING_SUBMISSION is false, by default the polling will not start.

window.FETCHING_SUBMISSION = false;

function setIsFetchingSubmission(state) {
  window.FETCHING_SUBMISSION = state;
}

To trigger the polling, we just need to set FETCHING_SUBMISSION state to true.

document.body.addEventListener("newSubmissionAdded", (event) => {
  setIsFetchingSubmission(true);
});

Triggering the polling via user action

<form  
  hx-post="/api/submissions"
  hx-on:htmx:after-request="submissionsAfterRequest(event)"
>
</form>

// the JS part

window.submissionsAfterRequest = function (e) {
  document.body.dispatchEvent(new Event("newSubmissionAdded"));
}

Stopping the polling. Each time the polling is triggered, we will check the status of the submission via hx-on:htmx:after-request. If the status is ready, we will stop the polling.

const HTMX_STOP_POLLING = 286;

window.fetchingSubmissionStatusAfterRequest =
  function (e) {
    const status = e.detail.xhr.status;

    if (status === HTMX_STOP_POLLING) {
      setIsFetchingSubmission(false);
    }
  };

HTMX afterSwap event not working?

You should add the afterSwap event to the element that you want to swap, instead of the trigger element.

This will not work

<button
  hx-post="/api/action"
  hx-target="#content"
  hx-on:htmx:after-swap="addClass('#content', 'bg-yellow')"
>
  Submit
</button>

<div id="content"></div>

This will work

<button
  hx-post="/api/action"
  hx-target="#content"
>
  Submit
</button>

<div 
  id="content" 
  hx-on:htmx:after-swap="addClass('#content', 'bg-yellow')">
</div>

Restarting HTMX Polling After It Has Been Stopped

Once HTMX polling is stopped, it cannot be restarted directly. To resume polling, you need to reload the HTMX element.

Here’s an example of how to reload the HTMX element.

Polling remains active until FETCHING_TITLES is set to false, at which point it will stop.

To restart polling, reload the HTMX element and set FETCHING_TITLES to true.

<div
    id="title_fetcher" 
    hx-get="/api/action"
    hx-trigger="every 10s [FETCHING_TITLES]"
    hx-on:htmx:after-request="reloadHTMXElement('title_fetcher')"
    >
</div>
window.reloadHTMXElement = function (element_id) {
  const htmx_element = htmx.find(element_id);
  const clone = htmx_element.cloneNode();

  htmx_element.parentNode.insertBefore(clone, htmx_element);
  htmx.remove(htmx_element);
  htmx.process(element_id);
};

Conclusion

HTMX is a powerful library that allows you to build modern web applications with minimal JavaScript.

By understanding these advanced concepts like proper event handling, event callback, and request triggering, you can create more robust and maintainable applications.

I hope this post has been helpful in your journey to mastering HTMX. Happy coding!