Laravel 8 - CRUD basic steps (Livewire and Tailwind)
Tags: laravel, livewire, crud, tailwindcss
Published: 2020-12-23 22:35:14

* Let's create migration file. Run command:
```php artisan make:migration create_companies_table```
* In migration file add code:
```php 
<?php

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

class CreateCompaniesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('companies', function (Blueprint $table) {
            $table->id();
            $table->string('title')->unique();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('companies');
    }
}
```
* Run command to add table in database: 
```php artisan migrate```
* Run command to create Livewire CRUD files (it will create /app/Http/Livewire/Companies.php and /resources/views/livewire/companies.blade.php):
```php artisan make:livewire companies```
* In /app/Http/Livewire/Companies.php file add code:
```php 
<?php

namespace App\Http\Livewire;

use Livewire\Component;
use App\Models\Company;

class Companies extends Component
{
    public $title;
    public $company_id;
    public $isOpen = 0;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
 
    public function render()
    {
        return view('livewire.companies', [
            'companies' => Company::orderBy('id', 'desc')]);
    }

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */

    public function create()
    {
        $this->resetInputFields();
        $this->openModal();
    }

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    public function openModal()
    {
        $this->isOpen = true;
    }

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    public function closeModal()
    {
        $this->isOpen = false;
    }

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    private function resetInputFields(){
        $this->title = '';
        $this->company_id = '';
    }

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */

    public function store()
    {
        $this->validate([
            'title' => 'required|unique:companies,title,'.$this->company_id,
        ]);
        $data = array(
            'title' => $this->title
        );
        $company = Company::updateOrCreate(['id' => $this->company_id],$data);
        session()->flash('message', $this->company_id ? 'Company updated successfully.' : 'Company created successfully.');
        $this->closeModal();
        $this->resetInputFields();
    }

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    public function edit($id)
    {
        $company = Company::findOrFail($id);
        $this->company_id = $id;
        $this->title = $company->title;
        $this->openModal();
    }

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    public function delete($id)
    {
        $this->company_id = $id;
        Company::find($id)->delete();
        session()->flash('message', 'Company deleted successfully.');
    }

}
```
* In /resources/views/livewire/companies.blade.php file add code
```html
<x-slot name="header">
    <h2 class="font-semibold text-xl text-gray-800 leading-tight">
        {{ __('Companies') }}
    </h2>
</x-slot>
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
    @if (session()->has('message'))
        <div id="alert" class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-green-500">
            <span class="inline-block align-middle mr-8">
                {{ session('message') }}
            </span>
            <button class="absolute bg-transparent text-2xl font-semibold leading-none right-0 top-0 mt-4 mr-6 outline-none focus:outline-none" onclick="document.getElementById('alert').remove();">
                <span>×</span>
            </button>
        </div>
    @endif
    <button wire:click="create()" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded mt-10">Create New Company</button>
    @if (count($companies)>0)
        <div class="py-10">
            <div class="inline-block min-w-full shadow rounded-lg overflow-hidden">
                <table class="min-w-full leading-normal">
                    <thead>
                        <tr>
                            <th
                                class="px-5 py-3 border-b-2 border-black bg-black text-left text-xs font-semibold text-white uppercase tracking-wider">
                                {{ __('Title') }}
                            </th>
                            <th
                                class="px-5 py-3 border-b-2 border-black bg-black text-left text-xs font-semibold text-white uppercase tracking-wider">
                            </th>
                        </tr>
                    </thead>
                    <tbody>
                        @foreach($companies as $company) 
                            <tr>
                                <td class="px-5 py-5 bg-white text-sm @if (!$loop->last) border-gray-200 border-b @endif">
                                    {{ Str::limit($company->title, 25) }}
                                </td>
                                <td class="px-5 py-5 bg-white text-sm @if (!$loop->last) border-gray-200 border-b @endif text-right">
                                    <div class="inline-block whitespace-no-wrap">
                                        <button wire:click="edit({{ $company->id }})" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">Edit</button>
                                        <button wire:click="$emit('triggerDelete',{{ $company->id }})" class="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded">Delete</button>
                                    </div>
                                </td>
                            </tr>
                        @endforeach
                    </tbody>
                </table>
            </div>
        </div>
    @endif
    @if($isOpen)
		<div class="fixed z-100 w-full h-full bg-gray-500 opacity-75 top-0 left-0"></div>
		<div class="fixed z-101 w-full h-full top-0 left-0 overflow-y-auto">
			<div class="table w-full h-full py-6">
				<div class="table-cell text-center align-middle">
					<div class="w-full max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
						<div class="bg-white rounded-lg text-left overflow-hidden shadow-xl">
							<form>
							    <div class="px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
								<div class="flex flex-wrap -mx-3 mb-6">
						    <div class="w-full md:w-1/2 px-3 mb-6 md:mb-0">
									<label for="titleInput" class="block text-gray-700 text-sm font-bold mb-2">Title:</label>
									<input type="text" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" id="titleInput" placeholder="Enter Title" wire:model="title">
									@error('title') <span class="text-red-500">{{ $message }}</span>@enderror
								    </div>
								</div>
							    </div>
							    <div class="px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
								<span class="flex w-full sm:ml-3 sm:w-auto">
								    <button wire:click.prevent="store()" type="button" class="inline-flex bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">Save</button>
								</span>
								<span class="mt-3 flex w-full sm:mt-0 sm:w-auto">
								    <button wire:click="closeModal()" type="button" class="inline-flex bg-white hover:bg-gray-200 border border-gray-300 text-gray-500 font-bold py-2 px-4 rounded">Cancel</button>
								</span>
							    </div>
							</form> 
						</div>
					</div>
				</div>
			</div>
		</div>
    @endif
</div>

@push('styles')
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/sweetalert2@10/dist/sweetalert2.min.css">
@endpush

@push('scripts')
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@10"></script>
<script src="https://cdn.jsdelivr.net/npm/promise-polyfill@8/dist/polyfill.js"></script>
<script type="text/javascript">
    document.addEventListener('DOMContentLoaded', function () {

        @this.on('triggerDelete', companyId => {
            Swal.fire({
                title: 'Are You Sure?',
                text: 'Company record will be deleted!',
                type: "warning",
                showCancelButton: true,
                confirmButtonColor: '#d33',
                cancelButtonColor: '#3085d6',
                confirmButtonText: 'Delete!'
            }).then((result) => {
                if (result.value) {
                    @this.call('delete',companyId)
                } else {
                    console.log("Canceled");
                }
            });
        });
    })
</script>
@endpush
```

