colbyalbo's avatar

Alpine Modal components in a Livewire Loop

Hello, has anyone else had issues with getting Alpine modals to work within a livewire data table?

They work at first, and after a few searches, sorts and back and forth with the pagination, the modal lose the events.

I've tried adding the wire:key in a few places to no avail, any help is appreciated, thanks!

index.blade.php

<x-admin>
    <div class="flex justify-between items-center mb-6">
        <h2>
            Purchases
        </h2>
    </div>
    <div class="max-w-full">
        <livewire:purchase.table />
    </div>
</x-admin>

livewire stuff

purchase/ table.blade.php

<div>
    <div class="w-1/3 mb-6 -mt-4">
        <label for="search" class="form-label">
            Search
            <span class="text-xs">
                (search by: name or course)
            </span>
        </label>
        <div class="relative">
            <input
                wire:model.debounce.200ms="search"
                id="search"
                placeholder="Search"
                type="text">
        </div>
    </div>
    <div>
        <span class="text-gray-600">
            (Sort by: name or course)
        </span>
    </div>
    <table class="table">
        <thead>
            <tr>
                <th class="table-header text-left">
                    <div class="flex items-center">
                        <button
                            wire:click.prevent="sortBy('name')">
                            Customer
                        </button>
                        <x-sort-icon
                            field="name"
                            :sortField="$sortField"
                            :sortAsc="$sortAsc" />
                    </div>
                </th>
                <th class="table-header text-left">
                    <div class="flex items-center">
                        <button
                            wire:click.prevent="sortBy('product_names')">
                            Course
                        </button>
                        <x-sort-icon
                            field="product_names"
                            :sortField="$sortField"
                            :sortAsc="$sortAsc" />
                    </div>
                </th>
         
                <th class="table-header">
                    Receipt
                </th>
            </tr>
        </thead>
        <tbody class="table-body">
            @forelse ($purchases as $purchase)
                <tr class="table-row">
                    <td class="table-cell text-left">
                        {{$purchase->user->name}}
                    </td>
                    <td class="table-cell text-left">
                        {{implode(', ', unserialize($purchase->product_names))}}
                    </td>
                    
                    <td class="table-cell">

				//ALPINE POP UP MODAL
                            <div wire:key="{{$purchase->id}}">
                                <x-purchase.receipt :purchase="$purchase" />
                            </div>

                     </td>
                </tr>
            @empty
                <tr class="table-row">
                    <td colspan="4" class="table-cell">No Courses Entered.</td>
                </tr>
            @endforelse
        </tbody>
    </table>
    <div class="mt-8">
        {{ $purchases->links('livewire.pagination.tailwind') }}
    </div>
</div>

<?php

namespace App\Http\Livewire\Purchase;

use App\Purchase;
use Livewire\Component;
use Livewire\WithPagination;

class Table extends Component
{
    use WithPagination;

    public $search;
    public $sortField = '';
    public $sortAsc = true;
    public $sortFieldUser = '';
    public $sortAscUser = true;
    protected $updatesQueryString = ['search', 'sortAsc', 'sortField', 'sortAscUser', 'sortFieldUser'];

    public function sortBy($field)
    {
        if ($this->sortField === $field) {
            $this->sortAsc = ! $this->sortAsc;
        } else {
            $this->sortAsc = true;
        }
        $this->sortField = $field;
    }

    public function sortByUser($field)
    {
        if ($this->sortFieldUser === $field) {
            $this->sortAscUser = ! $this->sortAscUser;
        } else {
            $this->sortAscUser = true;
        }
        $this->sortFieldUser = $field;
    }

    public function updatingSearch()
    {
        $this->resetPage();
    }

    public function render()
    {
        $purchases = Purchase::join('users', 'users.id', '=', 'purchases.user_id')
        ->where(function($query){
            $query->where('product_names', 'like', '%'.$this->search.'%');
            $query->orWhereHas('user', function($q){
                $q->where('name', 'like', '%'.$this->search.'%');
            });
        })->when($this->sortField, function ($query) {
            $query->orderBy($this->sortField, $this->sortAsc ? 'asc' : 'desc');
        });

        return view('livewire.purchase.table', [
            'purchases' => $purchases->select('purchases.*')->paginate(6),
        ]);
    }
}

Alpine pop up modal (css removed for clarity)

<div
wire:key="{{$purchase->id}}"
x-data="{ open: false,  }"
x-init="
$watch('open', value => {
    const body = document.body;
    if(!open) {
       body.classList.remove('h-screen');
       return body.classList.remove('overflow-hidden');
    } else {
        body.classList.add('h-screen');
        return body.classList.add('overflow-hidden');
    }
});">

    <div x-cloak x-ref="modal" x-show.transition.opacity="open" >
        <div x-on:mousedown.away="open=false" x-on:keydown.window.escape="open = false" >

            <div>
                <h2 class="text-xl leading-tight text-gray-700">
                    Purchase Receipt
                </h2>

                <button class="text-gray-400 hover:text-gray-600" x-on:click="open=false">
                       close
                </button>
            </div>
            <div class="py-8 px-5">
                <h2>Courses Purchased:</h2>
                <h2><strong>{{implode(', ', unserialize($purchase->product_names))}}</strong></h2>
                <h3>Purchased by: {{$purchase->user->name}}</h3>
               
                <h3>Date Purchased: {{formatDate($purchase->purchase_date)}}</h3>
                <h3>Amount: {{dollars($purchase->amount)}}</h3>
                <h3>Card Last Four: ...{{$purchase->last_four}}</h3>
            </div>
        </div>
    </div>

    <button
        x-on:click="open=true">
        View
    </button>

</div>
0 likes
1 reply
colbyalbo's avatar
colbyalbo
OP
Best Answer
Level 10

Just to cap this off, i ended up just taking Alpine out of the equation, and using vanilla JS for the modals, and serializing the ID on the main DIV of each pop up with the record id.

Please or to participate in this conversation.