I have a content editable node that is occasionally losing the focus when other parts of the DOM are being updated.
I wrapped around the editable node using Html.Keyed which improved things but the focus loss still happens just less often. Which made me think, perhaps I need another Html.Keyed higher up. To be honest, I know roughly what Html.Keyed does, but am not completely certain when it will still allow a node to be deleted and replaced. If the parent of a keyed node is deleted and replaced, will all its keyed children also be deleted?
So now I am thinking, can I make sure that Elm never touches a node? Can I do that with Html.lazy on an Int counter, and only let Elm change the node when the counter is bumped? I am a bit unclear on what things Elm will consider as referentially equal? The same 2 Ints?
onlyWhenCountChanges : Model -> Html Msg
onlyWhenCountChanges model =
Html.lazy
(\_ -> editableDiv model)
model.count
Of course the focus loss might be some other browser bug/crazy thing and nothing to do with Elm. The above might enable me to rule out Elm virtual DOM as the cause?
In my (very limited) experience and understanding, if you want to make sure the Keyed node stays in the document, it needs to be attached to the root of the mount point (directly or through a stable chain of parents).
Keyed will reuse existing DOM elements when it thinks theyâre âthe sameâ (using the key for comparison), but if your view function states the element should not be there at all, it wonât be.
If the parent of a keyed node is deleted and replaced, will all its keyed children also be deleted?
Yes. Itâs true of all nodes. The virtual dom canât move nodes between levels in the DOM.
If any of the parent nodes of a node change tagname or a parent is added or removed Elm will delete the whole branch from the DOM and recreate it.
Html.Lazy isnât what to want, it wonât help, it doesnât prevent Elm from deleting and recreating nodes that Elm thinks are different nodes.
Html.Keyed allows you to tell the virtual dom that a node in the new virtual dom is the same as a node in the old virtual dom. This can be helpful when it might not be clear that itâs the same node.
Usually this is useful for making reordering efficient as Elm can just move nodes instead of recreating them. It can also be useful for making sure Elm doesnât mix up some div tags at the same level.
Html.Keyed is only useful if Elm can identify that all the parent nodes are also the same nodes.
Check that youâre not adding additional parent nodes to your editable node and that youâre not adding additional siblings of those parents without using Html.Keyed on all siblings.
eg. https://ellie-app.com/g85Cv3rQY2ya1
The example might not be clear. https://ellie-app.com/g85Cv3rQY2ya1
The focus is lost on the input if you type âpizzaâ because that results in a new div being added as a sibling to the parent of the input. Elm canât tell if this new div is new, or a change to the old div(that contained the input) so it just recreates both div nodes and any of their children.
Diffing trees is very difficult and computationally expensive. All virtual dom libraries work on the idea that the DOM structure wonât change much and if it does, they just give up and recreate everything. Youâll see the same problem in React(a bit less so because React warns you if youâre not using keyed nodes)
Html.lazy compares the view function passed in as well as its arguments when deciding whether nor not the view function needs to be reevaluated. In order for it to work, youâll need to pass in a named function - a function expression like (\_ -> ... ) will be a new value every time.
Is seems to work as long as the parent-nodes and the number of previous sibling-nodes keep the same. Its probably a good idea, having functions returning not a list of nodes, but a single node (which could be a div-node including a list). A changing number of child-nodes is more a problem than a deep DOM tree for this diffing algorithm. Html.keyed ¡ An Introduction to Elm
As keyed-nodes supports the diffing, I am wondering, why they are not the default ones.
I had the problem using the quill editor because quill heavily modifies the dom. What I ended up doing was a dirty hack where a custom element would clone all of its HTML and mount quill on the cloned html. I saw no other way.
class quillEditorProxy extends HTMLElement {
connectedCallback() {
this.innerHTML = this.innerHTML + this.outerHTML.replace("-proxy", "");
}
static get observedAttributes() {
return ["content_id"];
}
attributeChangedCallback(_, newVal, oldVal) {
// dirty hack to refresh content because I can't find the setters for the custom element
// and doing .content = "abc" freezes the page.
try {
if (newVal == oldVal) {
return;
}
this.querySelector("quill-editor").remove();
this.innerHTML = this.innerHTML + this.outerHTML.replace("-proxy", "");
} catch (e) {}
}
}
customElements.define("quill-editor-proxy", quillEditorProxy);
I have Keyed all the way up to the root, but still having the issue. Its also weird because the node that is content editable only loses its focus when one of its siblings is changed. So I should not really need to Key all the way up to fix it. That leads me to think that perhaps its some other random thing/browser bug that is causing my issue.
Is there a fool-proof way to tell for certain when Elm is deleting a DOM node? I guess just add some debug statements in the virtual DOM code to log to console when it happens.
So I have things that are Keyed and now I can tell for sure that the diffing algorithm is removing them. Quite hard to read that code, so far I conclude that Keying mostly prevents things being overwritten but it is not a guarantee. I am not really sure what the conditions are yet, which determine if it happens or not.
In this particular application, the sibling nodes can change order in the DOM. They are SVG nodes, and since SVG draws things in order, to change the stacking order of the drawing the nodes must be moved. That is happening whilst editing is going on, and I think that is what sometimes removes the focus.
I should be able to solve it by simply moving the editing node to a different place in the DOM, where it does not have changing siblings. Assuming that works, its an easy fix for my particular issue.
However, I get the impression from the Elm Guide that Keying should work, no matter how you change the order of things:
Perhaps that is not true, and only certain changes to the keyed nodes result in no deletions. Like adding one, or removing one etc? Donât know really, I will have to read the algorithm carefully to understand it.
Elmâs implementation of Keyed is very simplistic â it basically just looks ahead one element, for supporting the common use cases:
Removing one thing in a list.
Adding one thing in a list.
Moving one thing in a list.
It does not do a full diff to guarantee that nodes with the same key arenât re-created.
I went for a more advanced technique in https://github.com/lydell/elm-safe-virtual-dom: I first try with a fast technique (for the âI use Keyed because of performance!â use case). If that gets stuck, it falls back on a slower technique (for the âI use Keyed because of DOM node re-use!â use case), guaranteeing that nodes arenât re-created. However, thereâs still a chance one has to remove a DOM node and insert it somewhere else among its children â thatâs after all how one re-orders things.
So I think that the conditions to guarantee that a node is not deleted and recreated are:
The node is keyed, and all its parent nodes are keyed up to the root of the Elm controlled DOM.
The node (and all its parent chain up to the root) is in a stable position where it will not be moved relative to its siblings.
Probably first amongst the siblings is the simplest to maintain a stable position, if siblings are changing. Doesnât matter if the siblings are static.
If siblings are static, they also probably do not need to be keyed. So the keying up to the root is only really needed on parts of the DOM that are changing, or likely to change. Keying up to the root is a way of sanity checking that you didnât miss something though.
The explanation in the guide is wrong, but I guess what you are meant to take away from it is not an exact understanding of what the current keyed implementation does, just that its an optimization that can help when making changes to lists of elements. The implementation could change, and it is not meant as a way of preventing node deletion.
I also found that setting a browser break-point on Element delete from the DOM, is a good way to catch when something is getting deleted-and-replaced by the Elm virtual-dom code. Its âBreak OnâŚâ â ânode removalâ in Chrome and Firefox.