Skip to content

Crud Package

Prerequisites

Zet de “minimum-stability” in je composer.json op “dev” om de package te kunnen installeren.

composer.json
{
"minimum-stability": "dev"
}

Installation

Terminal window
composer require justin0122/crud

Configuration

bootstrap/providers.php
<?php
return [
App\Providers\AppServiceProvider::class,
App\Providers\FortifyServiceProvider::class,
App\Providers\JetstreamServiceProvider::class,
\Justin0122\Crud\CrudServiceProvider::class,
];

Livewire directory

Maak een Livewire directory aan in de app directory.

Terminal window
# voor linux:
mkdir app/Livewire
# voor windows:
md app/Livewire

Usage

Terminal window
php artisan crud:make Post

Genereert de volgende bestanden:

  • app/Livewire/Post.php

  • app/Livewire/Posts.php

  • app/Models/Post.php

  • database/migrations/2021_01_01_000000_create_posts_table.php

  • resources/views/livewire/posts/index.blade.php

  • resources/views/livewire/crud/create.blade.php

  • resources/views/livewire/crud/edit.blade.php

Na het genereren van de bestanden

Model Fillables

app/Models/Post.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Post extends Model
{
use HasFactory;
use SoftDeletes;
protected $fillable = [
'title',
'content'
];
}

Migration

database/migrations/2024_06_16_000000_create_posts_table.php
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('content');
$table->timestamps();
$table->softDeletes();
});

Table

resources/views/components/table.blade.php
@props(['results', 'type', 'create' => false, 'fillables' => null, 'crud' => true, 'warning' => null])
<div class="mx-4">
@if($warning)
<div class="rounded p-1.5 bg-red-200">
<span class="font-bold">{{$warning}}</span>
</div>
@endif
<table class="table-auto w-full divide-y divide-gray-200 dark:divide-gray-700 dark:bg-gray-800 dark:text-gray-200">
<thead>
<tr>
@foreach($fillables ? $fillables : $results[0]->getFillable() as $fillable)
<th class="px-4 py-2 text-left">{{ ucwords(join(' ', preg_split('/(?=[A-Z])/', $fillable))) }}</th>
@endforeach
<th class="px-4 py-2 text-left">Created At</th>
<th class="px-4 py-2 text-left">Updated At</th>
<th class="px-4 py-2 text-left">Deleted At</th>
<th class="px-4 py-2 text-left">Actions</th>
</tr>
</thead>
<tbody>
@if ($create)
@can('crud')
<tr class="hover:bg-gray-100 dark:hover:bg-gray-700">
<form wire:submit.prevent="create">
@foreach($fillables ? $fillables : $results[0]->getFillable() as $fillable)
<td class="py-2 px-4">
<label for="{{ $results[0]->$fillable ?? $fillable }}"></label>
@if (str_contains($fillable, 'date') !== false)
<x-input
type="date"
class="w-full rounded border-gray-300 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-200"
wire:model="form.{{ $fillable }}"
/>
@error('form.' . $fillable) <span class="text-red-500">{{ str_replace('form.', '', $message) }}</span> @enderror
@elseif (str_contains($fillable, 'image') !== false || str_contains($fillable, 'logo') !== false)
<x-input
type="file"
class="w-full rounded border-gray-300 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-200"
wire:model="form.{{ $fillable }}"
/>
@error('form.' . $fillable) <span
class="text-red-500">{{ str_replace('form.', '', $message) }}</span> @enderror
@elseif (str_contains($fillable, 'time') !== false)
<x-input
type="datetime-local"
min="'{{ date('Y-m-d\TH:i:s', strtotime(now())) }}'"
class="w-full rounded border-gray-300 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-200"
wire:model="form.{{ $fillable }}"
/>
@else
<x-input
type="text"
class="w-full rounded border-gray-300 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-200"
wire:model="form.{{ $fillable }}"
/>
@endif
</td>
@endforeach
<td class="py-2 px-4 grid grid-cols-2 gap-2"></td>
<td class="py-2 px-4"></td>
<td></td>
<td class="py-2 px-4">
<x-button>Add</x-button>
</td>
</form>
</tr>
@endcan
@endif
@foreach ($results as $result)
<tr class="{{ $loop->odd ? 'bg-gray-50 dark:bg-gray-900' : '' }}">
@foreach ($result->getFillable() as $field)
<td class="py-2 px-4">
@if (str_contains($field, 'image') !== false || str_contains($field, 'logo') !== false)
<div>
<img src="{{ $result->$field }}" alt="{{ $result->$field }}"
class="w-20 h-20 rounded-full object-cover">
</div>
@else
{{ $result->$field }}
@endif
</td>
@endforeach
<td class="py-2 px-4">
{{ $result->created_at }}
</td>
<td class="py-2 px-4">
{{ $result->updated_at }}
</td>
<td class="py-2 px-4">
{{ $result->deleted_at }}
</td>
<td class="py-2 px-4">
<div class="flex">
{{ $buttons ?? '' }}
@if ($crud)
<a href="{{'?type=' . $type . '&id=' . $result->id}}" class="mr-2">
<x-secondary-button>
Edit
</x-secondary-button>
</a>
@if ($result->deleted_at)
<x-button wire:click="restore({{ $result->id }})" class="mr-2">
Restore
</x-button>
<x-danger-button wire:click="forceDelete({{ $result->id }})"
wire:confirm="Are you sure you want to force delete {{ ucwords(join(' ', preg_split('/(?=[A-Z])/', $type))) }} {{ $result->key ?? $result->name }}?">
Force Delete
</x-danger-button>
@else
<x-danger-button wire:click="delete({{ $result->id }})">
Delete
</x-danger-button>
@endif
@endif
</div>
</td>
</tr>
@endforeach
@if ($results->count() == 0)
<tr>
<td colspan="4" class="py-2 text-center">No results found.</td>
</tr>
@endif
</tbody>
</table>
<!-- Pagination links -->
<div class="p-4">
@if(isset($results->links))
{{ $results->links }}
@endif
</div>
</div>

