Laravel Ecommerce Tutorial: Part 6.2, Listing And Deleting Products

slimgee

Given Ncube

Posted on June 10, 2023

Laravel Ecommerce Tutorial: Part 6.2, Listing And Deleting Products

In the previous post, we covered the ability to create new products, add images with filepond.js and store the images on Cloudinary. This the part 6.2 of the on going tutorial series on building an ecommerce website in Laravel from start to deployment. If you missed previous tutorials, checkout part 1 here.

In this post, we will add the ability to list all products, in short we will implement the index action of our products resource. This is going to be a shorter post than most, let's dive in

To begin, head over to your Admin\ProductController and edit the index action and tell laravel to return the admin.products.index view with a list of recently created products paginated.

/**
 * Display a listing of the resource.
 *
 * @return Renderable
 */
public function index()
{
    $products = Product::paginate();

    return view('admin.products.index', [
        'products' => $products,
    ]);
}
Enter fullscreen mode Exit fullscreen mode

Now head over to the admin.products.index view and add the following snippet

@extends('layouts.app')

@section('title')
    Products
@endsection

@section('content')
    <section class="section">
        <div class="section-header">
            <h1>Products</h1>
            <div class="section-header-button">
                <a href="{{ route('admin.products.create') }}"
                   class="btn btn-primary">Add New</a>
            </div>
            <div class="section-header-breadcrumb">
                <div class="breadcrumb-item active"><a href="{{ route('admin.home.index') }}">Dashboard</a></div>
                <div class="breadcrumb-item"><a href="{{ route('admin.products.index') }}">Products</a></div>
                <div class="breadcrumb-item">All Posts</div>
            </div>
        </div>
        <div class="section-body">
            <h2 class="section-title">Products</h2>
            <p class="section-lead">
                You can manage all products, such as editing, deleting and more.
            </p>

            <div class="row mt-4">
                <div class="col-12">
                    <div class="card">
                        <div class="card-header">
                            <h4>Products</h4>
                            <div class="card-header-form">
                                <form>
                                    <div class="input-group">
                                        <input type="text"
                                               class="form-control"
                                               placeholder="Search">
                                        <div class="input-group-btn">
                                            <button class="btn btn-primary"><i class="fas fa-search"></i></button>
                                        </div>
                                    </div>
                                </form>
                            </div>
                        </div>

                        <div class="card-body">

                            <div class="table-responsive">
                                <table class="table table-borderless table-invoice rounded">
                                    <tr>
                                        <th>Name</th>
                                        <th>Category</th>
                                        <th>Quantity</th>
                                        <th>Created At</th>
                                        <th>Status</th>
                                    </tr>

                                    @foreach ($products as $product)
                                        <tr>
                                            <td data-controller="obliterate"
                                                data-obliterate-url-value="{{ route('admin.products.destroy', $product) }}">
                                                {{ $product->name }}
                                            </td>
                                            <td>
                                                {{ $product->category->name }}
                                            </td>
                                            <td>
                                                {{ $product->quantity ?? 0 }} left
                                            </td>
                                            <td>{{ $product->created_at->diffForHumans() }}</td>
                                            <td>
                                                @if ($product->status == 'active')
                                                    <div class="badge badge-primary">Active</div>
                                                @else
                                                    <div class="badge badge-danger">Draft</div>
                                                @endif
                                            </td>
                                            <td>
                                                <div>
                                                    <a href='{{ route('admin.products.edit', $product) }}'
                                                       class='btn btn-primary'>Edit</a>
                                                    <a data-turbo-method='delete'
                                                       href='{{ route('admin.products.destroy', $product) }}'
                                                       class='btn btn-danger'>Delete</a>
                                                </div>
                                            </td>
                                        </tr>
                                    @endforeach
                                </table>
                            </div>
                            <div class="float-right">
                                {{ $products->links('vendor.pagination.bootstrap-5') }}
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </section>
@endsection

Enter fullscreen mode Exit fullscreen mode

This snippet just displays a table with all products, action links to edit and delete, and pagination links.

