Laravel Livewire | Build Ecommerce Application with Turbolinks

In this tutorial, You’ll learn to Laravel Livewire | Build Ecommerce Application with Turbolinks. I have been testing all its awesome features for quite a long time and indeed very much impressed by what it provides. So I thought to publish a new post for you regarding Building E-commerce Application using Turbolinks in Laravel Livewire.

Before this, I have also published four posts which are Counter Application, log in and Registration, Multiple Files Upload and Todo Application using Livewire package and there you’ll learn various implementation of features.

In short, Below are the concepts which are covered in this post.

  • Installation and Configuration of Turbolinks.
  • Setting up project and component structure.
  • Using Turbolinks to navigate or load other components templates.
  • Handling flash messages across components.
  • Working and sharing cookies across various components.

What is Turbolinks?

Turbolinks provides out-of-box support to convert simple web application into a Single Page Application(SPA).
Some of the features in provides are given below:

  • Ability in navigating to any part of the web app without reloading the entire page.
  • Increase in performance as it doesn’t need server-side support.
  • Search Engine friendly.
  • Easy client-side integration.

How Turbolinks work under the hood?

Generally, when client request for webpage the server sends the response in HTML format and once it is rendered and turbolinks takes control and on user actions when clicked on a link the response of that is attached into HTML DOM and eliminates the need of again loading the whole webpage and increases the response time of web application.

Note

For more information on turbolinks visit git repository.

Installation and Configuration of Turbolinks

Generally, installing and configuring turbolinks in laravel does not take much time. After you have created a new project follow the below steps.

npm install --save turbolinks

All Javascript files will be downloaded under folder node_modules at the root path of laravel application. You’ll see the success message as shown in below image preview.
Laravel Livewire - ecommerce appinstalling turbolinks using npm install turbolinks

Importing Turbolinks

In resources/js/app.js import turbolinks module.

var Turbolinks = require("turbolinks");
Turbolinks.start();

The next step is to compile the javascript modules into a bundle using below command.

npm rum dev

Usually on success will return below output.
Laravel Livewie - compiling turbolinks using npm run dev

Alert

If you get an error during compile time than visit this thread on stack overflow.

Below is the preview of an error on running npm run dev.
Laravel Livewire - error installing turbolinks

Setting Layout Template

A layout is an HTML template file which contains assets and livewire scripts. You can consider it as the first view then will be rendered with components. By default, livewire assumes layout file is located at resources/views/layouts/app.blade.php.
Look at a snippet of app.blade.php.

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Laravel Livewire | E-Commerce Application using Turbolinks</title>
        @livewireStyles
        @livewireScripts
        <script src="{{ url('js/app.js') }}" ></script>
        <link rel="stylesheet" href="{{ url('assets/css/bootstrap.min.css') }}">

        <style>
            .error{
                color: red;
            }
        </style>

        @stack('styles')
    </head>
    <body>
        @livewire('ecommerce.menu-component') 
        
        @yield('content')

        <script src="{{ url('assets/js/jquery.min.js') }}" data-turbolinks-eval=false ></script>
        <script src="{{ url('assets/js/popper.min.js') }}" data-turbolinks-eval=false ></script>
        <script src="{{ url('assets/js/bootstrap.min.js') }}" data-turbolinks-eval=false ></script>

        @stack('scripts')
    </body>
</html>

Note

You can customize the layout location and inform livewire about its path in web.php. For example, visit official website docs.

Components and their purpose

This project of eCommerce application with turbolinks contains five components and each of them is explained below:

  • Home: This is the first component to be rendered. And for the most part, it renders products and sidebar filters.
  • MenuComponent: This renders navbar menu items and is used to redirect from one component to another using turbolinks.
  • CartComponent: It displays a table of products added to cart and also orders confirmation form.
  • Product/FormComponent: Performs save and update operation for products.
  • Product/ListComponent: Displays a list of saved products.

Source code of E-commerce application with Turbolinks

Project Directory Structure

laravel_app/
        |-app
        |   |-Http
        |   |    |-Livewire
        |   |        |-Ecommerce
        |   |        |   |-Product
        |   |        |       |-FormComponent.php
        |   |        |       |-ListComponent.php
        |   |        |----CartComponent.php
        |   |        |----Home.php
        |   |        |----MenuComponent.php
        |   |-Requests
        |       |-SaveOrderRequest.php
        |-ProductModel.php
        |-OrderModel.php
        |-Utils
        |    |-CookieManager.php
        |
        |-database
        |   |-migrations
        |       |-2020_05_17_133623_create_product_table.php
        |       |-2020_05_19_172115_create_order_table.php
        |       |-2020_05_19_172426_create_order_items_table.php
        |
        |-node_modules
        |-public
        |-ecommerce
        |     |- create_product.js
        |-resource
        |   |-views
        |       |-js
        |         |-app.js
        |       |-layouts
        |           |-app.blade.php
        |       |-livewire
        |           |-ecommerce
        |               |-product
        |                   |-create_form.blade.php
        |                   |-list.blade.php
        |               |-cart.blade.php
        |               |-home.blade.php
        |               |-menu.blade.php
        |               |-notification.blade.php       
        |-routes
        |   |-web.php