* Our javascript code has to be between tags:
```html
@push('scripts') ... @endpush
```
and style
```html
@push('styles') ... @endpush
```
It means in
/resources/views/layouts/app.blade.php
need to add @stack('styles') and @stack('scripts')

```html
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta name="csrf-token" content="{{ csrf_token() }}">

        <title>{{ config('app.name', 'Laravel') }}</title>

        <!-- Fonts -->
        <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700&display=swap">

        <!-- Styles -->
        <link rel="stylesheet" href="{{ asset('css/app.css') }}">

        @livewireStyles

        @stack('styles')

        <!-- Scripts -->
        <script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.7.0/dist/alpine.js" defer></script>
    </head>
    <body class="font-sans antialiased">
        <div class="min-h-screen bg-gray-100">
            @livewire('navigation-dropdown')

            <!-- Page Heading -->
            <header class="bg-white shadow">
                <div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
                    {{ $header }}
                </div>
            </header>

            <!-- Page Content -->
            <main>
                {{ $slot }}
            </main>
        </div>

        @stack('modals')

        @livewireScripts

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

* Run command to create Company Model:
```php artisan make:model Company```
* In /app/Models/Company.php file add code:
```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Company extends Model
{
    use HasFactory;

    protected $fillable = [
        'title'
    ];
}
```
* In /routes/web.php need to add route:
```
Route::middleware(['auth:sanctum', 'verified'])->get('/companies', App\Http\Livewire\Companies::class)->name('companies');
```
* And run command to clean route cache: 
```php artisan route:cache```
* And in the last step need to add companies pages link to main menu. In /resources/views/navigation-dropdown.blade.php add menu item:
```html
<div class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex">
    <x-jet-nav-link href="{{ route('companies') }}" :active="request()->routeIs('companies')">
        {{ __('Companies') }}
    </x-jet-nav-link>
</div>
```
* And in the same file, where is comment  <!-- Responsive Navigation Menu --> add:
```html
<x-jet-responsive-nav-link href="{{ route('companies') }}" :active="request()->routeIs('companies')">
{{ __('Companies') }}
</x-jet-responsive-nav-link>
```
* These are basic steps to create CRUD by using Laravel, Livewire and Tailwind, but if we want to customise our CRUD please visit my second tutorial part: [Laravel 8 - advanced CRUD (Livewire and Tailwind) ](https://dev.to/dariusdauskurdis/laravel-8-advanced-crud-livewire-and-tailwind-271l)