Notice the search form at the top right of the table, we want to be able to type something and just get the matched results as we type in semi realtime.

To do that, we first modify the index action a bit and add a query filter

/**
 * Display a listing of the resource.
 *
 * @return Renderable
 */
public function index(Request $request)
{
    $products = QueryBuilder::for(Product::class)
        ->allowedFilters([AllowedFilter::scope('search', 'whereScout')])
        ->paginate()
        ->appends($request->query());

    return view('admin.products.index', [
        'products' => $products,
    ]);
}
Enter fullscreen mode Exit fullscreen mode

This piece of code calss the whereScout scope to filter the results, let's configure scout for the model starting by adding the trait

use Laravel\Scout\Searchable;

class Product extends Model
{
    use Searchable;
...
Enter fullscreen mode Exit fullscreen mode

Then configure the toSearchableArray method to tell scout where to search

/*
 * Get the indexable data array for the model.
 *
 * @return array
 */
public function toSearchableArray(): array
{
    return [
        'name' => $this->name,
        'description' => $this->description,
        'slug' => $this->slug,
    ];
}
Enter fullscreen mode Exit fullscreen mode

To make it work let's add the whereScout scope

/**
 * Scope a query to only include listings that are returned by scout
 *
 * @param Builder $query
 * @param string $search
 * @return Builder
 */
public function scopeWhereScout(Builder $query, string $search): Builder
{
    return $query->whereIn(
        'id',
        self::search($search)
            ->get()
            ->pluck('id'),
    );
}
Enter fullscreen mode Exit fullscreen mode

On the frontend we use Turbo and Stimulus to simulate a real time effect, we already created the Stimulus controllers needed in the previous tutorials and we will just reuse those

In the admin.products.index view replace the search input with the one below

<input type="text"
class="form-control"
placeholder="Search"
{{ stimulus_controller('filter', [
   'route' => 'admin.products.index',
   'filter' => 'search',
]) }}
{{ stimulus_action('filter', 'change', 'input') }}>    
Enter fullscreen mode Exit fullscreen mode

We are connecting the filter controller to this input which listens for a change event in the input fires a filter change event to another controller which handles page reloading.

Let's connect the reloading controller by wrapping the table inside a turbo frame. Wrap everything inside the div with class .card-body inside the turbo frame tag like this

<turbo-frame class='w-full'
 id='categories'
 target="_top"
 {{ stimulus_controller('reload') }}
 {{ stimulus_actions([
     [
         'reload' => ['filterChange', 'filter:change@document'],
     ],
     [
         'reload' => ['sortChange', 'sort:change@document'],
     ],
 ]) }}>

    <!-- Everything that was inside the .card-body goes here -->
    <!-- Everything inside this frame will be reloaded when the controller reloads -->

</turbo-frame>
Enter fullscreen mode Exit fullscreen mode

Now if you start typing in the search fields you start getting results instantly.

While were on this page let's add the ability to delete products. Edit the destory action in your controller as follows

/**
 * Remove the specified resource from storage.
 *
 * @param Product $product
 * @return RedirectResponse
 */
public function destroy(Product $product)
{
    $product->delete();

    return to_route('admin.products.index')->with(
        'success',
        'Product was successfully deleted',
    );
}
Enter fullscreen mode Exit fullscreen mode

If you click the delete button in the products index page it should delete the product. Magic, right!? Well, not quite.

Here is what the button looks like

<a data-turbo-method='delete'
   href='{{ route('admin.products.destroy', $product) }}'
   class='btn btn-danger'>
Delete
</a>
Enter fullscreen mode Exit fullscreen mode

The data-turbo-method="delete" attribute tells Turbo, which is responsible for navigating to links, to make a DELETE request.

And for this tutorial we are done. In the next tutorial we will add the ability to edit products and like always to make sure you don't miss it when it comes out subscribe to the newsletter below and get an email when it's ready.

If you any questions reach out to me on Twitter @ncubegiven_

In the meantime Happy Coding!

💖 💪 🙅 🚩
slimgee
Given Ncube

Posted on June 10, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related