MarthinL
How can I get LiveView to tell morphdom to move a whole sub-tree to a different parent in the dom?
Hi, there’s a whole long TL:DR going on about Recursive Trees in LiveView for context, but in the process of formulating a solution there is one thing that can be done to a tree that I’m struggling to figure out how it could map efficiently onto the wire (for component updates as well as via PubSub) and the dom operations it triggers client side.
As the above discussion has started doing let’s stick to using file-system directory structure as commonly understood metaphor. In that vein, the operation I’m having trouble with would be similar to
mv /dir1/dir2/dir3 /dir1/dir15/dir3
meaning to reposition the dir3 folder and all of its contents from being a subdirectory of /dir1/dir2 to become a subdirectory of /dir1/dir15.
I don’t have any morphdom experience or prior exposure, and most certainly don’t understand how LiveView uses it, but from a naive observer’s perspective there does appear to be primitives in morphdom that could achieve this.
In the best case scenario, it would automatically be taken care of if I simply update the dir3 node as a child of dir15, but have no idea if the parent relationship is visible or addressible anywhere.
I also thought of creating a new empty temporary node at the destination and make that look like the source node through updates in the hope that diffing and morphdom will notice its the same content and move it but i run into the same problem as above with added undertainty if it’s even possible to change the id of a LiveView component after it’s rendered.
This may seem trivial, and perhaps it is or could be, but in deeply recursive trees the impact on the user would be comparible to miving a directory through the GUI (especially windows) or from the command line with the mv command. The latter being completed instanteneous since it’s really just a pointer change and no uswer interface needs to update, while the former tracks progress as it traverses through the structure to collect what copies to make, shows progress as it copies each file and once done it completely refreshes the display. I’d love to avoid that approach if possible, and I think the brower’s DOM itself is quite capable of displaying it post-change very quickly, but I need help figuring out a reliable high-levlel approach to “trick” LiveView into giving morpdom the correct instruction.
Even if it means dispatching a custom message to the client which directly instructs morphdom to apply the move directly to the DOM before makeing the equivalent update to the LiveView which will then look to morphdom as already done.
Or perhaps the resulting JS on the client would just brute-force change the id for the root of what consitutes the dir3 node to a temporary value so that when we insert a new child into dir15’s LiveComponent with that same ID morphdom would pick up that content and move it into the new node. Then we might change the id back eiher through the LiveComponent or directly.
Or, it strikes me now (this is an edit), I could do a send_update from the LiveCompoent of either dir2 or directly dir3 to the component for dir15 to insert the content as a child, and hope/trust/know/why/how liveview diffing and morphdom will pick it up.
I don’t know, I’m just throwing out ideas, and would absolutely love hearing from others more familiar with the environment about how this an be acheived. It’s not the end of the world if such a change results in a big rerender and update, as I believe it shouldn’t happen all that often and if it does it might not be a bad thing if it happens slowly so the user can follow what’s happening, but it would be good to have some idea beforeharnd about if and how it could be done.
P.S. Is the “parent reference” in nested Phoenix.Component and/or Phoenix.LiveComponent instances stored explicitly or is it effectlively only kept on the call stack as these components invoke each other?
Most Liked
garrison
When you’re using LiveComponents, they are assigned (by you!) a static id which uniquely identifies that component on the page. If you take a look at the LiveComponent docs and scroll down a bit you will see the following:
Two live components with the same module and ID are treated as the same component, regardless of where they are on the page. Therefore, if you change the location […] it won’t be remounted.
What this means is that LiveView is smart enough to send down a minimal diff notifying the client that a LiveComponent has been moved to a different place in the DOM, so the client can move that entire subtree wholesale without the whole thing being sent over the wire again.
The above does not apply to normal components, which would be sent again in full.
I do want to point out that you show a lot of concern for morphdom in your post (in the title, even), but your main concern should be the diffing that happens in LiveView before the diffs are sent over the wire. The morphdom part is really just a “final step” that you shouldn’t concern yourself with too much, as it’s plenty fast unless you are passing in truly huge diffs (in which case you will always run into wire problems first anyway).
MarthinL
Now that’s what I was talking about! That really is a great article explaining almost exactly the meat of what I was wondering about which isn’t how morphdom works but what are the right buttons to push to get LiveView to do things the most optimal way and the example I used was to move a large subtree on screen without rerendering it. With all that José shared in that article, I am confident that I can navigate my way around LiveView and my data.
What was also reaffirmed for me, is how important it is to have an effective source of truth for the data you present. It’s not only about the size of the data in memory, but having tracking changes as close to the source as possible. IF the LiveComponents involved are fed the actual changes that occurred they can do what they need to do to make those show up at the client. I great example of that is to know that a node moved or changed rahter than implementing it (as I’ve seen recommendations for) sending those as a delete and insert pair. Those are not equivalent, and even if LiveView can save the say and pick up many cases where that happens, it is still better to not put that on it and rather work off the facts.
Anyway, I’m out on my feet after a mind-breaking marathon over two days trying to wrap my rutted brain around breadth-first Elixr algorithms when my thinking had defaulted to depth-first for the logest time. Unfortunately (for me) the examples I found online about that lead me completely down the wrong path - they all used a queue (borrowed from Erlang’s :queue) to achieve the breadth first behaviour, so I presumed that it was a) hard, b) unnatural to Elixir and c) going to require that I do something similar. I must have written and discarded several thousand lines of code only to get nowhere. It felt like an intractible problem for two days. Then I woke up with an alternative idea and though it still took several hours of adjustment and even more restarts, I finally wrote code that constructed a tree in a breadth-first manner. It’s of no specific use to do it that way, it was just to wrap my head around it, but in the end the whole thing was less than 20 lines of rather straight forward code, and it ran in a fraction of the time my depth-first version had run.
I’ve now also written a tree generator that keeps the structure separate (in a nested list of tuples and lists) and a map for the label records. Next I’ve going to turn that generator into my infinite virtual tree that can scroll/navigate/zoom in any direction ad infinitum. yet keep a finite portion of the structure and only the referenced nodes in memory.
As an aside, it came to my attention that maps might well be streamed. I used to operate under the assumption that if you put a map on a stream that it would effectively be one entry but that might be untrue. The map of content nodes involved in any tree operation I wish to hand over to LiveView to handle would therefor make a great stream.
MarthinL
Of course, and when I quotes the example I I mentioned that it’s am obvious mistake in that setting, but when it’s something deep down and subtle the obviousness disappear and it’s no longer so easy identify who or what is to blame for the nagging mismatch between that users want and what they have.
We’ve clearly been talking way past each other on this. There are a lot of terms for it by I usually talk about the data model or and when i\ve referred to it in the Phoenix context I’ve called it associated data because Ecto calls it associations. In pure SQL domains you’d here talk of relations and joins but it all boils down to the same thing. I’ve not gone counting but I estimate me app currently uses around 90 of these one way or another. But there is nothing denormalised about them at all. Normalisation is a definite but often abused concept from relational databases and set theory which your normalise has nothing to do with. In fact, what you’re hopefully looking at is hopefully highly accuractely normalised data which is what makes the data so complex. The essential characteristic as far as the database being a source of truth is concerned, is that is direct or one-to-one mapping between every piece of data you present to the user and its represenetation in the database. When that’s not the case anymore, you’re mangling the data which is where the world of hurt comes into play. The other Ecto term used for that are preloaded data - where preloading is a way to automatically (declaritively) construct these in-memory structures from the data. I’ve had to deal with a few ORMs in my life and from that perspective how Ecto does it is brilliant. It does an excellent job of maintaining tracking the metadata of exactly how the data in memory correlates to the data in the dataset to tje point that you can point at any loaded record and simply instruct Ecto to build a changeset for it for you and save changes to it to the database. If you had actual denormalised data, that would not be possible. But like I said, your job as designer and programmer is to ensure that the structure of the data in the database and on the screen with all the layers of abstraction in between matches the user’s problem domain exactly.
That also sets the scene for me to reiterate what I’ve said about the structure of my data since you probably missed it based on the unfamiliar terms I used. I have data that’s fundamentally recursive - meaning that the same constellations of associated records repeats, but between those recursions points there may be a great many other records who’s data adds up to fully display a node. My objective is to split the actual recursive data, in essence just the records involvrd in the parent-child relationship’s id’s, in a separate structure as the cheap and efficient version of the tree that I can afford to keep more of in memory to help me guide LiveView towards making only the changes that are required without having to keep big data in memory for it to do the comparisons itself.
Just like it’s known and understood between us that I’m not a front-end fundi, I think it’s becoming fair comment to say while your frame of reference is skewed towards the front-end you tend to solve problems there rather than at the back-end where your skill-set is less pronounced.
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #elixirconf-us
- #advent-of-code
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #hex










