Laravel Ecommerce Tutorial: Part 6.2, Listing And Deleting Products
Tags: laravel, php, webdev, tutorial
Published: 2023-06-10 17:06:11

In the [previous post](https://flixtechs.co.zw/posts/laravel-ecommerce-tutorial-part-61-creating-products "Laravel Ecommerce Tutorial: Part 6.1 Creating Products"), we covered the ability to create new products, add images with [filepond.js](https://pqina.nl/filepond "Filepond") 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](https://flixtechs.co.zw/posts/laravel-ecommerce-tutorial-part-1-introduction).

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.

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

    return view('admin.products.index', [
        'products' => $products,
    ]);
}
```

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

```html
@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

```

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

```php
/**
 * 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,
    ]);
}
```

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

```php
use Laravel\Scout\Searchable;

class Product extends Model
{
    use Searchable;
...
```

Then configure the `toSearchableArray` method to tell scout where to search

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

To make it work let's add the `whereScout` scope

```php
/**
 * 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'),
    );
}
```

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

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

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](https://turbo.hotwired.dev/handbook/frames "Turbo Frames"). Wrap everything inside the div with class `.card-body` inside the turbo frame tag like this

```html
<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>
```

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

```php
/**
 * 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',
    );
}
```

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

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

The `data-turbo-method="delete"` attribute tells [Turbo](https://t), 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\_](https://twitter.com/ncubegiven_ "Given Ncube on Twitter")

In the meantime Happy Coding!