htmx icon indicating copy to clipboard operation
htmx copied to clipboard

hx-boost ignores changing body css classes

Open dwasyl opened this issue 2 years ago • 8 comments

Using hx-boost="true" ignores any new/changed class= entries for the body tag (when hx-boost is on the <body> tag).

Is this the intended behaviour? We have some functionality that depends on body classes, and while we can restructure an inner div to serve the same function, it seems unnecessary as boost should merge in the new body.

I've tested this on 1.9.0.

If this is the intended behaviour perhaps something could be added to the docs.

dwasyl avatar Apr 17 '23 06:04 dwasyl

I'm experiencing this issue as well; using an inner div as a workaround for now but it would be tidier to be able to use the body element.

tristan-pv01 avatar Jun 14 '23 10:06 tristan-pv01

You could change the default swap style to outerHTML to also swap the body. Or hook to afterSwap and call htmx.process on body.

How are the new classes added to the body?

andryyy avatar Jun 14 '23 10:06 andryyy

You could change the default swap style to outerHTML to also swap the body.

I did try this but it wasn't working for me. I'm adding the classes to the body with:

<body
class="application_container
{% block base_class %}{% endblock base_class %}">

With each template having its class in the base_class block.

tristan-pv01 avatar Jun 21 '23 13:06 tristan-pv01

It looks like hx-boost ignores all attribute changes to the body, not just the class tag. It will update based on changes to the contents of the head tag but completely ignores any change to the body tag.

cj avatar Aug 28 '23 17:08 cj

You could change the default swap style to outerHTML to also swap the body. Or hook to afterSwap and call htmx.process on body.

Ran into this issue as well. Using outerHTML swap style didn't seem to work, but was able to work around this with an afterSwap event:

document.body.addEventListener('htmx:afterSwap', function(evt) {
    const parser = new DOMParser();
    const parsedResponse = parser.parseFromString(evt.detail.xhr.response, "text/html");
    const bodyAttributes = parsedResponse.getElementsByTagName('body')[0].attributes;
    for (const attribute of bodyAttributes) {
        evt.detail.target.setAttribute(attribute.name, attribute.value);
    }
});

Would be nice to not have to work around this though.

backlineint avatar Sep 20 '23 13:09 backlineint

This problem interfered with my use of Alpine, since the page wasn't finding x-data defined on the body when boosting from another page.

scrhartley avatar Sep 26 '23 13:09 scrhartley

You could change the default swap style to outerHTML to also swap the body. Or hook to afterSwap and call htmx.process on body.

Ran into this issue as well. Using outerHTML swap style didn't seem to work, but was able to work around this with an afterSwap event:

document.body.addEventListener('htmx:afterSwap', function(evt) {
    const parser = new DOMParser();
    const parsedResponse = parser.parseFromString(evt.detail.xhr.response, "text/html");
    const bodyAttributes = parsedResponse.getElementsByTagName('body')[0].attributes;
    for (const attribute of bodyAttributes) {
        evt.detail.target.setAttribute(attribute.name, attribute.value);
    }
});

Would be nice to not have to work around this though.

I noticed this code updates any element on any htmx-request, and doesn't remove attributes.

Here is an updated version which skips anything that isn't a BODY tag, and removes all attributes before adding the new ones:

// HX-Boost does not update body attributes nor classes by default.
htmx.on('htmx:afterSwap', function(evt) {
  if (evt.detail.target.tagName != "BODY") {
    return;
  }

  const parser = new DOMParser();
  const parsedResponse = parser.parseFromString(evt.detail.xhr.response, "text/html");
  const bodyAttributes = parsedResponse.getElementsByTagName('body')[0].attributes;
  const targetEl = evt.detail.target;
  
  Object.values(targetEl.attributes).forEach(({ name }) =>
    targetEl.removeAttribute(name)
  );

  for (const attribute of bodyAttributes) {
      evt.detail.target.setAttribute(attribute.name, attribute.value);
  }
});

maekoos avatar Jul 12 '24 08:07 maekoos