# 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:
app/Http/Livewire/Shop.php
resources/views/livewire/shop.blade.php
- 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
- The
namespace App\Http\Livewire; use Livewire\Component; class Shop extends Component { public function render() { return view('livewire.shop'); } }
Copied!
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 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
routes/web.php
resources/views/components/layout/nav.blade.php
result
- 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'); });
Copied!
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 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
app/Http/Livewire/Shop.php
result
- 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' ]); } }
Copied!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 thecompact()
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 themb_id
attribute of the record
Livewire/Shop.php
livewire/shop.blade.php
result
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' ]); } }
Copied!
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# Update the card
TIP
- If you can't remember the attributes of a model, you can (temporary) use:
dd($records);
ordd($records->toArray());
to dump and die the recordset, but this will stop the execution of the codedump($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
Livewire/Shop.php
result
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' ]); } }
Copied!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# Add the correct information to the card
- Update the card within the
foreach
loop with the correct information about the record
livewire/shop.blade.php
result
- Line 6 - 8: replace the
src
attribute with the correct image, and update thealt
andtitle
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
Copied!
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
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
# Hide the order button when not in stock
- 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
livewire/shop.blade.php
result
- 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>
Copied!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Pagination
- 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)
Livewire/Shop.php
livewire/shop.blade.php
result
- 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 withpaginate($this->perPage)
- Line 11: add the
use WithPagination
trail to ensure the view can use the pagination method
- Line 19: replace the
<?php namespace App\Http\Livewire; use App\Models\Record; use Livewire\Component; use Livewire\WithPagination; class Shop extends Component { use WithPagination; // public properties public $perPage = 6; public function render() { $records = Record::orderBy('artist') ->paginate($this->perPage); // dump($records->toArray()[0]); return view('livewire.shop', compact('records')) ->layout('layouts.vinylshop', [ 'description' => 'Shop', 'title' => 'Shop' ]); } }
Copied!
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
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
# Custom pagination links
- 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 thetailwind.blade.php
view)
- This is done by running the following command in the terminal:
vendor/livewire/tailwind.blade.php
result
- 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 ...
- from:
{{-- 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
Copied!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
Livewire/Shop.php
livewire/shop.blade.php
result
- Add a public property
$loading
with a default value of'Loading records...'
// public properties public $perPage = 6; public $loading = 'Please wait...';
Copied!
1
2
3
2
3
# Add a tooltip to the buttons
- We use the Tippy.js (opens new window) library to add some tooltips to the buttons in the card
- Follow the installation instructions to install the library in your project
livewire/shop.blade.php
result
- 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>
Copied!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19