Database Migrations and Models

For instance, I’ll create a migration for three tables they are products, orders and order_items.

Migration classes

In database/migrations/2020_05_17_133623_create_product_table.php.

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateProductTable extends Migration
{
    /**
        * Run the migrations.
        *
        * @return void
        */
    public function up()
    {
        Schema::create('products', function (Blueprint $table) {
            $table->bigIncrements('product_id');
            $table->string('name', 100);
            $table->string('image', 100)->nullable();
            $table->integer('price');
            $table->timestamps();
            $table->softDeletes();
        });
    }

    /**
        * Reverse the migrations.
        *
        * @return void
        */
    public function down()
    {
        Schema::dropIfExists('products');
    }
}    

In database/migrations/2020_05_19_172115_create_order_table.php.

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateOrderTable extends Migration
{
    /**
        * Run the migrations.
        *
        * @return void
        */
    public function up()
    {
        Schema::create('orders', function (Blueprint $table) {
            $table->bigIncrements('order_id');
            $table->string('customer_name', 100);
            $table->string('email', 100);
            $table->text('delivery_address');
            $table->string('mobile_no', 100);
            $table->string('alternate_mobile_no', 100)->nullable();
            $table->float('total_payable_amount');
            $table->string('status', 100)->default('pending');
            $table->timestamps();
            $table->softDeletes();
        });
    }

    /**
        * Reverse the migrations.
        *
        * @return void
        */
    public function down()
    {
        Schema::dropIfExists('orders');
    }
}

In database/migrations/2020_05_19_172426_create_order_items_table.php.

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateOrderItemsTable extends Migration
{
    /**
        * Run the migrations.
        *
        * @return void
        */
    public function up()
    {
        Schema::create('order_items', function (Blueprint $table) {
            $table->bigIncrements('order_item_id');
            $table->bigInteger('order_id');
            $table->bigInteger('product_id');
            $table->integer('quantity');
            $table->float('price');
            $table->timestamps();
            $table->softDeletes();
        });
    }

    /**
        * Reverse the migrations.
        *
        * @return void
        */
    public function down()
    {
        Schema::dropIfExists('order_items');
    }
}

Models

In app/ProductModel.php.

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Log;

class ProductModel extends Model
{
    protected $table="products";
    protected $primaryKey = 'product_id';
    protected $fillable = ['name', 'image', 'price'];
    
    public function getImageAttribute(){ 
        $image = "";
        if(!empty($this->attributes["image"])){
            $image = url('storage/uploads/'.$this->attributes["image"]);
        }
        return $image;
    }
}

In app/OrderModel.php.

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Log;

class OrderModel extends Model
{
    protected $table="orders";
    protected $primaryKey = 'order_id';
    protected $fillable = ['customer_name', 'email', 'delivery_address', 'mobile_no', 'alternate_mobile_no', 'total_payable_amount', 'status'];
    
    public function items()
    {
        return $this->hasMany(OrderItemModel::class, 'order_id', 'order_id');
    }
}

In app/OrderItemModel.php.

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Log;

class OrderItemModel extends Model
{
    protected $table="order_items";
    protected $primaryKey = 'order_item_id';
    protected $fillable = ['order_id', 'product_id', 'quantity', 'price'];
    
    public function order()
    {
        return $this->belongsTo(OrderModel::class, 'order_id', 'order_id');
    }
}

Routing for Livewire Components

In routes/web.php.

Route::group(['prefix' => "ecom"], function () {
    Route::livewire('/', 'ecommerce.home')->name('ecommerce.home');
    Route::livewire('/products', 'ecommerce.product.list-component')->name('ecommerce.product.list');
    Route::livewire('/product/create', 'ecommerce.product.form-component')->name('ecommerce.product.form');
    Route::livewire('/product/edit/{product_id}', 'ecommerce.product.form-component')->name('ecommerce.product.edit');
    Route::livewire('/cart-items', 'ecommerce.cart-component')->name('ecommerce.cart-items');
});

Cookie Manager

