Laravel Livewire Pagination
Laravel - 8.6.0 (Jetstream), Livewire - 2.2.7
Między innymi mam dwie proste tabele z danymi wejściowymi wyszukiwania renderowanymi za pomocą Livewire. Oba komponenty Livewire mają prawie taką samą logikę, główną różnicą jest format zapytania DB. W jednej tabeli wszystko działa idealnie (wyszukiwanie, paginacja), ale w drugiej jest problem z paginacją.
Co jest nie tak? Kiedy klikam na link do paginacji (np. Z pierwszej strony gotoPage (2)), żądanie jest wysyłane, adres URL w przeglądarce zmienia się na właściwy ciąg zapytania, odpowiedź jest odbierana, ale nie ma części HTML w odpowiedzi, nie ma zmiany w DOM a paginacja pokazuje, że pierwsza strona jest aktywna. Jeśli ręcznie odświeżę stronę, tabela pokazuje dane dla strony 2, a potem mogę nawet pobrać nowe dane dla następnej lub poprzedniej strony, ale tylko raz po początkowym odświeżeniu strony. Jeśli ponownie kliknę linki paginacji, brak danych HTML w odpowiedzi, brak zmiany DOM ...
Czego próbowałem?
- Przejrzałem dokumentację Livewire, wypróbowałem wszystkie sugerowane rozwiązania z sekcji „Rozwiązywanie problemów”.
- Sprawdzałem swój kod wiele razy, porównywałem zarówno komponenty Livewire, jak i widoki Blade, nie byłem w stanie znaleźć jakiegoś oczywistego błędu.
- Przez ostatnie 10 dni szukałem odpowiedzi na SO, forum Livewire, forum Laracast, ... znalazłem kilka dobrych sugestii, większość z nich wypróbowałem, ale żadna z nich nie rozwiązuje mojego problemu.
- Wyeliminowano możliwość, że logika dla zapytania DB powoduje pewne problemy, problem kończy się, nawet jeśli nie używam złożonego filtrowania rekordów (np. Jeśli po prostu podzielę wszystkie rekordy na strony - Model :: all () -> paginate (10)).
- Problematyczna tabela ma dodatkowe filtrowanie rekordów za pomocą przycisków radiowych, tymczasowo to usunąłem, ale problem pozostaje.
Kod działającego komponentu Livewire i widoku Blade:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class Extension extends Model
{
use HasFactory;
protected $guarded = []; public function user() { return $this->belongsTo('App\Models\User', 'directory', 'ext');
}
public static function search($query) { return empty($query) ? static::query()
: static::where('directory', 'like', '%' . $query . '%') ->orWhere('mobile', 'like', '%' . $query . '%')
->orWhere('department', 'ilike', '%' . $query . '%') ->orWhere('name1', 'ilike', '%' . $query . '%')
->orWhere('name2', 'ilike', '%' . $query . '%');
}
}
<?php
namespace App\Http\Livewire;
use Livewire\Component;
use Livewire\WithPagination;
class PhonebookTable extends Component
{
use WithPagination;
public $perPage = 10;
public $search = ''; public function render() { return view('phonebook.phonebook-table', [ 'phonebook' => \App\Models\Extension::search($this->search)
->with('user')
->orderBy('directory', 'asc')
->paginate(10)
]);
}
}
/**
* Livewire Lifecycle Hook
*/
public function updatingSearch(): void
{
$this->resetPage();
}
}
<div>
<div class="flex items-center justify-between">
{{-- Search Input --}}
<div class="relative text-gray-700 focus-within:text-gray-600 m-2">
<span class="absolute inset-y-0 left-0 flex items-center pl-2">
<button type="submit" class="p-1 focus:outline-none focus:shadow-outline">
<svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" viewBox="0 0 24 24" class="w-6 h-6">
<path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
</svg>
</button>
</span>
<input wire:model="search" type="search" name="q"
class="py-2 text-sm text-gray
bg-gray-100 rounded-md pl-10 focus:outline-none
focus:bg-gray-200 focus:text-gray-700"
placeholder="{{ __('Search...') }}"
autocomplete="off">
</div>
{{-- End Of Search Input --}}
{{-- PerPage Select Dropdown --}}
<div>
<span>{{ __('Per page:') }}</span>
<div class="inline-block relative mr-2">
<select wire:model="perPage" class="block appearance-none w-full
bg-white border border-gray-400 hover:border-gray-500 px-4 py-2
pr-8 rounded shadow leading-tight focus:outline-none
focus:shadow-outline">
<option>10</option>
<option>25</option>
<option>50</option>
</select>
<div class="pointer-events-none absolute
inset-y-0 right-0 flex items-center px-2 text-gray-700">
<svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20">
<path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757
6.586 4.343 8z" />
</svg>
</div>
</div>
</div>
{{-- End Of PerPage Select Dropdown --}}
</div>
{{-- Phonebook Table --}}
<div class="container p-2">
<table class="text-center w-full">
<thead class="bg-gray-400 flex text-white w-full item-center rounded-md">
<tr class="flex w-full py-2">
<th class="px-4 w-1/4">{{ __('Name1') }}</th>
<th class="px-4 w-1/4">{{ __('Name2') }}</th>
<th class="px-4 w-1/4">{{ __('Department') }}</th>
<th class="px-4 w-1/4">{{ __('Extension') }}</th>
<th class="px-4 w-1/4">{{ __('Mobile') }}</th>
</tr>
</thead>
<tbody class="">
@foreach ($phonebook as $directory) <tr class="flex w-full py-2 hover:bg-gray-100"> <td class="px-4 w-1/4">{{ $directory->name1??$directory->name }} </td> <td class="px-4 w-1/4">{{ $directory->name2 }}</td>
<td class="px-4 w-1/4">{{ $directory->user->department??'' }}</td> <td class="px-4 w-1/4">{{ $directory->directory }}</td>
<td class="px-4 w-1/4">{{ $directory->user->mobile??'' }}</td> </tr> @endforeach </tbody> </table> </div> {{-- End Of Phonebook Table --}} {{-- Pagination and Records Count info --}} <div class="p-2 bg-gray-200"> {{ $phonebook->links('vendor.pagination.livewire-tailwind') }}
</div>
{{-- End Of Pagination and Records Count info --}}
</div>
Kod NIEdziałającego komponentu Livewire i widoku Blade:
<?php
namespace App\Models;
use Illuminate\Support\Facades\Auth;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Livewire\WithPagination;
class Phonecall extends Model
{
use HasFactory;
use WithPagination;
protected $table = "md_phonecall"; // custom method for "live" searching from livewire component public static function search($query)
{
return empty($query) ? static::query() : static::where('dialednumber', 'like', '%' . $query . '%')
->orWhere('startdate', 'like', $query) ->orWhere('starttime', 'like', $query)
->orWhere('chargednumber', 'like', '%' . $query . '%') ->orWhere('conditioncode', 'ilike', '%' . $query . '%');
}
// 'conditioncode' accessor for casting integer value from DB to String shown in table View
public function getConditioncodeAttribute($value) { switch ($value) {
case 7;
return 'Incoming call (' . $value . ')'; case 8; return 'Internal call (' . $value . ')';
case 10;
return 'Outgoing call (' . $value . ')'; case 15; return 'Transfer call (' . $value . ')';
case 19;
return 'Outgoing call (' . $value . ')'; case 23; return 'Missed call (' . $value . ')';
case 25;
return 'Internal call (' . $value . ')'; case 28; return 'Outgoing call (' . $value . ')';
case 29;
return 'Outgoing call (' . $value . ')'; default; return 'Unknown type (' . $value . ')';
}
}
// Local Scope for filtering only calls belonging to logged User
public function scopeMycalls($query) { return $query->where('chargednumber', '=', Auth::user()->ext)->orWhere('dialednumber', '=', Auth::user()->ext);
}
/**
* Scope a query to only include phonecalls of a given type.
*
* @param \Illuminate\Database\Eloquent\Builder $query * @param mixed $type
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeOfType($query, $type)
{
if ($type === 'all') return $query->where('conditioncode', 'like', '%');
if ($type === 'incoming') return $query->where('conditioncode', '=', 7);
if ($type === 'outgoing') return $query->where('conditioncode', '=', 10)->orWhere('conditioncode', '=', 19)->orWhere('conditioncode', '=', 28);
if ($type === 'missed') return $query->where('conditioncode', '=', 23);
if ($type === 'internal') return $query->where('conditioncode', '=', 8)->orWhere('conditioncode', '=', 15)->orWhere('conditioncode', '=', 25);
}
public function scopePeriod($query, $from, $to) { return $query->whereBetween('startdate', [$from, $to]);
}
public function scopeOfExtension($query, $extension)
{
return $query->where('dialednumber', 'like', '%' . $extension . '%')
->orWhere('chargednumber', 'like', '%' . $extension . "%");
}
}
<?php
namespace App\Http\Livewire;
use Livewire\Component;
use Livewire\WithPagination;
class MyPhonecallsTable extends Component
{
use WithPagination;
public $perPage = 10;
public $search = ''; public $calltype = 'all';
public function render()
{
return view('mycalls.my-phonecalls-table', [
'mycalls' => \App\Models\Phonecall::search(
$this->search )->mycalls() ->ofType($this->calltype)
->orderBy('startdate', 'desc')->orderBy('starttime', 'desc')->paginate($this->perPage), ]); } /** * Livewire Lifecycle Hook */ public function updatingSearchInput(): void { $this->resetPage();
}
}
<div>
<div class="flex items-center justify-between">
{{-- Search Input --}}
<div class="relative text-gray-700 focus-within:text-gray-600 m-2">
<span class="absolute inset-y-0 left-0 flex items-center pl-2">
<button type="submit" class="p-1 focus:outline-none focus:shadow-outline">
<svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" viewBox="0 0 24 24" class="w-6 h-6">
<path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
</svg>
</button>
</span>
<input wire:model="search" type="search" name="q"
class="py-2 text-sm text-gray bg-gray-100 rounded-md pl-10 focus:outline-none focus:bg-gray-200 focus:text-gray-700"
placeholder="{{ __('Search...') }}" autocomplete="off">
</div>
{{-- End Of Search Input --}}
{{-- Call type selector --}}
@include('partials.calltypes')
{{-- End of Call type selector --}}
{{-- Paginator Select Dropdown --}}
<div>
<span>{{ __('Per page:') }}</span>
<div class="inline-block relative mr-2">
<select wire:model="perPage"
class="block appearance-none w-full bg-white border border-gray-400 hover:border-gray-500 px-4 py-2 pr-8 rounded shadow leading-tight focus:outline-none focus:shadow-outline">
<option>10</option>
<option>25</option>
<option>50</option>
</select>
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700">
<svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z" /></svg>
</div>
</div>
</div>
{{-- End Of Paginator Select Dropdown --}}
</div>
{{-- Phonecalls Table --}}
<div class="container p-2" id="my-calls">
<table class="text-center w-full" id="my-calls-table">
<thead class="bg-gray-400 flex text-white w-full item-center rounded-md">
<tr class="flex w-full py-2">
<th class="px-4 w-1/5">{{ __('Date') }}</th>
<th class="px-4 w-1/5">{{ __('Time') }}</th>
<th class="px-4 w-1/5">{{ __('Duration') }}</th>
<th class="px-4 w-1/5">{{ __('Calling No') }}</th>
<th class="px-4 w-1/5">{{ __('Called No') }}</th>
{{-- <th class="px-4 w-1/6">{{ __('Call Type') }}</th> --}}
</tr>
</thead>
<!-- Remove the nasty inline CSS fixed height on production and replace it with a CSS class — this is just for demonstration purposes! -->
<tbody class="">
@foreach ($mycalls as $mycall)
<tr class="flex w-full py-2 hover:bg-gray-100">
<td class="px-4 w-1/5">
{{ \Carbon\Carbon::createFromFormat('Y-m-d',$mycall->startdate)->format('d.m.Y.')}} </td> <td class="px-4 w-1/5"> {{ \Carbon\Carbon::createFromFormat('H:i:s', $mycall->starttime, 'UTC')->tz('Europe/Belgrade')->toTImeString() }}
</td>
<td class="px-4 w-1/5">
{{ gmdate('H:i:s', $mycall->duration) }}</td> <td class="px-4 w-1/5">{{ $mycall->chargednumber}}</td>
<td class="px-4 w-1/5">{{ $mycall->dialednumber}}</td> {{-- <td class="px-4 w-1/6">{{ $phonecall->conditioncode}}</td> --}}
</tr>
@endforeach
</tbody>
</table>
</div>
{{-- End Of Phonecalls Table --}}
{{-- Pagination and Records Count info --}}
<div class="p-2 bg-gray-200">
{{ $mycalls->links('vendor.pagination.livewire-tailwind') }}
</div>
{{-- End Of Pagination and Records Count info --}}
</div>
Czekając na sugestie, spróbuję jeszcze jednej czystej instalacji Laravel / Jetstream / Livewire. Z góry dziękuję, każda pomoc jest bardzo mile widziana!
Odpowiedzi
Patrząc jeszcze raz na mój kod, zauważyłem pewne „resztki” w klasach modeli. W klasie modelu nie powinno być "użyj WithPagination;" Cecha Livewire! Kiedy usunąłem statystyki „użyj” i wyczyściłem pamięć podręczną widoku, wszystko działa OK!