htmx icon indicating copy to clipboard operation
htmx copied to clipboard

multi-swap vs select-oob

Open Grafikart opened this issue 2 years ago • 4 comments

This is not an issue with the code so to speak. If we need to replace some specific elements there is 2 solutions showcased in the documentation :

select-oob that allow the user to select specific element (that support the extra syntax with : and ,

<button ... hx-select-oob="#posts:beforeend,#more:outerHTML">

But there is also an extension multi-swap that seems to do the exact same thing. The documentation compares it to hx-swap-oob but I don't see the difference with hx-select-oob. It could be interesting to clarify the use case for both methods.

Grafikart avatar Jun 23 '23 08:06 Grafikart

multi-swap traverses the DOM tree. OOB only does top level. That seems to be stated in the multi-swap documentation.

Spectrum-Chris avatar Aug 23 '23 16:08 Spectrum-Chris

To clarify a bit, top level means that if you return a bunch of fragments that will be swapped into different places, they all need to be in the top-level of the response - thus its not necessarily a "properly formed" html document so much as a bunch of fragments appended consecutively?

Whereas with multi-swap, you could return an actual (say, cached) html page with any degree of nesting, and it will find the fragments wherever they might be and swap them into wherever was designated?

Would it stand to reason then that multi-swap, while more flexible, is also slower than oob swap? Though is the performance difference typically even perceptible?

nickchomey avatar Aug 23 '23 20:08 nickchomey

I'd love to see description of this in documentation for hx-select-oob. It'd save me from a huge headache. I just spent 2 days debugging this behavior of hx-select-oob. I've been happily oob-swapping-in the element with hx-get which was supposed to inherit some properties from already existing parent elements. All looked good in Firefox's DOM inspector, but displaying properties of swapped-in elements showed that they have no parent nodes and HTMX's inheritance mechanism couldn't find any properties to inherit.

mgoral avatar Mar 17 '24 00:03 mgoral

multi-swap traverses the DOM tree. OOB only does top level. That seems to be stated in the multi-swap documentation.

I'm confused, since I thought that was the case until a couple of weeks ago. By now I tried to just use hx-select-oob for nested swaps, and it seems to be just working.

Also I don't see a restriction to top-level oob swaps in the code. Here for example is some of the code related to hx-select-oob

// select-oob swaps
if (swapOptions.selectOOB) {
    const oobSelectValues = swapOptions.selectOOB.split(',')
    for (let i = 0; i < oobSelectValues.length; i++) {
        const oobSelectValue = oobSelectValues[i].split(':', 2)
        let id = oobSelectValue[0].trim()
        if (id.indexOf('#') === 0) {
            id = id.substring(1)
        }
        const oobValue = oobSelectValue[1] || 'true'
        const oobElement = fragment.querySelector('#' + id)
        if (oobElement) {
            oobSwap(oobValue, oobElement, settleInfo)
        }
    }
}

So just a querySelector and no restriction to parent == null or something.

There is a config somewhat related to that htmx.config.allowNestedOobSwaps, see https://htmx.org/attributes/hx-swap-oob/. Here the related code for hx-swap-oob

    /**
     * @param {DocumentFragment} fragment
     * @param {HtmxSettleInfo} settleInfo
     */
    function findAndSwapOobElements(fragment, settleInfo) {
        forEach(findAll(fragment, '[hx-swap-oob], [data-hx-swap-oob]'), function (oobElement) {
            if (htmx.config.allowNestedOobSwaps || oobElement.parentElement === null) {
                const oobValue = getAttributeValue(oobElement, 'hx-swap-oob')
                if (oobValue != null) {
                    oobSwap(oobValue, oobElement, settleInfo)
                }
            } else {
                oobElement.removeAttribute('hx-swap-oob')
                oobElement.removeAttribute('data-hx-swap-oob')
            }
        })
    }

Also here it is not just possible to have nested oob swaps, it is enabled by default (allowNestedSwaps is true by default).

Apparently this got changed in 1.0.2 (see https://github.com/bigskysoftware/htmx/blob/master/CHANGELOG.md#102---2020-12-12), and before that it was

function handleOutOfBandSwaps(fragment, settleInfo) {
    forEach(toArray(fragment.children), function (child) {
        var oobValue = getAttributeValue(child, "hx-swap-oob");
        if (oobValue != null) {
            oobSwap(oobValue, child, settleInfo);
        }
    });
}

which would explain where the "only top level hx-swap-oob" comes from.

I can't see anything right now that multi-swap now can do that hx-select-oob can't. But I the most tired right now than I've been in months, so maybe it's just me... So if anyone could chime in on this, then I would like to put something in the docs.

oliverhaas avatar Dec 21 '24 01:12 oliverhaas

We realized this 3 years later, that OOB swap could totally handle nested elements, see #2119 I could have sworn I had changed the multiswap docs along back then to remove that note, but it seems it was all just a dream!

We certainly should update the documentation to avoid confusion about what OOB can and cannot do

If you're interested, please feel free to open a documentation PR!

Telroshan avatar Jan 04 '25 10:01 Telroshan