Skip to content

Problem applying focus() after afterWap #3411

@callor

Description

@callor

I am using the following code combining thymeleaf and HTMX

I wrote a code to receive a serial number from an input form, and when the focus is removed from this input form, a request is sent to the server and the response is received as html based on thymleaf.

After the screen swap rendering is complete, I want to capture the htmx:afterSwap event in javascript and react based on the result.

If the serial number is empty or duplicated, we want to implement a user UX that gives focus() to the input tag so that the user can input again.

However, the focus is not assigned to the serial number and moves on to the next input tag. After entering the serial number, use the tab key or use the mouse to click the next input tag.

Please tell me how to solve this problem.

I tried using various events, such as html:afterSettle and html:afterSwap, but I couldn't solve it.

input form

        <label class="equipment-label" id="equipmentSn-label">
            <strong class="label flex-1 flex justify-end-safe">
                <span class="text-red-700">*</span>
                <span class="text-blue-700">Serial Num</span>
            </strong>
            <input class="equipment-input-text"
                   id="equipmentSn"
                   th:field="*{equipmentSn}"
                   placeholder="ex) AFUXXXXXX"
                   type="text"
                   required
                   oninvalid="this.setCustomValidity('Please enter your serial number')"
                   oninput="this.setCustomValidity('')"
                   th:hx-post="@{/equipment/check-serial}"
                   hx-trigger="blure changed delay:100ms"
                   hx-target="#sn-check-result"/>
        </label>
        <label class="equipment-label">
            <strong class="label flex-1"></strong>
            <strong id="sn-check-result" class="flex-2 text-sm mt-1"></strong>
        </label>

Response HTML Code

<div th:fragment="result">
    <span th:if="${#strings.isEmpty(equipmentSn)}"  data-invalid="true" class="text-gray-400">
       Please enter your serial number
    </span>
    <span th:if="${!#strings.isEmpty(equipmentSn) and exists}"  data-invalid="true" class="text-red-700 font-bold">
        ⚠ This serial number has already been registered.
    </span>
    <span th:if="${!#strings.isEmpty(equipmentSn) and !exists}" data-invalid="false" class="text-green-600">
        ✔ Available serial numbers.
    </span>
</div>
document.body.addEventListener("htmx:afterSwap", function (evt) {
  const targetId = evt.target.id;

  if (targetId === "sn-check-result") {
    const dataInvalid = evt.detail.target.querySelector("[data-invalid='true']");

    if (dataInvalid) {
      setTimeout(() => {
        const inputSn = document.querySelector("#equipmentSn");
        console.log("equipmentSn element:", inputSn);
        console.log("is visible:", inputSn?.offsetParent !== null);
        console.log("is enabled:", !inputSn?.disabled);

        if (inputSn && inputSn.offsetParent !== null && !inputSn.disabled) {
          inputSn.focus();
          inputSn.select();
        } else {
          console.log("Focus failed - element not ready");
        }
      }, 100);
    }
  }
});

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions