haltu / muuri

Infinite responsive, sortable, filterable and draggable layouts
https://muuri.dev
MIT License
10.81k stars 643 forks source link

[Question] Best way to synchronize to the DOM's updated data? #542

Closed karmaral closed 1 year ago

karmaral commented 1 year ago

Hello! I'm absolutely loving this library. I've been looking for a good vanilla equivalent of react-beautiful-dnd to be able to use/implement in Svelte and I'm having a lot of fun with it. The API is very concise and well structured.

Since I'm a bit new to the library, perhaps there's something I'm missing. I've tried searching through the issues but couldn't quite find something similar, so I thought I'd ask myself:

The Problems

Clues

I've included a very basic function that alters the source data, so that the DOM gets updated to match the new data. You can see this in the small representation on the top.

So I suppose my question is, is there a way to force a re-render based on the updated data? Something like a reverse synchronize()? Or should I craft a diffing mechanism to dynamically track and move/add/remove the items as they change? That seems like a bit overkill.

I have a big and fairly complex program so I've tried to emulate the test case as best as I could. Here's the REPL

I'd really appreciate any pointers. Thank you, and thank you for making this incredible library!


Edit: I've managed to fix the height behaviour by calling the toplevel mainList.refreshItems().layout() inside the group's layoutStart handler on its first run. This seems like the most logical way and it doesn't feel as hacky as my first workarounds.

The data synchronization problem still stands, so I've edited the issue for more clarity.

gamecubate commented 1 year ago

@karmaral (I'm not sure this applies here it is, but just in case...) Beware of Svelte-side reactivity constraints; said reactivity is triggered by assignments only, i.e., methods that mutate arrays or objects will not trigger updates by themselves. See their tutorial chapter on this matter.

karmaral commented 1 year ago

@karmaral (I'm not sure this applies here it is, but just in case...) Beware of Svelte-side reactivity constraints; said reactivity is triggered by assignments only, i.e., methods that mutate arrays or objects will not trigger updates by themselves. See their tutorial chapter on this matter.

I'm confused, did you see the code? The only place I'm mutating arrays is inside the store update function, which has no connection to the reactivity since it's a scoped variable.

I've delved a bit more in the source code and so far it seems there isn't a way, because of how the library works, so I might have to focus in pursuing the most elegant way of working around them for keeping the data synced.

I don't really mind the DOM, since the 'true' state of the source data will reflect whatever re-arrangments are made via Muuri. The re-rendered DOM should match it, so it's just a matter of keeping the data fresh so it doesn't start misbehaving.

I will update for posterity when I find a solution.

ikasianiuk commented 1 year ago

hey @karmaral, did you manage to find any solution? I am currently facing the same issue here

karmaral commented 1 year ago

@ikasianiuk Unfortunately, since this is for a side project, I haven't had the time to commit to this investigation properly.

However, the idea is (I think) relatively straightforward: Just compare the source of truth with the current state of the list.getElements() array.

I've made a bare-bones implementation of the workflow in this REPL. It should be enough to get you started.

Note that it only accounts for reordering. Deletion gets automatically handled by Muuri (items missing in the DOM are removed from its list), and additions should be handled manually.

There are probably many use cases it's not covering, but as I said, I haven't had the time to fully work on this.

The changes are in the ItemList component.

niklasramo commented 1 year ago

My list data depends on an external source which should get altered by Muuri's interactions and then refresh the DOM. I need a way to re-render/recompute the children to reflect the new state.

If I understand the issue correctly you have data (an array of items) which's order changes and you want Muuri to relayout the grid items every time the data's order changes, right? This can be done with the .sort() method. The sort method sorts Muuri's internal order of items and relayouts the grid items based on the new order.

And I assume the data might have additions and/or deletions too so we need to add/remove items also based on the diff of the previous and next data sets. Additions are handled with .add() method and deletions with .remove() method. Both methods mutate Muuri's internal set of items and relayouts the grid items based on the new items.

Note that you can skip the automatic relayout for any method that does it by providing { layout: false } as options to the method. This is great for doing multiple sort/add/remove mutations in a row and then finally calling grid.layout() when you're done mutating Muuri's items data.

And lastly, unless you are already synchronizing the DOM elements manually, you might want to use .synchronize() method to synchronize the DOM elements based on Muuri's current layout data. But note that this is not necessary unless the order of elements in the DOM matter in your application.

You also need to have some sort of stable id for each item in the data sets and store it in the grid item's DOM element, e.g. as a data attribute data-id="ITEM_ID_HERE". Every Muuri item is bound to a DOM element, accessible via .getElement() method, so it's easy to query the id when needed: item.getElement().getAttribute('data-id').

To sync the data you just need to listen to the appropriate events and sync the data back to your external store. move, add and remove (and maybe sort) events are a good starting point if you want granular control.

Check out this example for reference: https://codepen.io/niklasramo/pen/YveqNN. It does sorting based on external data set and also syncs the data to the external store, but does not demonstrate additions/removals.

ikasianiuk commented 1 year ago

@karmaral @niklasramo thanks a lot for your response and examples, that helped a lot!

karmaral commented 1 year ago

@niklasramo I appreciate the thorough response. What you describe is more or less what I was doing, though I hadn't looked into the sort method and it seems to be a more elegant solution to the problem.

Since my external store will always have the right indices, it is a matter of sorting the items using a data-index attribute. I was missing the bit about declaring the sortData beforehand in the constructor.

Overall this is a much more succint way of handling it! Thank you. I will share the final implementation once I finish it.