In app/Utils/CookieManager.php.

<?php

namespace App\Utils;

use Log;
use Cookie;
use Illuminate\Support\Arr;

class CookieManager
{
    public function getCookie($name="cart_data"){
        $json = Cookie::get($name) ?? [];
        $info = [
            "json" => is_array($json)? "" : $json,
            "array" => json_decode(is_array($json)? "" : $json, TRUE),
        ];

        return $info;
    }

    public function checkIfProductExistsInCart($id){
        ["array" => $array] = $this->getCookie();
        
        if(empty($array["items"])){
            return TRUE;
        }

        $first = Arr::first($array["items"], function ($value, $key) use ($id){
            return $value["product_id"] == $id;
        });
        
        if(!empty($first)){
            return FALSE;
        }else{
            return TRUE;
        }
    }    

    public function execute($arr, $cookie_name="cart_data"){
        $minutes = 60*3600;
        Cookie::queue($cookie_name, json_encode($arr), $minutes);
    }

    public function flush($cookie_name="cart_data"){
        $minutes = 60*3600;
        Cookie::queue($cookie_name, json_encode([]), $minutes);
    }
}

Home Component

In app/Http/Livewire/Ecommerce/Home.php.

<?php

namespace App\Http\Livewire\Ecommerce;

use Livewire\Component;
use App\ProductModel;
use Log;
use Cookie;
use Illuminate\Support\Arr;
use App\Utils\CookieManager;

class Home extends Component
{

    public $products = [];
    public $process_messages = NULL;

    public $listeners = [];

    public $filter = [
        "name" => "",
        "range-from" => "",
        "range-to" => "",
        "order_field" => "order_by_name_asc",
    ];
    
    public function mount(){
        $this->loadProducts();
    }

    public function loadProducts(){
        try {
            session()->flash("success", "Loading products ...");
            $query = [];
            $objects = ProductModel::where($query);

            // Price Range 
            if(!empty($this->filter["range-from"]) && !empty($this->filter["range-to"])){
                $price_1 = $this->filter["range-from"];
                $price_2 = $this->filter["range-to"];

                $objects = $objects->whereBetween('price', [$price_1, $price_2]);
            }

            // Search
            if(!empty($this->filter["name"])){
                $filter = $this->filter;
                $objects = $objects->where(function ($query) use ($filter) {
                    $query->where('name', 'LIKE', $this->filter['name'] . '%');
                });
            }
            
            // Ordering
            $field = 'name';
            $type = 'asc';
            switch ($this->filter["order_field"]) {
                case 'order_by_name_asc':
                    $type = 'asc';
                    break;

                case 'order_by_name_desc':
                    $type = 'desc';
                    break;

                case 'order_by_price_low':
                    $field = 'price';
                    $type = 'asc';
                    break;

                case 'order_by_price_high':
                    $field = 'price';
                    $type = 'desc';
                    break;
            }

            $objects = $objects->orderBy($field, $type);
            
            $this->products = $objects->get()->toArray();
        } catch (Throwable $e) {
            session()->flash("error", "Something went wrong");
            $this->products = [];
        }
    }

    public function addToCart($id){
        try {
            $cookie_manager = new CookieManager();
            
            // Check if cookie exits
            [
                "array" => $cookie_data
            ] = $cookie_manager->getCookie();

            if(empty($cookie_data["items"])){
                $cookie_data["items"] = [];
            }
            
            $product = [];
            foreach($this->products as $k => $v){
                if($v["product_id"] == $id){
                    $product = $v;
                }
            }
            
            if($cookie_manager->checkIfProductExistsInCart($id)){
                $product["quantity"] = 1;
                array_push($cookie_data["items"], $product);

                $cookie_manager->execute($cookie_data);

                session()->flash("success", "Products added to cart.");
            }else{
                session()->flash("info", "Product already exists in cart.");
            }
        } catch (Throwable $e) {
            session()->flash("error", "Something went wrong.");
        }
    }

    public function render()
    {
        return view('livewire.ecommerce.home');
    } 
}
    

Product List Component

In app/Http/Livewire/Ecommerce/Product/ListComponent.php.

<?php

namespace App\Http\Livewire\Ecommerce\Product;

use Livewire\Component;
use App\ProductModel;
use Log;

class ListComponent extends Component
{

    public $products = [];

    public $listeners = [
        "delete_product" => "deleteProduct",
    ];
    
    public function mount(){
        $this->loadProducts();
    }

    public function loadProducts(){
        try {
            $products = ProductModel::all()->toArray();
            $this->products = $products;
        } catch (Throwable $e) {
            session()->flash("error", "Something went wrong");
            $this->products = [];
        }
    }

