Skip to content

Commit

Permalink
enable hx-preserve handing for oob swaps (#2934)
Browse files Browse the repository at this point in the history
* Add support for oob swaps with hx-preserve

* Add tests

* Documentation

* Impove fix to handle when oob swaps shouldSwap set false
  • Loading branch information
MichaelWest22 authored Oct 3, 2024
1 parent d528c9d commit 4a81723
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 1 deletion.
4 changes: 3 additions & 1 deletion src/htmx.js
Original file line number Diff line number Diff line change
Expand Up @@ -1450,7 +1450,9 @@ var htmx = (function() {

target = beforeSwapDetails.target // allow re-targeting
if (beforeSwapDetails.shouldSwap) {
handlePreservedElements(fragment)
swapWithStyle(swapStyle, target, target, fragment, settleInfo)
restorePreservedElements()
}
forEach(settleInfo.elts, function(elt) {
triggerEvent(elt, 'htmx:oobAfterSwap', beforeSwapDetails)
Expand Down Expand Up @@ -1479,7 +1481,7 @@ var htmx = (function() {
}

/**
* @param {DocumentFragment} fragment
* @param {DocumentFragment|ParentNode} fragment
*/
function handlePreservedElements(fragment) {
forEach(findAll(fragment, '[hx-preserve], [data-hx-preserve]'), function(preservedElt) {
Expand Down
34 changes: 34 additions & 0 deletions test/attributes/hx-preserve.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,38 @@ describe('hx-preserve attribute', function() {
byId('d1').innerHTML.should.equal('Old Content')
byId('d2').innerHTML.should.equal('New Content')
})

it('preserved element should not be swapped if it is part of a oob swap', function() {
this.server.respondWith('GET', '/test', "Normal Content<div id='d2' hx-swap-oob='true'><div id='d3' hx-preserve>New oob Content</div><div id='d4'>New oob Content</div></div>")
var div1 = make("<div id='d1' hx-get='/test'>Click Me!</div>")
var div2 = make("<div id='d2'><div id='d3' hx-preserve>Old Content</div></div>")
div1.click()
this.server.respond()
byId('d1').innerHTML.should.equal('Normal Content')
byId('d3').innerHTML.should.equal('Old Content')
byId('d4').innerHTML.should.equal('New oob Content')
})

it('preserved element should not be swapped if it is part of a hx-select-oob swap', function() {
this.server.respondWith('GET', '/test', "Normal Content<div id='d2'><div id='d3' hx-preserve>New oob Content</div><div id='d4'>New oob Content</div></div>")
var div1 = make("<div id='d1' hx-get='/test' hx-select-oob='#d2'>Click Me!</div>")
var div2 = make("<div id='d2'><div id='d3' hx-preserve>Old Content</div></div>")
div1.click()
this.server.respond()
byId('d1').innerHTML.should.equal('Normal Content')
byId('d3').innerHTML.should.equal('Old Content')
byId('d4').innerHTML.should.equal('New oob Content')
})

it('preserved element should relocated unchanged if it is part of a oob swap targeting a different loction', function() {
this.server.respondWith('GET', '/test', "Normal Content<div id='d2' hx-swap-oob='innerHTML:#d5'><div id='d3' hx-preserve>New oob Content</div><div id='d4'>New oob Content</div></div>")
var div1 = make("<div id='d1' hx-get='/test'>Click Me!</div>")
var div2 = make("<div id='d2'><div id='d3' hx-preserve>Old Content</div></div>")
var div5 = make("<div id='d5'></div>")
div1.click()
this.server.respond()
byId('d1').innerHTML.should.equal('Normal Content')
byId('d2').innerHTML.should.equal('')
byId('d5').innerHTML.should.equal('<div id="d3" hx-preserve="">Old Content</div><div id="d4">New oob Content</div>')
})
})
12 changes: 12 additions & 0 deletions www/content/attributes/hx-preserve.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,18 @@ The response requires an element with the same `id`, but its type and other attr
Note that some elements cannot unfortunately be preserved properly, such as `<input type="text">` (focus and caret position are lost), iframes or certain types of videos. To tackle some of these cases we recommend the [morphdom extension](https://github.com/bigskysoftware/htmx-extensions/blob/main/src/morphdom-swap/README.md), which does a more elaborate DOM
reconciliation.

## OOB Swap Usage

You can include `hx-preserve` in the inner response of a [hx-swap-oob](@/attributes/hx-swap-oob.md) and it will preserve the element unchanged during the out of band partial replacement as well. However, you cannot place `hx-preserve` on the same element as the `hx-swap-oob` is placed. For example, here is an oob response that replaces notify but leaves the retain div unchanged.

```html
<div id="notify" hx-swap-oob="true">
<p>The below content will not be changed</p>
<div id="retain" hx-preserve>Use the on-page contents</div>
</div>
```

## Notes

* `hx-preserve` is not inherited
* `hx-preserve` can cause elements to be relocated to a new location when swapping in a partial response

0 comments on commit 4a81723

Please sign in to comment.