2

I've got Sprig set up to filter entries by a few categories. I'm trying to re-layout Masonry JS after this update takes place.

For starters, I add Masonry to the window variable (defined as global in my JS module since I'm using node):

global.masonryLayout = new Masonry('.masonry', options)

Then, in my sprig component, I'm using a <script> tag that runs each time sprig loads entries, as per the docs:

<script>
  if (window.masonryLayout) {
    window.masonryLayout.reloadItems() 
  }
</script>

I've got an example up and running, including console outputs each time the script runs. The Masonry class is there, but it just isn't laying out.

  1. https://masonry.brycekirk.com/press
  2. Filter by Source > The New York Times

Expected behaviour: Items are loaded into a Masonry layout.

Actual behaviour: No errors, but the layout is just normal columns, not Masonry (the 2nd row of articles are on their own line below the others).

Right now I'm using .reloadItems(), which actually populates colYs and cols.

.layout() leaves colYs and cols blank.

What am I missing here??

EDIT: Working code based on MoritzLost's answer

In my JS module:

global.Masonry = Masonry
global.masonryOptions = options
global.masonryLayout = new Masonry('.masonry', options)

In my sprig component:

if (window.masonryLayout) {
    window.masonryLayout.destroy()
    window.masonryLayout = new window.Masonry('.masonry', window.masonryOptions)
}

EDIT (JULY 2023): This is much easier with htmx events. Just need to make sure to load the sprig script before your sprig component.

JS module:

global.Masonry = Masonry
global.masonryOptions = options // These are just masonry options I've defined in an object variable

window.onload = () => {
  global.masonryLayout = new Masonry('.masonry', options)
}

In my sprig component:

  htmx.on('htmx:beforeSwap', function(e) {
    window.masonryLayout.destroy()
  })
  
  htmx.on('htmx:afterSwap', function(e) {
    window.masonryLayout = new window.Masonry('.masonry', window.masonryOptions)
    document.querySelector('#load-more-oob').scrollIntoView({
      behavior: 'smooth',
      block: 'end'
    }) // This just re-aligns the window
  })

1 Answer 1

3

This is oddly similar to this question.

The problem is that your Sprig component includes the wrapper (the div with the masonry class), so reloading the Sprig component not only replaces the items, but also the wrapper itself. Masonry keeps a reference to this wrapper (the element masonry was initialized with) and uses it to find new children. Since the original wrapper was removed from the DOM, masonry can't find the new items.

You can verify that by running window.masonryLayout.getItemElements() in your component script. It will always include the original six items, regardless of the current filters.

The easiest solution would be to completely remove the Masonry instance using .destroy() and initialize a new instance with the new wrapper whenever the component is reloaded. Not sure if that can be done with your current setup, since the Sprig component won't have access to the Masonry class. You could probably integrate the reinitialization logic in your compiled JS code by watching for DOM changes using a Mutation Observer. Or just expose the Masonry class to the window object in your Webpack build step, this way you can initialize a new instance in your Sprig component script.

1
  • 1
    Thank you!! Tried using MutationObserver, but it wasn't detecting child node changes, though I think I could have gotten that sorted with some more time. For now, added the Masonry class to the window object and everything is working 👌. Added working code to my answer.
    – Bryce
    Commented Aug 13, 2021 at 20:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.