    public function deleteProduct($id){
        try {
            ProductModel::find($id)->delete();
            session()->flash("success", "Product removed successfully.");
            $this->loadProducts();
        } catch (Throwable $e) {
            session()->flash("error", "Something went wrong");
        }
    }

    public function render()
    {
        return view('livewire.ecommerce.product.list');
    }
}
    

Product Form Component

In app/Http/Livewire/Ecommerce/Product/FormComponent.php.

<?php

namespace App\Http\Livewire\Ecommerce\Product;

use Livewire\Component;
use Exception;
use Log;
use File;
use Illuminate\Support\Str;
use Storage;
use Illuminate\Support\Facades\Validator;
use App\ProductModel;
use DB;
use Illuminate\Support\Arr;
use Throwable;
use Illuminate\Support\Facades\Redirect;

class FormComponent extends Component
{

    public $product = [
        "product_id" => NULL,
        "name" => "",
        "image" => "",
        "price" => "",
    ];

    public $listeners = [
        "product_file_uploaded" => "fileUploaded"
    ];

    public $validation_errors = [];

    public function mount($product_id=NULL){
        try {
            $product = ProductModel::find($product_id)->toArray();
            $this->product = Arr::except($product, ['created_at', 'updated_at', 'deleted_at']);
        } catch (Throwable $e) {

        }
    }

    public function fileUploaded($file){
        $this->product["image"] = "";
        if($this->getFileInfo($file)["file_type"] == "image"){
            $this->product["image"] = $file;
        }
    }

    public function getFileInfo($file){
        $info = [
            "decoded_file" => NULL,
            "file_meta" => NULL,
            "file_mime_type" => NULL,
            "file_type" => NULL,
            "file_extension" => NULL,
        ];
        try{
            $info['decoded_file'] = base64_decode(substr($file, strpos($file, ',') + 1));
            $info['file_meta'] = explode(';', $file)[0];
            $info['file_mime_type'] = explode(':', $info['file_meta'])[1];
            $info['file_type'] = explode('/', $info['file_mime_type'])[0];
            $info['file_extension'] = explode('/', $info['file_mime_type'])[1];
        }catch(Exception $ex){

        }

        return $info;
    }

    public function save(){

        $rules=[
            'product_id' => 'nullable',
            'name' => 'required',
            'image' => 'nullable',
            'price' => 'required|integer',
        ];

        $messages = [
            "name.required" => "Name must be filled.",
            "price.required" => "Enter price.",
            "price.integer" => "Price must not have decimal points.",
        ];

        $validator = Validator::make($this->product,$rules, $messages);
        
        $validator->after(function ($validator) {
            if(empty($validator->getData()["product_id"]) && !empty($validator->getData()["image"]) && $this->getFileInfo($validator->getData()["image"])["file_type"] != "image"){
                $validator->errors()->add('image', 'Must be an image');   
            }

        });

        if($validator->fails()){
            return $this->validation_errors = $validator->errors()->toArray();
        }else{
            $info = [
                "success" => FALSE,
                "product" => NULL,
            ];

            DB::beginTransaction(); 
            try {
                $product = $this->product;
                unset($product["product_id"], $product["image"]);
                $query = $product;
    
                $condition = [
                    "product_id" => $this->product["product_id"]
                ];

                $info["product"] = ProductModel::updateOrCreate($condition, $query);

                if(!empty($this->product["image"]) && $this->getFileInfo($this->product["image"])["file_type"] == "image"){
                    
                    $file_data = $this->getFileInfo($this->product["image"]);
                    $file_name = Str::random(10).'.'.$file_data['file_extension'];
                    $result = Storage::disk('public_uploads')->put($file_name, $file_data['decoded_file']);

                    if($result){
                        $info["product"]["image"] = $file_name;
                    }
                }

                $info["product"]->save();
                
                DB::commit();
                $info['success'] = TRUE;
            } catch (\Exception $e) {
                DB::rollback();
                $info['success'] = FALSE;
            }
        
            if($info["success"]){ 
                $type = "success";
                if($info["product"]->wasRecentlyCreated){
                    $message = "New product created successfully.";
                }else{
                    $message = "Product updated successfully.";
                }   
            }else{
                $type = "error";
                $message = "Something went wrong while saving task.";
            }
            
            session()->flash($type, $message);

            if($info["success"]){
                return redirect()->to(route('ecommerce.product.edit', ["product_id" => $info["product"]["product_id"] ]));
            }
        }
    }

    public function render()
    {
        return view('livewire.ecommerce.product.create_form');
    }
}
    

