Liveview with Muuri grid (would phx-update="stop-ignore" make sense?)

tl;dr How to force liveview to phx-update="ignore" an element and then to phx-update="replace" its children so as not to reset client state on that one particular element?

I’m working on a project using liveview and Muuri (muuri is a js grid layout library) to create a draggable dashboard of real-time widgets that can be added and removed. The page roughly looks like this:

<div id="grid" phx-hook="Muuri">
  <%= for widget <- @widgets do %>
    <div class="grid-item">
      <!-- ... -->
      <button phx-click="remove" phx-value-widget="<%= widget.id %>">Remove</button>
      <!-- ... real time data as a component or a view ... -->
    </div>
  <% end %>
</div>

<button phx-click="add-widget">Add widget</button>

The problem I have is that I need liveview to ignore and never patch the elements which are controlled by Muuri, namely #grid and .grid-item. And at the same time I need liveview to be able to modify the contents of .grid-item freely for the real-time functionality so as not to do it in JS. I also need liveview to be able to prepend to the list of widgets when a new widget is added.

Here’re my attempts at achieving it:

  1. The first thing that I tried was phx-update="prepend" on #grid. Using this directive adding, moving and removing widgets is easy. But any other update/patch (unrelated to grid and widgets) sent to the client resets the attributes on #grid and .grid-item which breaks the grid layout. The workaround is to remove all previous items from the muuri instance and re-add them again. On my laptop this sometimes results in a 50ms blip in the layout, which is not good.

  2. The next thing that I tried was phx-update="ignore" on #grid's parent. In order to be able to use that directive and still show real-time data from inside of the grid, all widgets are turned into liveviews. Moving and removing widgets also works with little modification, but adding new widgets doesn’t seem to be possible since even though the new widget update is sent to the client, it’s never applied to the DOM and never added to the grid. I can probably use push_event to get the new widget html and init the liveview manually but I’d rather avoid that.

I wonder how would others approach the problem, and whether anyone thinks that a phx-update attribute that would offset ignore one would make sense. For example, with my first approach it would allow me to wrap #grid and .grid-item into ignore and stop-ignore blocks and still be able to add, move, and remove widgets without interferring with Muuri.

2 Likes

Hello,

What if you generate the .grid-item list in JS (if you’re not using any “heavy” framework, Alpinejs might be good enough for that, but you will still need to populate the list as a JS object, could be done in a <script></script> in the LiveView)
And have the inner part of the widgets as stateless LiveComponents (or stateful if you handle events)?

I don’t know Muuri (never heard of but it seems cool) so I don’t really know the problem you’re facing, but anyway I would like to hear how you managed to solve your problem…

Hi!

Sorry, I can’t seem to understand how that would work exactly … If I create .grid-items via JS, how would the inner part be a live component?

I’ve made the second option work with custom liveview rendering and joining from the client. But that felt like a hack, so right now I’m switching to using onBeforeElUpdated instead. I’ll post a demo project here once I’m done. :+1:

2 Likes

Would you mind posting your onBeforeElUpdated hook if you get the chance @idiot? Just started working on a Muuri/LiveView project strangely enough, would love to know what workarounds the community has come up with before I go too deep! :pray:

1 Like

@makeitrein :wave:

I’ll try and post an example on github tomorrow.

1 Like

tl’dr I’m just copying the classes and styles muuri adds and it seems to work.

The relevant bits:

Video recording of how it works

There are some “gotchas” that would require extra handling:

  • if the content of the cards (aka .muuri-item) involves something blocking the UI thread (like rendering charts), it would block muuri as well and the page would get stuck with cards stacked on top of each other for some time. So the initialisation of these elements would need to be scheduled after the first layoutEnd event.

  • show method – which is suggested in !410 to make the grid appear pretty – scales the items during in transition, which breaks the layout of some charting and mapping libraries. One more reason to postpone inner content initialisation after layoutEnd.

  • if liveview updates the sizes of some of the grid elements, muuri would need to refreshItems and layout. I send the updated sizes with push_event. I add a ResizeObserver to each new element as well.

1 Like

Amazing @idiot! Looks very nifty when I spammed the “Add card” button! Immediately forked and cloned, lol.

Appreciate the heads up about the gotchas - luckily don’t plan on having any thread-intensive Javascript, so should be able to avoid those pitfalls. Mostly images and text.

I’ll see if I can return the favor when I start getting into the weeds in October - quitting my current gig next week to pursue some the side projects kicking around in my noggin… will try to post any snippets if they prove helpful.

1 Like