Search code examples
javascriptlaravellaravel-livewireswiper.js

swiper blow up when its used with livewire


I have a javascript code to make a slider, and inside the slider, there are content changes when user select deferent variation of the product, and the problem is when I try to select an variation, the slider blows up, and the javascript code won't work.

slider code:

<div class="w-full relative mt-5">
    <div class="swiper multiple-slide-carousel swiper-container relative">
        <div class="swiper-wrapper mb-16">

            @foreach ($products as $product)
                <div class="swiper-slide">
                    <div class="relative overflow-hidden rounded-lg border border-base-300 bg-base-100 shadow-2xl">
                        <a class=" mx-3 mt-3 flex h-48 overflow-hidden rounded-xl"
                            href="{{ route('Productdetail', $product->id) }}">
                            <img class="object-cover mx-auto" src=" {{ asset('upload/photos/' . $product->image_path) }} "
                                alt="product image" />

                            @if ($product->hasVariations())
                                @if (isset($selectedVariation[$product->id]))
                                    @if ($selectedVariation[$product->id]->price_sale_desacount)
                                        <span
                                            class="absolute top-0 left-0 m-2 rounded-full bg-red-500 px-2 text-center text-sm font-medium text-white">{{ intval((($selectedVariation[$product->id]->price_sale - $selectedVariation[$product->id]->price_sale_desacount) * 100) / $selectedVariation[$product->id]->price_sale) }}%
                                            داشکان </span>
                                    @endif
                                @endif
                            @else
                                @if (isset($product->price_sale_desacount))
                                    <span
                                        class="absolute top-0 left-0 m-2 rounded-full bg-red-500 px-2 text-center text-sm font-medium text-white">{{ intval((($product->price_sale - $product->price_sale_desacount) * 100) / $product->price_sale) }}%
                                        داشکان </span>
                                @endif
                            @endif



                        </a>

                        <div class=" flex flex-col justify-between mt-4 px-5 pb-16">
                            <a href="{{ route('Productdetail', $product->id) }}">
                                <h5 class="text-lg tracking-tight">{{ $product->name }}</h5>
                            </a>
                            <div class="mt-2 mb-5 flex flex-wrap items-center justify-between">

                                @if ($product->hasVariations())
                                    @if (isset($selectedVariation[$product->id]))
                                        <p class="text-center">
                                            @if ($selectedVariation[$product->id]->price_sale_desacount)
                                                <span
                                                    class="text-xl font-bold text-slate-900">{{ number_format($selectedVariation[$product->id]->price_sale_desacount) }}<span
                                                        class="text-g text-sm"> IQD</span></span>
                                                <span class="text-sm text-slate-900 line-through">
                                                    {{ number_format($selectedVariation[$product->id]->price_sale) }}</span>
                                            @else
                                                <span class="text-xl font-bold text-slate-900">
                                                    {{ number_format($selectedVariation[$product->id]->price_sale) }}<span
                                                        class="text-g text-sm"> IQD</span></span>
                                            @endif

                                        </p>
                                    @endif

                                    <select id="variation-{{ $product->id }}"
                                        wire:model="selectedVid.{{ $product->id }}"
                                        wire:change="updatePrice({{ $product->id }})"
                                        class="select select-bordered text-center mt-2 mx-auto">
                                        @foreach ($product->variations as $variation)
                                            <option value="{{ $variation->id }}">{{ $variation->size }}</option>
                                        @endforeach
                                    </select>
                                @else
                                    <p class="text-center">
                                        @if ($product->price_sale_desacount)
                                            <span
                                                class="text-xl font-bold text-slate-900">{{ number_format($product->price_sale_desacount) }}<span
                                                    class="text-g text-sm"> IQD</span></span>
                                            <span class="text-sm text-slate-900 line-through">
                                                {{ number_format($product->price_sale) }}</span>
                                        @else
                                            <span
                                                class="text-xl font-bold text-slate-900">{{ number_format($product->price_sale) }}<span
                                                    class="text-g text-sm"> IQD</span></span>
                                        @endif
                                    </p>
                                @endif

                            </div>


                            <a href="#" wire:click='addToBasket({{ $product->id }})'
                                @click="$dispatch('imtemAddedToBasket')"
                                class="flex absolute left-2/4 bottom-5 px-16 translate-x-[-50%] items-center justify-center rounded-md bg-slate-900 py-2.5 text-center text-sm font-medium text-white hover:bg-gray-700 focus:outline-none focus:ring-4 focus:ring-blue-300">
                                <svg xmlns="http://www.w3.org/2000/svg" class="mr-2 h-6 w-6" fill="none"
                                    viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
                                    <path stroke-linecap="round" stroke-linejoin="round"
                                        d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z" />
                                </svg>
                                کڕین</a>
                        </div>

                    </div>
                </div>
            @endforeach


        </div>
        <div class="absolute flex justify-center items-center m-auto left-0 right-0 w-fit bottom-12">
            <button id="slider-button-left"
                class="swiper-button-prev group !p-2 flex justify-center items-center border border-solid border-indigo-600 !w-12 !h-12 transition-all duration-500 rounded-full  hover:bg-indigo-600 !-translate-x-16"
                data-carousel-prev>
                <svg class="h-5 w-5 text-indigo-600 group-hover:text-white" xmlns="http://www.w3.org/2000/svg"
                    width="16" height="16" viewBox="0 0 16 16" fill="none">
                    <path d="M10.0002 11.9999L6 7.99971L10.0025 3.99719" stroke="currentColor" stroke-width="1.6"
                        stroke-linecap="round" stroke-linejoin="round" />
                </svg>
            </button>
            <button id="slider-button-right"
                class="swiper-button-next group !p-2 flex justify-center items-center border border-solid border-indigo-600 !w-12 !h-12 transition-all duration-500 rounded-full hover:bg-indigo-600 !translate-x-16"
                data-carousel-next>
                <svg class="h-5 w-5 text-indigo-600 group-hover:text-white" xmlns="http://www.w3.org/2000/svg"
                    width="16" height="16" viewBox="0 0 16 16" fill="none">
                    <path d="M5.99984 4.00012L10 8.00029L5.99748 12.0028" stroke="currentColor" stroke-width="1.6"
                        stroke-linecap="round" stroke-linejoin="round" />
                </svg>
            </button>
        </div>
    </div>