Cart Component

In app/Http/Livewire/Ecommerce/CartComponent.php.

<?php

namespace App\Http\Livewire\Ecommerce;

use Livewire\Component;
use App\Utils\CookieManager;
use Illuminate\Support\Arr;
use App\Http\Requests\SaveOrderRequest;
use DB;
use App\OrderModel;
use App\OrderItemModel;

class CartComponent extends Component
{
    public $items = [];
    public $order_form = [
        'customer_name' => '',
        'email' => '',
        'delivery_address' => '',
        'mobile_no' => '',
        'alternate_mobile_no' => '',
    ];
    public $totals = [
        "qty" => 0,
        "amount" => 0,
    ];

    public function mount()
    {
        $cart = $this->getItems();
        $this->items = $cart['items'] ?? [];
    }

    public function getItems(){
        $cookie_manager = new CookieManager();

        [
            "array" => $array
        ] = $cookie_manager->getCookie();


        foreach(($array["items"] ?? []) as $k => $v){
            $this->totals["qty"] += $v["quantity"];
            $this->totals["amount"] += (int)$v["quantity"]*$v["price"];
        }

        return $array;
    }

    public function updatedItems(){
        $cookie_manager = new CookieManager();
        $this->totals["qty"] = 0;
        $this->totals["amount"] = 0;
        foreach(($this->items ?? []) as $k => $v){
            $this->totals["qty"] += $v["quantity"];
            $this->totals["amount"] += (int)$v["quantity"]*$v["price"];
        }

        [
            "array" => $array
        ] = $cookie_manager->getCookie();

        $array["items"] = $this->items;

        $cookie_manager->execute($array);
    }

    public function removeItem($id)
    {
        $cookie_manager = new CookieManager();

        [
            "array" => $array
        ] = $cookie_manager->getCookie();

        foreach(($array['items'] ?? []) as $k => $v){
            if($v["product_id"] == $id){
                unset($array['items'][$k]);
            }
        }

        $this->items = $array['items'];

        $cookie_manager->execute($array);

        session()->flash('success', "Item removed from cart.");

        $this->updatedItems();
    }

    public function saveOrder(){
        $saveOrderRequest = new SaveOrderRequest();
        $saveOrderRequest->merge($this->order_form);

        $validated_data = $saveOrderRequest->validate($saveOrderRequest->rules());

        DB::beginTransaction(); 
        try {
            $query = [
                "customer_name" => $validated_data["customer_name"],
                "email" => $validated_data["email"],
                "delivery_address" => $validated_data["delivery_address"],
                "mobile_no" => $validated_data["mobile_no"],
                "alternate_mobile_no" => $validated_data["alternate_mobile_no"],
                "total_payable_amount" => $this->totals["amount"], 
                "status" => "pending",
            ];

            $info["order"] = OrderModel::create($query);

            foreach($this->items as $k => $v){
                $query = [
                    "product_id" => $v["product_id"],
                    "quantity" => $v["quantity"],
                    "price" => $v["price"],
                ];
                $info["order"]->items()->create($query);
            }
            
            DB::commit();
            $info['success'] = TRUE;

            $this->order_form = [
                'customer_name' => '',
                'email' => '',
                'delivery_address' => '',
                'mobile_no' => '',
                'alternate_mobile_no' => '',
            ];
        } catch (\Exception $e) {
            DB::rollback();
            $info['success'] = FALSE;
        }

        if($info["success"]){ 

            // empty cookie data
            $cookie_manager = new CookieManager();
            $cookie_manager->flush();
            $this->items = [];

            $type = "success";
            $message = "New order confirmed.";
        }else{
            $type = "error";
            $message = "Something went wrong while saving order.";
        }

        session()->flash($type, $message);
    }

    public function render()
    {
        return view('livewire.ecommerce.cart');
    }
}
    

Menu Component

<?php

namespace App\Http\Livewire\Ecommerce;

use Livewire\Component;

class MenuComponent extends Component
{
    public function render()
    {
        return view('livewire.ecommerce.menu');
    }
} 

Home Component Template: resources/views/livewire/ecommerce/home.blade.php