Livewire Component

<!-- resources/views/livewire/Post/index.blade.php -->
<div class="container mx-auto px-4">
@if($this->id)
{{ __('Edit') }}
@include('livewire.crud.edit')
@else
{{ __('Create') }}
@include('livewire.crud.create')
{{ $results->links() }}
<div class="mt-4">
<x-table :results="$results" :type="'post'" :create="true" :fillables="$fillables"/>
</div>
@endif
</div>

Zoekfunctie en per page

<!-- resources/views/livewire/Post/index.blade.php -->
<div class="container mx-auto px-4">
@if($this->id)
{{ __('Edit') }}
@include('livewire.crud.edit')
@else
{{ __('Create') }}
@include('livewire.crud.create')
<div class="flex row-auto gap-2">
<label class="w-full block text-sm font-medium text-gray-900 dark:text-gray-400"
for="search">
<input wire:model.live="search" type="text"
class="w-full rounded border-gray-300 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-200"
placeholder="Search...">
</label>
<x-select-per-page/>
<x-button wire:click="clearFilters">
Clear Filters
</x-button>
</div>
{{ $results->links() }}
<div class="mt-4">
<x-table :results="$results" :type="'post'" :create="true" :fillables="$fillables"/>
</div>
@endif
</div>
app/Livewire/Posts.php
<?php
namespace App\Livewire;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
use App\Models\Post as PostModel;
class Post extends Component
{
use WithPagination;
#[Url (as: 'id')]
public $id;
public $form = [];
public $url = '';
#[Url(as: 'q')]
public $search = "";
public $perPage = 10;
public $showDeleted = false;
public function render()
{
if ($this->id && !PostModel::find($this->id)) {
$this->id = '';
}
$query = PostModel::where('title', 'like', '%' . $this->search . '%');
if ($this->showDeleted) {
$query = $query->onlyTrashed();
} else {
$query = $query->withoutTrashed();
}
return view('livewire.Post.index',
[
'results' => $this->id ? PostModel::find($this->id) : $query->paginate($this->perPage),
'fillables' => (new PostModel())->getFillable(),
'url' => current(explode('?', url()->current())),
]);
}
public function create()
{
$Post = new PostModel();
foreach ($this->form as $key => $value) {
$Post->$key = $value;
}
$Post->save();
}
public function update()
{
$Post = PostModel::find($this->id);
foreach ($this->form as $key => $value) {
$Post->$key = $value;
}
$Post->save();
}
public function delete($id)
{
$Post = PostModel::find($id);
$Post->delete();
return redirect()->route(strtolower('Post'));
}
public function clearFilters()
{
$this->reset(['search', 'showDeleted']);
}
public function restore($id)
{
$Post = PostModel::withTrashed()->find($id);
$Post->restore();
return redirect()->route(strtolower('Post'));
}
public function forceDelete($id)
{
$Post = PostModel::withTrashed()->find($id);
$Post->forceDelete();
return redirect()->route(strtolower('Post'));
}
}

Routes

Route::get('/posts', function () {
return view('posts');
})->name('posts');