bigskysoftware / htmx

</> htmx - high power tools for HTML
https://htmx.org
Other
37.44k stars 1.27k forks source link

hx-boost ignores changing body css classes #1384

Open dwasyl opened 1 year ago

dwasyl commented 1 year ago

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.

tristan-pv01 commented 1 year ago

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.

andryyy commented 1 year ago

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?

tristan-pv01 commented 1 year ago

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.

cj commented 1 year ago

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.

backlineint commented 1 year ago

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.

scrhartley commented 12 months ago

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.

maekoos commented 2 months ago

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