<div class="container" >
    @include('livewire.ecommerce.notification')
    
    @push('styles')
        <style>
            .content-container{
                margin-top: 25px;
            }

            .filter-container{
                padding: 10px 15px;
                background: #f1f1f1;
            }
            
            .filter-container > div{
                margin-bottom: 15px;
            }

            .img-container{
                height: 150px;
                background: #fff;
            }

            .img-container > img{
                width: 100%;
                height: 150px;
                object-fit: contain;
                padding: 10px 0;
            }

            .products-list-container > .item{
                padding: 10px;
                background-color: #eaa356;
            }

            /* Grid View */
            .products-list-container.grid-view{
                display: grid;
                grid-template-columns: repeat(3,1fr);
                grid-column-gap: 10px;
                grid-row-gap: 10px;
                margin-top: 15px;
            }

            .product-content-container > p{
                margin: 0;
            }

            .product-content-container > p:nth-child(1){
                font-weight: bold;
                font-size: 18px;
            }
        </style>
    @endpush

    <div class="title-container">
        <h2 class="text-center" style="margin: 0;" >Laravel Livewire | E-Commerce Application using Turbolinks</h2>
    </div>

    <div class="content-container">
        <div class="row">
            <div class="col-md-3">
                <div class="filter-container">
                    <div>
                        <h3 class="" style="margin: 0;" >Filter</h3>
                        <hr>
                    </div>

                    <div>
                        <label for="">Search</label>
                        <input type="text" wire:model="filter.name" class="form-control" >
                    </div>

                    <div>
                        <label for="">Range</label>
                        <div class="row">
                            <div class="col-md-6">
                                <label for="">From</label>
                                <input type="number" wire:model="filter.range-from" class="form-control" >
                            </div>

                            <div class="col-md-6">
                                <label for="">To</label>
                                <input type="number" wire:model="filter.range-to" class="form-control" >
                            </div>
                        </div>
                    </div>

                    <div style="text-align: right;">
                        <button type="button" wire:click="loadProducts" class="btn btn-primary btn-sm" >Filter</button>
                    </div>
                </div>
            </div>

            <div class="col-md-9">
                <div class="product-layout-container">
                    <select style="width: 250px;" class="form-control" wire:model="filter.order_field" wire:change="loadProducts" >
                        <option value="order_by_name_asc">Order name by ascending</option>
                        <option value="order_by_name_desc">Order name by decending</option>
                        <option value="order_by_price_low">Price: Low to High</option>
                        <option value="order_by_price_high">Price: High to Low</option>
                    </select>
                </div>

                @if(!empty($process_messages))
                    {{$process_messages}}
                @endif

                <div class=" products-list-container grid-view">
                    @if(!empty($products))
                        @foreach($products as $k => $v)
                            <div class="item">
                                <div class="img-container">
                                    <img src="{{ $v['image'] ?? '' }}" alt="">
                                </div> 

                                <div class="product-content-container">
                                    <p>{{ $v['name'] }}</p>
                                    <p>Price : {{ $v['price'] }}/-</p>
                                    <a href="javascript:void(0)" wire:click="addToCart({{ $v['product_id'] }})" class="btn btn-primary btn-sm">Add to Cart</a>
                                </div>
                            </div>
                        @endforeach
                    @else

                    @endif
                </div>
            </div>
        </div>
    </div>
</div>

MenuComponent Template: resources/views/livewire/ecommerce/menu.blade.php

<nav class="navbar navbar-expand-md bg-dark navbar-dark">
    <a class="navbar-brand" href="#">E-Commerce</a>

    <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#collapsibleNavbar">
        <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="collapsibleNavbar">
        <ul class="navbar-nav">
            <li class="nav-item">
                <a class="nav-link" href="{{ route('ecommerce.home') }}">Home</a>
            </li>
            
            <li class="nav-item dropdown">
                <a class="nav-link dropdown-toggle" href="#" id="navbardrop" data-toggle="dropdown">
                    Products
                </a>
                <div class="dropdown-menu">
                    <a class="dropdown-item" href="{{ route('ecommerce.product.form') }}">Create Product</a>
                    <a class="dropdown-item" href="{{ route('ecommerce.product.list') }}">List Products</a>
                </div>
            </li>
            
            <li class="nav-item">
                <a class="nav-link" href="{{ route('ecommerce.cart-items') }}">Cart</a>
            </li>    
        </ul>
    </div>  
</nav>

CartComponent Template: resources/views/livewire/ecommerce/cart.blade.php

