Proposal: moveOrInsertBefore()
What problem are you trying to solve?
In adding support for moveBefore() to libraries, we have to add checks that mirror the Move operation in the spec. Something like:
// Do once per range of sibling nodes that need to be moved
const sameRoot = oldParent.getRootNode({composed: true}) === newParent.getRootNode({composed: true});
// This conditional attempts to re-implement the precondition checks that
// the Move operation has in addition to those the Insert operation has in the DOM spec
const moveMethod = sameRoot && (node.nodeType === 1 || node.nodeType === 3 || node.nodeType === 8)
? container.moveBefore
: container.insertBefore;
moveMethod.call(container, node, refNode);
This is a bit tricky to get right because what preconditions you check depends on which are invariants for the particular type of move we're doing (many moves are within the same parent for instance, but some are not), and because the conditions are more complex than for methods like insertBefore().
It also is redundant, as moveBefore() is already performing these checks, just to throw. In fact, we could do:
try {
container.moveBefore(node, refNode);
} catch (e) {
container.insertBefore(node, refNode);
}
but since moveBefore already knows whether we can use an atomic move or not, why can't we have a method that just does this check and fallback for us? IOW, we know we want to move the node - there's no exception where we decide to just not move it - and we want to preserve state if possible, so do that with as little ceremony as possible:
container.moveOrInsertBefore(node, refNode);
Frameworks and libraries really don't want to have to carry around extra code for checks the DOM could do, or perform redundant checks that the DOM is already doing.
What solutions exist today?
No response
How would you solve it?
No response
Anything else?
Bulk mutation methods would help reduce repeated checks even in the DOM for ranges of nodes: https://github.com/whatwg/dom/issues/1369
The example code is quite unclear. sameroot isn't used for anything, as an example. What is the problematic case you're trying to handle?
moveOrInsertBefore certainly does smell a bit like a kitchen sink method ;)
@smaug---- sorry I was trying to simplify my actual code to make it more readable here. I'll clean it up.
The point is that in order to safely use moveBefore() we have to reimplement the checks that it does and fall back to insertBefore() if our nodes don't meet the requirements. It would be a lot easier, and likely faster, if there was a DOM API that just did this for us. I don't see the benefit in forcing this conditional logic to be wrapped around so many `moveBefore() calls.
@smaug---- the code is updated now.
This version is probably sufficient for any practical use (unless you really need the runtime is-element nodeType checks):
function insertOrMoveBefore(parent: Element, child: Element, ref: Node?) {
if (child.isConnected && parent.isConnected &&
(child.ownerDocument === parent.ownerDocument))
parent.moveBefore(child, ref);
else
parent.insertBefore(child, ref);
}
or:
function insertOrMoveBefore(parent, child, ref) {
try {
parent.moveBefore(child, ref);
} catch (e) {
parent.insertBefore(child, ref);
}
}
Some of the other premutations like moving CharacterData or moving elements in an inactive document are not that interesting if you're anyway falling back to insertBefore - those are cases where moveBefore would "succeed" but have the same effect as insertBefore.
I don't mind having this as part of the platform (and it was discussed before) but the polyfill is perhaps not that bad?
We should consider the performance constraints of JS code like that. It's been a while for me looking at stuff like this but try/catch in a hot path could impact perf. If on the implementation side we're just checking a flag, then it might be enough to provide a reasonable justification to add to the platform - but I think emperical evidence is necessary.
We should consider the performance constraints of JS code like that. It's been a while for me looking at stuff like this but
try/catchin a hot path could impact perf.
That's just one of the options. The first one was just checking a couple of flags.