</div>

javascript:

var swiper = new Swiper(".multiple-slide-carousel", {
        // loop: true,
        slidesPerView: 2,
        spaceBetween: 20,
        navigation: {
            nextEl: ".multiple-slide-carousel .swiper-button-next",
            prevEl: ".multiple-slide-carousel .swiper-button-prev",
        },
        breakpoints: {
            2560: { // 4K screens
                slidesPerView: 7,
                spaceBetween: 40
            },
            1920: { // Full HD screens
                slidesPerView: 6,
                spaceBetween: 40
            },
            1440: { // QHD screens
                slidesPerView: 5,
                spaceBetween: 30
            },
            1028: {
                slidesPerView: 4,
                spaceBetween: 30
            },
            768: { // Tablets
                slidesPerView: 3,
                spaceBetween: 20
            },
            576: { // Mobile devices
                slidesPerView: 2,
                spaceBetween: 10
            }
        }

    });

I tried dispatch an event when content changes, and write script code inside that event listener but it isn't fixed the problem


Solution

  • The problem is that when you call a backend method (for example using wire:change) the DOM is rewritten and therefore external javascript libraries lose their references.

    This is a possible solution:

    • the initialization of the slider is enclosed into a function

    • in the initialization is implemented an event handler that saves the current position of the slider in the Alpine store, also the first element to display (0 by default) is set

    • a custom event is sent from the backend which is intercepted using Alpine and which reinitializes the slider at the saved position, using nextTick() to allow Livewire to apply his settings first

    In the backend:

    public function updatePrice($id)
    {
        // do some stuff
    
        $this->dispatch('price-updated');
    }
    

    In the frontend:

    <div x-data="{swiper: null}"
         x-init="$store.mySwiperPosition = 0; swiper = initSwiper()"
         @price-updated.window="$nextTick(() => swiper = initSwiper($store.mySwiperPosition))"
    >
        <div class="swiper multiple-slide-carousel swiper-container relative">
    
            .....
    
        </div>
    
    </div>
    
    ......
    
    <script>
    
        function initSwiper(initialSlide = 0) {
    
            return new Swiper(".multiple-slide-carousel", {
    
                initialSlide: initialSlide, // ~~~> added
    
                slidesPerView: 2,
    
                .....
    
                on: {     // ~~~> added
    
                    activeIndexChange: function (thisSwiper) {
                        Alpine.store("mySwiperPosition", thisSwiper.activeIndex)
                    },
               }
            })
        }
    
    </script>