<div class="container">
    @push('styles')
        <style>
            .order-form-container{

            }

            .order-form-container .title{
                margin: 0;
            }

            .order-form-container > div{
                margin-bottom: 15px;
            }
        </style>
    @endpush

    @include('livewire.ecommerce.notification')
    <div class="row">
        <div class="col-md-8">
            <h2 style="margin:0">Cart Items</h2>
            <div>
                <table class="table table-bordered table-striped" >
                    <thead>
                        <tr>
                            <th>Image</th>
                            <th>Item</th>
                            <th>Quantity</th>
                            <th>Price</th>
                            <th>Total</th>
                            <th>Action</th>
                        </tr>
                    </thead>
                    <tbody>
                        @if($items)
                            @foreach($items as $k => $v)
                                <tr>
                                    <td>
                                        @if($v["image"])
                                            <img src="{{ $v['image'] }}" style="height: 100px;">
                                        @else
                                            NA
                                        @endif
                                    </td>
                                    <td>{{ $v['name'] }}</td>
                                    <td><input type="number" min="1" class="form-control" wire:model="items.{{ $k }}.quantity" ></td>
                                    <td>{{ $v['price'] }}</td>
                                    <td>{{ (int)$v['quantity']*$v['price'] }}</td>
                                    <td>
                                        <button type="button" wire:click="removeItem({{ $v['product_id'] }})" class="btn btn-danger btn-sm">Remove</button>
                                    </td>
                                </tr>
                            
                            @endforeach
                        @else
                            <tr>
                                <td colspan="6">No products added...<a href="{{ route('ecommerce.home') }}">click here</a> to start shopping.</td>
                            </tr>
                        @endif
                    </tbody>

                    @if($items)
                        <tfoot>
                            <td colspan="2" ></td>
                            <td>{{ $totals["qty"] }}</td>
                            <td></td>
                            <td>{{ $totals["amount"] }}</td>
                            <td></td>
                        </tfoot>
                    @endif
                </table>
            </div>
        </div>
        <div class="col-md-4">
            <form wire:submit.prevent="saveOrder" method="post">
                <div class="order-form-container" >
                    <div>
                        <h2 class="title">Order Form</h2>
                    </div>

                    <div>
                        <label for="">Customer Name</label>
                        <input type="text" wire:model="order_form.customer_name" class="form-control">
                        @error('customer_name') 
                            <label class="error" >{{ $message }}</label>
                        @enderror
                    </div>

                    <div>
                        <label for="">Email</label>
                        <input type="text" wire:model="order_form.email" class="form-control">
                        @error('email') 
                            <label class="error" >{{ $message }}</label>
                        @enderror
                    </div>

                    <div>
                        <label for="">Mobile No</label>
                        <input type="text" wire:model="order_form.mobile_no" class="form-control">
                        @error('mobile_no') 
                            <label class="error" >{{ $message }}</label>
                        @enderror
                    </div>

                    <div>
                        <label for="">Alternate Mobile No</label>
                        <input type="text" wire:model="order_form.alternate_mobile_no" class="form-control">
                        @error('alternate_mobile_no') 
                            <label class="error" >{{ $message }}</label>
                        @enderror
                    </div>

                    <div>
                        <label for="">Delivery Address</label>
                        <textarea wire:model="order_form.delivery_address" class="form-control" rows="3" ></textarea>
                        @error('delivery_address') 
                            <label class="error" >{{ $message }}</label>
                        @enderror
                    </div>

                    <div>
                        <button type="submit" class="btn btn-success btn-sm" >Confirm Order</button>
                    </div>
                </div>
            </form>
        </div>
    </div>
</div>

Product/ListComponent Template: resources/views/livewire/ecommerce/product/list.blade.php

<div class="container-fluid" >
    <div class="title-container">
        <h2 class="text-center" style="margin: 0;" >List Products</h2>
    </div>

    <div class="row">
        <div class="col-md-6 offset-3">
            <div class="table-container">
                <table class="table table-bordered">
                    <thead>
                        <tr>
                            <th style="width: 100px;" >Image</th>
                            <th>Name</th>
                            <th>Price</th>
                            <th>Action</th>
                        </tr>
                    </thead>

                    <tbody>

                        @if(!empty($products))
                            @foreach($products as $k => $v)
                                <tr>
                                    <td>
                                        @if(!empty($v['image']))
                                            <img src="{{ $v['image'] }}" alt="" style="width: 100%;" >
                                        @else
                                            NA
                                        @endif
                                    </td>
                                    <td>{{ $v['name'] }}</td>
                                    <td>{{ $v['price'] }}</td>
                                    <td>
                                        <a href="{{ route('ecommerce.product.edit', ['product_id' => $v['product_id'] ]) }}" class="btn btn-primary btn-sm" >Edit</a>
                                        <button type="button" wire:click="$emit('confirm_product_before_delete', {{ $v['product_id'] }} )" class="btn btn-danger btn-sm" >Remove</button>
                                    </td>
                                </tr>
                            @endforeach
                        @else
                            <tr>
                                <td colspan="4" >No products... <a href="{{ route('ecommerce.product.form') }}">click here</a> to add</td>
                            </tr>
                        @endif
                    </tbody>
                </table>
            </div>
        </div>
    </div>

    @push('scripts')
        <script>
            window.livewire.on('confirm_product_before_delete', function(id){
                let cfn = confirm('Confirm to remove product ?');

                if(cfn){
                    window.livewire.emit('delete_product', id);
                }else{
                    return false;
                }
            });
        </script>
    @endpush
