livewire problem with js plugin

Hello!

I have a Livewire 3 project where I use simple lightbox js plugin <script src="fslightbox.js"> </script> added before end of the <body> tag. I also have simple filtering on this page and everything works fine when the page is first loaded and after first component update, but stops working after that.

Does anyone maybe have some suggestions how can I solve this?

Any help would be appreciated.

Many thanks.

rafal428 Member
rafal428
0
19
95
alex Member
alex
Moderator

You may need to exclude Livewire from re-rendering the component. Not tested, but may help:

<div wire:ignore>
   // Your plugin stuff here
</div>
rafal428 Member
rafal428

many thanks for quick reply Alex,

I've tried that but no luck. Code is fairly simple so what am i doing wrong here?

@foreach ($items as $item)

<div wire:key="{{$item->id}}">

    <div wire:ignore>

          <a  data-fslightbox="{{ $item->id }}"  href="#">

                     {{ $item->name }}        
      
          </a>

    </div>

</div>

@endforeach

attribute "data-fslightbox" is the only thing realted to the plugin...

alex Member
alex
Moderator

Let me pull together a local example for you and test it. Will let you know shortly!

alex Member
alex
Moderator

Can I just ask what this app does so I can re-create a similar example?

Does it list through items, and clicking on each item opens it's own lightbox showing images?

rafal428 Member
rafal428

Yes - lts a list of imges clicking on each opens its own lightsbox.

But there is also have a simple function that filters $items based on 'type':


//    App\Livewire\Items.php

public $items;

public function mount(){

    $this->items = Item::all();

}

public function filterByType($type){

     $this->items = Item::where('type', $type)->get();

}

