Skip to content

Commit 9dcb028

Browse files
committed
fix oobBeforeSwap and hx-select-oob backwards compat
1 parent 67a3013 commit 9dcb028

5 files changed

Lines changed: 59 additions & 19 deletions

File tree

src/htmx.js

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1478,19 +1478,25 @@ var htmx = (function() {
14781478
forEach(
14791479
targets,
14801480
function(target) {
1481+
let fragment
14811482
const oobElementClone = oobElement.cloneNode(true)
1482-
const fragment = getDocument().createDocumentFragment()
1483+
fragment = getDocument().createDocumentFragment()
14831484
fragment.appendChild(oobElementClone)
14841485
if (swapSpec.strip === undefined && !isInlineSwap(swapSpec.swapStyle, target)) {
14851486
swapSpec.strip = true
14861487
}
1488+
if (swapSpec.strip) {
1489+
// @ts-ignore if elt is template, content will be valid so use as inner content
1490+
fragment = oobElementClone.content || asParentNode(oobElementClone) // if this is not an inline swap, we use the content of the node, not the node itself
1491+
swapSpec.strip = undefined
1492+
}
14871493

1488-
const beforeSwapDetails = { shouldSwap: true, target, fragment }
1494+
const beforeSwapDetails = { shouldSwap: true, target, fragment, swapSpec }
14891495
if (!triggerEvent(target, 'htmx:oobBeforeSwap', beforeSwapDetails)) return
14901496

14911497
target = beforeSwapDetails.target // allow re-targeting
14921498
if (beforeSwapDetails.shouldSwap) {
1493-
swap(target, fragment, swapSpec, {
1499+
swap(target, fragment, beforeSwapDetails.swapSpec, {
14941500
contextElement: target,
14951501
afterSwapCallback: function(settleInfo) {
14961502
forEach(settleInfo.elts, function(elt) {
@@ -1846,7 +1852,7 @@ var htmx = (function() {
18461852
}
18471853

18481854
/**
1849-
* @param {DocumentFragment} fragment
1855+
* @param {DocumentFragment|ParentNode} fragment
18501856
* @param {HtmxSettleInfo} settleInfo
18511857
* @param {Node|Document} [rootNode]
18521858
*/
@@ -1870,7 +1876,7 @@ var htmx = (function() {
18701876
* Implements complete swapping pipeline, including: delay, view transitions, focus and selection preservation,
18711877
* title updates, scroll, OOB swapping, normal swapping and settling
18721878
* @param {string|Element} target
1873-
* @param {string|DocumentFragment} content
1879+
* @param {string|ParentNode} content
18741880
* @param {HtmxSwapSpecification} swapSpec
18751881
* @param {SwapOptions} [swapOptions]
18761882
* @param {HtmxSettleInfo} [oobSettleInfo]
@@ -1911,12 +1917,12 @@ var htmx = (function() {
19111917
target.textContent = content
19121918
// Otherwise, make the fragment and process it
19131919
} else {
1920+
/** @type DocumentFragment|ParentNode */
19141921
let fragment = typeof content === 'string' ? makeFragment(content) : content
19151922

19161923
// @ts-ignore if fragment is a ParentNode title will be undefined which is fine
19171924
settleInfo.title = swapOptions.title || fragment.title
19181925
if (swapOptions.historyRequest) {
1919-
// @ts-ignore fragment can be a parentNode Element
19201926
fragment = fragment.querySelector('[hx-history-elt],[data-hx-history-elt]') || fragment
19211927
}
19221928

@@ -1926,12 +1932,15 @@ var htmx = (function() {
19261932
const oobSelectValues = swapOptions.selectOOB.split(',')
19271933
for (let i = 0; i < oobSelectValues.length; i++) {
19281934
const oobSelectValue = oobSelectValues[i].split(':')
1929-
let id = oobSelectValue.shift().trim()
1930-
if (id.indexOf('#') === 0) {
1931-
id = id.substring(1)
1932-
}
1935+
const selector = oobSelectValue.shift().trim()
19331936
const oobValue = oobSelectValue.length > 0 ? oobSelectValue.join(':') : 'true'
1934-
const oobElement = fragment.querySelector('#' + CSS.escape(id))
1937+
let oobElement
1938+
if (selector.indexOf('#') !== 0) {
1939+
oobElement = fragment.querySelector('#' + CSS.escape(selector)) // check if selector is an id first
1940+
}
1941+
if (!oobElement) {
1942+
oobElement = fragment.querySelector(selector) // then support any full selector
1943+
}
19351944
if (oobElement) {
19361945
oobSwap(oobValue, oobElement, settleInfo, rootNode)
19371946
}
@@ -1947,10 +1956,6 @@ var htmx = (function() {
19471956
})
19481957
}
19491958

1950-
if (swapSpec.strip) {
1951-
// @ts-ignore if element is really a template tag we can safely use its content otherwise use first child
1952-
fragment = fragment.firstElementChild?.content || fragment.firstElementChild // if this is not an inline swap, we use the content of the node, not the node itself
1953-
}
19541959
// normal swap
19551960
if (swapOptions.select) {
19561961
const newFragment = getDocument().createDocumentFragment()
@@ -1959,6 +1964,9 @@ var htmx = (function() {
19591964
})
19601965
fragment = newFragment
19611966
}
1967+
if (swapSpec.strip) {
1968+
fragment = fragment.firstElementChild
1969+
}
19621970
handlePreservedElements(fragment)
19631971
swapWithStyle(swapSpec.swapStyle, swapOptions.contextElement, target, fragment, settleInfo)
19641972
restorePreservedElements()

test/attributes/hx-select-oob.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,26 @@ describe('hx-select-oob attribute', function() {
6666
var div2 = byId('d2')
6767
div2.innerHTML.should.equal('bar')
6868
})
69+
70+
it('basic hx-select-oob works with just an id without #', function() {
71+
this.server.respondWith('GET', '/test', "<div id='d1'>foo</div><div id='d2'>bar</div>")
72+
var div = make('<div hx-get="/test" hx-select="#d1" hx-select-oob="d2"></div>')
73+
make('<div id="d2"></div>')
74+
div.click()
75+
this.server.respond()
76+
div.innerHTML.should.equal('<div id="d1">foo</div>')
77+
var div2 = byId('d2')
78+
div2.innerHTML.should.equal('bar')
79+
})
80+
81+
it('basic hx-select-oob works with just a non id selector', function() {
82+
this.server.respondWith('GET', '/test', "<div id='d1'>foo</div><span><div id='d2'>bar</div></span>")
83+
var div = make('<div hx-get="/test" hx-select="#d1" hx-select-oob="span div"></div>')
84+
make('<div id="d2"></div>')
85+
div.click()
86+
this.server.respond()
87+
div.innerHTML.should.equal('<div id="d1">foo</div>')
88+
var div2 = byId('d2')
89+
div2.innerHTML.should.equal('bar')
90+
})
6991
})

test/attributes/hx-select.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,13 @@ describe('BOOTSTRAP - htmx AJAX Tests', function() {
3434
this.server.respond()
3535
div.innerHTML.should.equal('<div id="d1">foo</div>')
3636
})
37+
38+
it('properly handles a select with strip:true', function() {
39+
var i = 1
40+
this.server.respondWith('GET', '/test', "<div id='d1'>foo</div><div id='d2'>bar</div>")
41+
var div = make('<div hx-get="/test" hx-select="#d1" hx-swap="innerHTML strip:true"></div>')
42+
div.click()
43+
this.server.respond()
44+
div.innerHTML.should.equal('foo')
45+
})
3746
})

test/attributes/hx-swap-oob.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,7 @@ describe('hx-swap-oob attribute', function() {
420420
byId('d3').innerHTML.should.equal('Swapped14')
421421
})
422422

423-
it.only('handles using template as the encapsulating tag of an inner swap', function() {
423+
it('handles using template as the encapsulating tag of an inner swap', function() {
424424
this.server.respondWith('GET', '/test', '<template id="foo" hx-swap-oob="innerHTML"><tr><td>Swapped15</td></tr></template>')
425425
var div = make('<div hx-get="/test">click me</div>')
426426
make('<table><tbody id="foo"></tbody></table>')

www/content/events.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ You can also override the details of the history restoration request in this eve
275275
* `detail.item.content` - the content of the cache that will be swapped in
276276
* `detail.item.title` - the page title to update from the cache
277277
* `detail.path` - the path and query of the page being restored
278-
* `detial.swapSpec` - the swapSpec to be used containing the defatul swapStyle='innerHTML'
278+
* `detial.swapSpec` - the swapSpec to be used containing the default swapStyle='innerHTML'
279279

280280
### Event - `htmx:historyCacheMiss` {#htmx:historyCacheMiss}
281281

@@ -289,7 +289,7 @@ You can also modify the xhr request or other details before it makes the the req
289289
* `detail.historyElt` - the history element or body that will get replaced
290290
* `detail.xhr` - the `XMLHttpRequest` that will retrieve the remote content for restoration
291291
* `detail.path` - the path and query of the page being restored
292-
* `detial.swapSpec` - the swapSpec to be used containing the defatul swapStyle='innerHTML'
292+
* `detial.swapSpec` - the swapSpec to be used containing the default swapStyle='innerHTML'
293293

294294
### Event - `htmx:historyCacheMissLoadError` {#htmx:historyCacheMissLoadError}
295295

@@ -314,7 +314,7 @@ You can modify the details before it makes the swap to restore the history
314314
* `detail.xhr` - the `XMLHttpRequest`
315315
* `detail.path` - the path and query of the page being restored
316316
* `detail.response` - the response text that will be swapped in
317-
* `detial.swapSpec` - the swapSpec to be used containing the defatul swapStyle='innerHTML'
317+
* `detial.swapSpec` - the swapSpec to be used containing the default swapStyle='innerHTML'
318318

319319
### Event - `htmx:historyRestore` {#htmx:historyRestore}
320320

@@ -375,6 +375,7 @@ This event is triggered as part of an [out of band swap](@/docs.md#oob_swaps) an
375375
* `detail.shouldSwap` - if the content will be swapped (defaults to `true`)
376376
* `detail.target` - the target of the swap
377377
* `detail.fragment` - the response fragment
378+
* `detial.swapSpec` - the swapSpec to be used containing the swapStyle
378379

379380
### Event - `htmx:oobErrorNoTarget` {#htmx:oobErrorNoTarget}
380381

0 commit comments

Comments
 (0)