Shop: master section REMARKS
Because this is our first Livewire component in this course, we will split the solution in 3 parts:
Part 1 : let's start with the "master page" where all the records are shown on a card and add some pagination Part 2 : when the user clicks on a record, a modal pops up with some extra details (the tracks) of the record Part 3 : finally, we'll add some filtering (by name, by price and by genre) and sorting How to actually order a record in the shop is a bit advanced and will be explained in a later chapter Preparation Create the Shop component Make a new Livewire component with the terminal command php artisan make:livewire Shop
This command creates two files:
app/Http/Livewire/Shop.php (the component class contains the logic that was previously in the controller) resources/views/livewire/shop.blade.php (the view) Check the following code to add the component to the resources/views/shop.blade.php view: This is a basic skeleton of a Livewire component with only the render()
method
The render()
method is one of the so-called Lifecycle Hooks (opens new window)
that will be executed every time the component is rendered for the first time AND when something in the component is updated (e.g. a public property has changed) For now, it only returns the resources/views/livewire/shop.blade.php view namespace App\ Http\ Livewire ;
use Livewire\ Component ;
class Shop extends Component
{
public function render ( )
{
return view ( 'livewire.shop' ) ;
}
}
1 2 3 4 5 6 7 8 9 10 11
Replace the code in this file with a basic card that will be used as a template for all the records < div>
{ { -- show preloader while fetching data in the background -- } }
{ { -- filter section: artist or title, genre, max price and records per page -- } }
{ { -- master section: cards with paginationlinks -- } }
< div class = "grid grid-cols-1 lg:grid-cols-2 2xl:grid-cols-3 gap-8 mt-8" >
< div class = "flex bg-white border border-gray-300 shadow-md rounded-lg overflow-hidden" >
< img class = "w-52 h-52 border-r border-gray-300 object-cover"
src= "{{ asset('storage/covers/no-cover.png') }}"
alt= ""
title= "" >
< div class = "flex-1 flex flex-col" >
< div class = "flex-1 p-4" >
< p class = "text-lg font-medium" > Artist< / p>
< p class = "italic pb-2" > Title< / p>
< p class = "italic font-thin text-right pt-2 mb-2" > Genre< / p>
< / div>
< div class = "flex justify-between border-t border-gray-300 bg-gray-100 px-4 py-2" >
< div> Price< / div>
< div class = "flex space-x-4" >
< div class = "w-6 cursor-pointer hover:text-red-900" >
< x- phosphor- shopping- bag- light/ >
< / div>
< div class = "w-6 cursor-pointer hover:text-red-900" >
< x- phosphor- music- notes- light/ >
< / div>
< / div>
< / div>
< / div>
< / div>
< / div>
{ { -- Detail section -- } }
< / div>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
Add a new route Add a get-route to the routes/web.php file Update the navigation menu in resources/views/components/layout/nav.blade.php Add a get-route to the routes/web.php file
The shop route, with the name shop will be handled by the Shop::class component Don't forget to import the Shop component class at the top of the file (use App\Http\Livewire\Shop;
) Route: : view ( '/' , 'home' ) - > name ( 'home' ) ;
Route: : get ( 'shop' , Shop: : class ) - > name ( 'shop' ) ;
Route: : view ( 'contact' , 'contact' ) - > name ( 'contact' ) ;
Route: : view ( 'playground' , 'playground' ) - > name ( 'playground' ) ;
Route: : prefix ( 'admin' ) - > name ( 'admin.' ) - > group ( function ( ) {
Route: : redirect ( '/' , '/admin/records' ) ;
Route: : get ( 'records' , [ RecordController: : class , 'index' ] ) - > name ( 'records.index' ) ;
} ) ;
1 2 3 4 5 6 7 8 9 10
Line 1 : update route('home')
to route('shop')
< x- jet- nav- link href= "{{ route('shop') }}" : active= "request()->routeIs('shop')" >
Shop
< / x- jet- nav- link>
1 2 3
The page shows a error message, because the view is rendered by default with the layouts/app.blade.php file Reference the vinylshop layout template As for now, we get an error when we navigate to the shop page By default, Livewire will render the component into the $slot
of a blade layout component located at: resources/views/layouts/app.blade.php To fix this error, we must add a reference to the appropriate template (layouts/vinylshop.blade.php ) to the view Line 10 : reference the layouts/vinylshop.blade.php template Line 11 - 12 : update the named slots in the template with the appropriate content namespace App\ Http\ Livewire ;
use Livewire\ Component ;
class Shop extends Component
{
public function render ( )
{
return view ( 'livewire.shop' )
- > layout ( 'layouts.vinylshop' , [
'description' = > 'Shop' ,
'title' = > 'Shop'
] ) ;
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Get all records It's time to get all the records and render a card for each one of them Getting the records is achieved INSIDE the render()
method in the component, and you pass them, with the compact()
methode, to the view Loop through the records in the view and display them all IMPORTANT NOTICE ABOUT LIVEWIRE LOOPS
ALWAYS use the wire:key
directive inside a loop to make sure that Livewire can keep track of the records
This is necessary because the records are displayed in a loop If you don't use the wire:key
directive, Livewire will not be able to keep track of the records and will not be able to update them The wire:key
directive is a unique identifier for each record In this case, we could use the id
or the mb_id
attribute of the record class Shop extends Component
{
public function render ( )
{
$records = Record: : orderBy ( 'artist' )
- > get ( ) ;
return view ( 'livewire.shop' , compact ( 'records' ) )
- > layout ( 'layouts.vinylshop' , [
'description' = > 'Shop' ,
'title' = > 'Shop'
] ) ;
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13
Line 3 - 9 : loop over the records and display them Line 5 : add a unique identifier to each record (record-1
, record-2
, etc.) { { -- master section: cards with paginationlinks -- } }
< div class = "grid grid-cols-1 lg:grid-cols-2 2xl:grid-cols-3 gap-8 mt-8" >
@foreach ( $records as $record )
< div
wire: key= "record-{{ $record - > id }}"
class = "flex bg-white border border-gray-300 shadow-md rounded-lg overflow-hidden" >
. . .
< / div>
@endforeach
< / div>
1 2 3 4 5 6 7 8 9 10
Update the card TIP
If you can't remember the attributes of a model, you can (temporary) use:
dd($records);
or dd($records->toArray());
to dump and die the recordset, but this will stop the execution of the code dump($records->toArray());
to just dump the recordset as an array to the screen, without stopping the execution of the code or use dump($records->toArray()[0]);
to dump only the first element of the recordset class Shop extends Component
{
public function render ( )
{
$records = Record: : orderBy ( 'artist' )
- > get ( ) ;
dump ( $records - > toArray ( ) [ 0 ] ) ;
return view ( 'livewire.shop' , compact ( 'records' ) )
- > layout ( 'layouts.vinylshop' , [
'description' = > 'Shop' ,
'title' = > 'Shop'
] ) ;
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14
Update the card within the foreach
loop with the correct information about the record Line 6 - 8 : replace the src
attribute with the correct image, and update the alt
and title
attributes Line 11 : replace the Artist
text with $record->artist
Line 12 : replace the Title
text with $record->title
Line 13 : replace the Genre
text with $record->genre_name
Line 16 : replace the Price
text with $record->price_euro
@foreach ( $records as $record )
< div
wire: key= "record-{{ $record - > id }}"
class = "flex bg-white border border-gray-300 shadow-md rounded-lg overflow-hidden" >
< img class = "w-52 h-52 border-r border-gray-300 object-cover"
src= "{{ $record - > cover [ 'url' ] }}"
alt= "{{ $record - > title }}"
title= "{{ $record - > title }}" >
< div class = "flex-1 flex flex-col" >
< div class = "flex-1 p-4" >
< p class = "text-lg font-medium" > { { $record - > artist } } < / p>
< p class = "italic pb-2" > { { $record - > title } } < / p>
< p class = "italic font-thin text-right pt-2 mb-2" > { { $record - > genre_name } } < / p>
< / div>
< div class = "flex justify-between border-t border-gray-300 bg-gray-100 px-4 py-2" >
< div> { { $record - > price_euro } } < / div>
< div class = "flex space-x-4" >
< div class = "w-6 cursor-pointer hover:text-red-900" >
< x- phosphor- shopping- bag- light/ >
< / div>
< div class = "w-6 cursor-pointer hover:text-red-900" >
< x- phosphor- music- notes- light/ >
< / div>
< / div>
< / div>
< / div>
< / div>
@endforeach
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
Showing the order button is only relevant when the record is in stock Else we should hide it and replace it with a message SOLD OUT Line 4 : check if the record is in stock or not:
in stock : show the order button not in stock : hide the order button and show the message SOLD OUT < div class = "flex justify-between border-t border-gray-300 bg-gray-100 px-4 py-2" >
< div> { { $record - > price_euro } } < / div>
< div class = "flex space-x-4" >
@if ( $record - > stock > 0 )
< div class = "w-6 cursor-pointer hover:text-red-900" >
< x- phosphor- shopping- bag- light/ >
< / div>
@else
< p class = "font-extrabold text-red-700" > SOLD OUT < / p>
@endif
< div class = "w-6 cursor-pointer hover:text-red-900" >
< x- phosphor- music- notes- light/ >
< / div>
< / div>
< / div>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Showing all the records on one page is not a good idea, so we will add pagination (opens new window) to the page We add a public property $perPage
to the component to keep track of the number of records per page
(We'll change this value later in the view to make it more dynamic) Line 14 : add the public property $perPage
with a default value of e.g. 6
to the component Pagination:
Line 19 : replace the get()
method with paginate($this->perPage)
Line 11 : add the use WithPagination
trail to ensure the view can use the pagination method <?php
namespace App\ Http\ Livewire ;
use App\ Models\ Record ;
use Livewire\ Component ;
use Livewire\ WithPagination ;
class Shop extends Component
{
use WithPagination ;
public $perPage = 6 ;
public function render ( )
{
$records = Record: : orderBy ( 'artist' )
- > paginate ( $this - > perPage ) ;
return view ( 'livewire.shop' , compact ( 'records' ) )
- > layout ( 'layouts.vinylshop' , [
'description' = > 'Shop' ,
'title' = > 'Shop'
] ) ;
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
Line 2 and 12 : add the pagination links $records->links()
just before de grid and (optional) after the grid { { -- master section: cards with paginationlinks -- } }
< div class = "my-4" > { { $records - > links ( ) } } < / div>
< div class = "grid grid-cols-1 lg:grid-cols-2 2xl:grid-cols-3 gap-8 mt-8" >
@foreach ( $records as $record )
< div
wire: key= "record-{{ $record - > id }}"
class = "flex bg-white border border-gray-300 shadow-md rounded-lg overflow-hidden" >
. . .
< / div>
@endforeach
< / div>
< div class = "my-4" > { { $records - > links ( ) } } < / div>
1 2 3 4 5 6 7 8 9 10 11 12
The pagination links are now visible at the top and bottom of the page and the records are split over multiple pages Click, for example, on page 4:
The query parameter ?page=4
is added to the URL Also look at the tab of the browser window: the page will not be reloaded but the content will be updated!
The default pagination links are not very nice, so we will change the default layout and highlight the current page with a darker background color Before we can do this we need to pull Livewires pagination component out of the framework and into our project
This is done by running the following command in the terminal: php artisan livewire:publish --pagination
This will copy the 4 pagination views to the resources/views/vendor/livewire
folder
(You can safely delete three of them because we only need the tailwind.blade.php
view) Open the resources/views/vendor/livewire/tailwind.blade.php view, find the classes for the current page and change two classes (line 81):
from: ... text-gray-500 bg-white ...
to: ... text-white bg-gray-800 ...
{ { -- Array Of Links -- } }
@if ( is_array ( $element ) )
@foreach ( $element as $page = > $url )
< span wire: key= "..." >
@if ( $page == $paginator - > currentPage ( ) )
< span aria- current= "page" >
< span class = "relative inline-flex ... text-white bg-gray-800 ..." > { { $page } } < / span>
< / span>
@else
< button . . . >
{ { $page } }
< / button>
@endif
< / span>
@endforeach
@endif
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
Add a preloader Switching between pages is very fast (every click is a round-trip to the server), but it is still nice to show a preloader while the page is updating in the background Add a public property $loading
with a default value of 'Loading records...'
public $perPage = 6 ;
public $loading = 'Please wait...' ;
1 2 3
Line 4 - 6 : we will use our <x-tmk.preloader>
component for the spinner Line 3 : with the attribute wire:loading
the div
tag that contains the preloader is:
hidden by default showing the$loading
message when the component is regenerating the data that will be displayed in the view { { -- show preloader while fetching data in the background -- } }
< div class = "fixed top-8 left-1/2 -translate-x-1/2 z-50 animate-pulse"
wire: loading>
< x- tmk. preloader class = "bg-lime-700/60 text-white border border-lime-700 shadow-2xl" >
{ { $loading } }
< / x- tmk. preloader>
< / div>
1 2 3 4 5 6 7
Line 8 and 16 : add the data-tippy-content
attribute to the two buttons in the card Line 7 and 15 : add class="outline-0"
to remove the outline around the buttons when they are clicked < div class = "flex justify-between border-t border-gray-300 bg-gray-100 px-4 py-2" >
< div> { { $record - > price_euro } } < / div>
< div class = "flex space-x-4" >
@if ( $record - > stock > 0 )
< div class = "w-6 cursor-pointer hover:text-red-900" >
< x- phosphor- shopping- bag- light
class = "outline-0"
data- tippy- content= "Add to basket<br><span class='text-red-300'>NOT IMPLEMENTED YET</span>" / >
< / div>
@else
< p class = "font-extrabold text-red-700" > SOLD OUT < / p>
@endif
< div class = "w-6 cursor-pointer hover:text-red-900" >
< x- phosphor- music- notes- light
class = "outline-0"
data- tippy- content= "Show tracks" / >
< / div>
< / div>
< / div>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Last Updated: 11/10/2022, 7:26:58 AM