public function render() {

     return view('livewire.items.index', [

            'items' => $this->items,

     ]}

As i mentioned before It works fine when the page is first loaded and then when I start to filter the results based on type it works after first component update and then lightbox doesnt work anymore.

My previous view code isnt probably clear enough so this is slightly updated version:


@foreach ($items as $item)

<div wire:key="{{$item->id}}">

    <div wire:ignore>

          <a  data-fslightbox="{{ $item->id }}"  href="{{ Storage::disk('items')->url($item->photo)}}">

                   <img src = "{{ Storage::disk('items')->url($item->photo)}}">
      
          </a>

    </div>

</div>

@endforeach

Hope it makes sense.

alex Member
alex
Moderator
Solution

I've put together a full working example that'll hopefully help clear this up. It may work slightly differently to how you're doing things, but the general idea:

  • There's an Item model that contains many photos
  • It shows items within the component and generates a link for each lightbox gallery
  • In my example, the lightbox images are generated randomly using the $index, so ignore that

Here are all the relevant files:

app.blade.php

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta name="csrf-token" content="{{ csrf_token() }}">

        <title>{{ config('app.name', 'Laravel') }}</title>

        <!-- Fonts -->
        <link rel="preconnect" href="https://fonts.bunny.net">
        <link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />

        <!-- Scripts -->
        @vite(['resources/css/app.css', 'resources/js/app.js'])
    </head>
    <body class="font-sans antialiased">
        <div class="min-h-screen bg-gray-100">
            <livewire:layout.navigation />

            <!-- Page Heading -->
            @if (isset($header))
                <header class="bg-white shadow">
                    <div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
                        {{ $header }}
                    </div>
                </header>
            @endif

            <!-- Page Content -->
            <main>
                {{ $slot }}
            </main>
        </div>

        <script src="https://cdnjs.cloudflare.com/ajax/libs/fslightbox/3.0.9/index.js"></script>
    </body>
</html>

lightbox.blade.php (the Livewire component template)

<div>
    @foreach($items as $item)
        <div class="flex flex-col gap-2">
            {{--This is the item's preview image--}}
            <a data-fslightbox="gallery" href="https://picsum.photos/1000?{{ $item->id }}">
                <img src="https://picsum.photos/100?{{ $item->id }}" alt="Random image">
            </a>

            {{--These are the images associated with the item for the lightbox--}}
            @foreach (range(1,3) as $index => $image)
                <div wire:key="{{ $index }}">
                    <a class="hidden" data-fslightbox="gallery" href="https://picsum.photos/1000?{{ $index }}">
                        Image #{{ $index }}
                    </a>
                </div>
            @endforeach
        </div>
    @endforeach

    <button wire:click="update">Update order</button>
</div>

Livewire.php (the Livewire component)

<?php

namespace App\Livewire;

use App\Models\Item;
use Illuminate\Support\Collection;
use Livewire\Attributes\Layout;
use Livewire\Component;

class Lightbox extends Component
{
    public Collection $items;

    public function mount()
    {
        $this->update();
    }

    public function update()
    {
        $this->items = Item::inRandomOrder()->get();
    }

    #[Layout('layouts.app')]
    public function render()
    {
        return view('livewire.lightbox', [
            'items' => $this->items
        ]);
    }
}

The update method just re-renders the component with a new order for the $items, thus completely re-rendering the lightbox.

This is working fine with this example I've pulled together, even after randomly ordering (in your case filtering) the items.

If you're still stuck, here to help!

rafal428 Member
rafal428

many thanks Alex,

your code works like a charm, but when I tried quickly adapt mine then it still stops working (although this time after 3rd, 4th update - weird). I willl completely rewrite my component using your code as a tempolate and let you know.

many thanks for your time

alex Member
alex
Moderator

Happy to help! Let me know how it goes and pop back here. We’ll sort this!

rafal428 Member
rafal428

Checked again. Your code works every time but when I change update() function to include "where" clause i.e


public function update(){ 

         $type = rand(1, 5);

         $this->items = Item::where('type',  $type)->get();

}

it stops working after first component update.

alex Member
alex
Moderator

That's strange. By stops working, do you mean the lightbox just won't open? Any errors in the console?

rafal428 Member
rafal428

morning Alex,

by "stops" I mean after I click a photo - browser redirects me to this photo instead of opening a lightbox,

No console errors.

I also noticed that when I hardcode type


$this->items = Item::where('type',  1)->get();

it works fine every time compnent is updated.

weird.

alex Member
alex
Moderator

That's strange, I've adjusted my code to match and I can't re-create.

Could you paste in your full Livewire component and template so I can copy over to my project locally?

rafal428 Member
rafal428

tested with fresh laravel install - same problem however sometimes component can refresh even 6-8 times before problem occurs

plugin script is included before end of the <body> tag.

lightbox.blade.php:

<div class="max-w-7xl mx-auto px-8 mt-8 flex space-x-12">

    <div >
        <h1> FILTER</h1>
    <div class="flex flex-col gap-2 mt-4">
            <button wire:click="filterByType(1)">
                type 1
            </button>    
            <button wire:click="filterByType(2)">
                type 2
            </button> 
            <button wire:click="filterByType(3)">
                type 3
            </button> 
            <button wire:click="filterByType(4)">
                type 4
            </button> 
            <button wire:click="filterByType(5)">
                type 5
            </button> 
    </div>
    
    </div>  

    <div>
        @foreach($items as $item)

        <div class="flex flex-col gap-2">
            {{--This is the items preview image--}}
            <a data-fslightbox="gallery" href="https://picsum.photos/1000?{{ $item->id }}">
                <img src="https://picsum.photos/100?{{ $item->id }}" alt="Random image">
            </a>

            {{--These are the images associated with the item for the lightbox--}}
            @foreach (range(1,3) as $index => $image)
                <div wire:key="{{ $index }}">
                    <a class="hidden" data-fslightbox="gallery" href="https://picsum.photos/1000?{{ $index }}">
                        Image #{{ $index }}
                    </a>
                </div>
            @endforeach

        </div>

        @endforeach
    </div>
</div>

Lightbox.php:



namespace App\Livewire;

use Livewire\Component;
use Livewire\Attributes\Layout;
use Illuminate\Support\Collection;
use App\Models\Item;

class Lightbox extends Component
{
    public Collection $items;

    public function mount()
    {
        $this->items = Item::all();
    }

    public function filterByType($type)
    {

        $this->items = Item::where('type', $type)->get();
    }

    #[Layout('layouts.app')]
    public function render()
    {
        return view(
            'livewire.lightbox',
            [
                'items' => $this->items
            ]
        );
    }
}



alex Member
alex
Moderator

Just swapped over to use exactly these and not seeing an issue even after filtering multiple times.

With the fslightbox script you're including, is this from local or from a CDN?

rafal428 Member
rafal428

tried both.

Thank you for your efforts Alex. Dont waste more of your time on this. You were more than helpful.

I've just tried another lightbox library - Photoswipe and I have exactly the same issue. Tried on two diffrent PC's, three different browsers.

Really strange. Just one of those things I guess....

Anyway - thanks again.

One last question :

Is there a reason why you are not including wire:key in your @foreach($items as $item) loop?

alex Member
alex
Moderator

Happy to help! I'll continue to play around with it, will be super helpful for future knowledge what the issue was.

Let me know if you solve it in the meantime.

Is there a reason why you are not including wire:key in your @foreach($items as $item) loop?

Just forgot :)

rafal428 Member
rafal428

yep - I'll be back when I fix it.

rafal428 Member
rafal428

So,

I've noticed that it works fine when the number of filtered results is the same, thats why Alex's code always works - it returns the same number of results , just in random order. And thats why my code works fine for a random number of updates (works as long as number of filtered results is the same as previous).

So it has something to do with number of gallery insatnces on the page.

In documentation i found refreshFsLightbox() function that incorporates changes to <a> elements but it didnt help in my case.

So i tried and dynamically load the fslightbox script on every component update (via dispatch and window.addEventListener() ).

I dont belive this is the best solution but it works in my case.

hope this helps.

alex Member
alex
Moderator

Well, I'm glad you found one solution! The tricky thing with scripts pulled in like this and not used as an Alpine directive/plugin, they can be temperamental.

As always, let me know if you need anything else :)