# 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');
    }
}
Copied!
1
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
  • 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

# 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'
            ]);
    }
}
Copied!
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'
            ]);
    }
}
Copied!
1
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); 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'
            ]);
    }
}
Copied!
1
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
  • 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
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

# 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
  • 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

# 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)
  • 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 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
  • 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
Copied!
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 properties
public $perPage = 6;
public $loading = 'Please wait...';
Copied!
1
2
3

# Add a tooltip to the buttons

  • 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
Last Updated: 11/10/2022, 7:26:58 AM