</div>
    

Product/CreateComponent Template: resources/views/livewire/ecommerce/product/create_form.blade.php

<div class="container" >
    @push('styles')
        <style>
            .form-container > div{
                margin-bottom: 20px;
            }
        </style>
    @endpush

    <div class="title-container">
        <h2 class="text-center" style="margin: 0;" >
        @if(!empty($product["product_id"]))
            Edit
        @else
            Create
        @endif Product</h2>
    </div>
    <br>

    <form wire:submit.prevent="save" method="post">
        <input type="hidden" wire:model="product.product_id" >
        <div class="row form-container">
            <div class="col-md-4 offset-4">
                <div>
                    <label for="">Product Name</label>
                    <input type="text" class="form-control" wire:model="product.name" >
                    @if(!empty($validation_errors["name"]))
                        @foreach($validation_errors["name"] as $k => $v)
                            <label for="" class="error" >{{ $v }}</label>
                        @endforeach
                    @endif
                </div>
            </div>
            
            <div class="col-md-4 offset-4">
                <div>
                    <label for="">Image</label>
                    <input type="file" wire:change=$emit('product_file_selected')" class="form-control" >
                    @if(!empty($validation_errors["image"]))
                        @foreach($validation_errors["image"] as $k => $v)
                            <label for="" class="error" >{{ $v }}</label>
                        @endforeach
                    @endif

                    @if(!empty($product["image"]))
                        <br>
                        <img src="{{ $product['image'] }}" class="img-thumbnail" >
                    @endif
                </div>
            </div>
            
            <div class="col-md-4 offset-4">
                <div>
                    <label for="">Price</label>
                    <input type="text" class="form-control" wire:model="product.price" >
                    @if(!empty($validation_errors["price"])) 
                        @foreach($validation_errors["price"] as $k => $v)
                            <label for="" class="error" >{{ $v }}</label>
                        @endforeach
                    @endif
                </div>
            </div>
            
            <div class="col-md-4 offset-4">
                <div>
                    <input type="submit" class="btn btn-success btn-sm" value="Save" >
                    @if(!empty($product["product_id"]))
                        <a href="{{ route('ecommerce.product.form') }}" class="btn btn-primary btn-sm" >Create New Product</a>
                    @endif
                </div>
            </div>
        </div>
    </form>

    @push('scripts')
        <script src="{{ url('ecommerce/create_product.js') }}"></script>
    @endpush

</div>

Javascript File: public/ecommerce/create_product.js

window.livewire.on('product_file_selected', () => {
    try {
        let file = event.target.files[0];
        if(file){
            let reader = new FileReader();

            reader.onloadend = () => {
                window.livewire.emit('product_file_uploaded', reader.result);
            }
            reader.readAsDataURL(file);
        }
    } catch (error) {
        console.log(error);
    }
});

Notification Template: resources/views/livewire/ecommerce/notification.blade.php

@if(session()->has("success"))
    <div class="alert alert-success alert-dismissible fade show">
        <button type="button" class="close" data-dismiss="alert">×</button>
        {{ session('success') }}
    </div>
@endif

@if(session()->has("info"))
    <div class="alert alert-info alert-dismissible fade show">
        <button type="button" class="close" data-dismiss="alert">×</button>
        {{ session('info') }}
    </div>
@endif

@if(session()->has("error"))
    <div class="alert alert-danger alert-dismissible fade show">
        <button type="button" class="close" data-dismiss="alert">×</button>
        {{ session('error') }}
    </div>
@endif

Output

Home Page Preview

Laravel Livewire rendering home or default page

Cart Page Preview

Laravel Livewire rendering cart page

Product Create Page Preview

Laravel Livewire create product form with file upload

Product List Page Preview

Laravel Livewire CRUD operation on products

Video Preview

Conclusion

In conclusion, this is the end of the post on Laravel Livewire | Build Ecommerce Application with Turbolinks. For suggestions and queries comment below and to learn more visit the official livewire website.

Summary
Review Date
Reviewed Item
Laravel Livewire | Build Ecommerce Application with Turbolinks
Author Rating
51star1star1star1star1star
Software Name
Laravel Livewire Package
Software Name
ubuntu, windows, mac os
Software Category
Web Development