Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 12 additions & 62 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Ginkelsoft DataTables is a flexible and easy-to-use package for managing tabular
## Requirements

- **PHP 8.2+**
- **Laravel 10.0+** 
- **Laravel 10.0+**
- **Livewire** *(Optional, only if you need AJAX-driven data tables.)*

---
Expand All @@ -30,7 +30,7 @@ Ginkelsoft DataTables is a flexible and easy-to-use package for managing tabular
1. **Require the package**:

```bash
composer require ginkelsoft/datatables:dev-main
composer require ginkelsoft/datatables
```

2. **Publish the package views** (optional) for customization:
Expand All @@ -41,60 +41,6 @@ Ginkelsoft DataTables is a flexible and easy-to-use package for managing tabular

---

## Usage (Without Livewire)

For a traditional server-rendered app:

```php
use Ginkelsoft\DataTables\DataTable;
use Ginkelsoft\DataTables\Column;
use App\Models\User;

public function index()
{
$query = User::query();

$datatable = (new DataTable($query))
->setColumns([
Column::make('id', 'ID'),
Column::make('name', 'Name'),
Column::make('email', 'Email'),
])
->setPerPage(10);

$rows = $datatable->getRows();

return view('users.index', compact('rows'));
}
```

And in `resources/views/users/index.blade.php`:

```blade
<table>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Email</th>
</tr>
</thead>
<tbody>
@foreach($rows as $row)
<tr>
<td>{{ $row->id }}</td>
<td>{{ $row->name }}</td>
<td>{{ $row->email }}</td>
</tr>
@endforeach
</tbody>
</table>

{{ $rows->links() }}
```

---

## Usage (With Livewire)

If you prefer an **AJAX-driven** workflow with real-time sorting, searching, and pagination:
Expand All @@ -104,8 +50,8 @@ If you prefer an **AJAX-driven** workflow with real-time sorting, searching, and
model="App\\Models\\User"
:columns="['id', 'name', 'email']"
:actions="[
['label' => 'Edit', 'route' => 'users.edit'],
['label' => 'Delete', 'route' => 'users.destroy']
['label' => 'Edit', 'route' => 'users.edit', 'class' => 'bg-blue-500 hover:bg-blue-600 text-white px-2 py-1 rounded', 'style' => 'margin-right: 5px;'],
['label' => 'Delete', 'route' => 'users.destroy', 'class' => 'bg-red-500 hover:bg-red-600 text-white px-2 py-1 rounded']
]"
:bulkActions="[
'delete' => ['label' => 'Delete', 'route' => 'users.bulk.delete'],
Expand All @@ -127,18 +73,20 @@ You can now:

### Row Actions

Specify row-level actions in your Blade:
Actions now support custom **classes** and **styles** for better UI customization:

```blade
:actions="[
['label' => 'Edit', 'route' => 'users.edit'],
['label' => 'Delete', 'route' => 'users.destroy']
['label' => 'Edit', 'route' => 'users.edit', 'class' => 'bg-blue-500 hover:bg-blue-600 text-white px-2 py-1 rounded', 'style' => 'margin-right: 5px;'],
['label' => 'Delete', 'route' => 'users.destroy', 'class' => 'bg-red-500 hover:bg-red-600 text-white px-2 py-1 rounded']
]"
```

These actions will be rendered as buttons with the specified styles.

### Bulk Actions

For bulk actions (like deleting multiple rows) set `:bulkActions`:
For bulk actions (like deleting multiple rows), define them as follows:

```blade
:bulkActions="[
Expand All @@ -157,6 +105,8 @@ When multiple rows are selected, the component redirects to the specified route
- **Filter Class** for custom filters (status, categories, etc.).
- **Sorting Class** for ascending/descending ordering.
- **Select All** (with confirmation modal) to choose between only visible rows or all rows.
- **Custom Actions** now support custom classes and inline styles.
- **Prevent row selection when clicking an action button**, ensuring that clicking an action does not check the row checkbox.

---

Expand Down
10 changes: 10 additions & 0 deletions resources/views/vendor/datatables/actions.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<div class="flex items-center space-x-2">
@foreach($actions as $action)
<a href="{{ route($action['route'], $row->id) }}"
class="{{ $action['class'] ?? 'px-2 py-1 text-sm font-medium text-white bg-blue-500 rounded hover:bg-blue-600' }}"
style="{{ $action['style'] ?? '' }}"
onclick="event.stopPropagation();">
{{ $action['label'] }}
</a>
@endforeach
</div>
234 changes: 186 additions & 48 deletions resources/views/vendor/datatables/datatable.blade.php
Original file line number Diff line number Diff line change
@@ -1,53 +1,191 @@
<div>
{{-- 🔍 Zoekbalk --}}
<div class="flex items-center mb-4">
<input type="text" wire:model.debounce.500ms="search" placeholder="Zoeken..." class="border px-3 py-1 rounded">
<select wire:model="perPage" class="ml-2 border px-2 py-1 rounded">
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
</select>
</div>
<div wire:loading.class="opacity-50">
<div class="bg-white shadow-md rounded-lg p-6">
<div class="flex justify-between items-center mb-4">
{{-- 🔍 Zoekbalk --}}
<div class="flex justify-between items-center mb-4">
{{-- 🔍 Zoekbalk met knop --}}
<div class="flex items-center gap-2">
<input type="text" wire:model="search"
placeholder="Zoekterm..."
class="border border-gray-300 rounded px-3 py-1 text-sm w-64">

{{-- Zoekknop --}}
<button wire:click="applySearch"
class="bg-blue-500 hover:bg-blue-600 text-white px-3 py-1 rounded text-sm">
Zoeken
</button>

{{-- Resetknop --}}
<button wire:click="resetSearch"
class="bg-gray-300 hover:bg-gray-400 text-gray-700 px-3 py-1 rounded text-sm">
Reset
</button>
</div>
</div>


{{-- Selectie aantal resultaten per pagina --}}
<div class="flex items-center gap-2">
<label for="perPage" class="text-sm text-gray-600">Resultaten per pagina:</label>
<select wire:change="updatePerPage($event.target.value)" id="perPage"
class="border border-gray-300 rounded px-3 py-1 text-sm w-24">
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
</div>
</div>

<div class="flex justify-between items-center mb-4">
{{-- ✅ Bulkactie dropdown - Alleen tonen als er rijen geselecteerd zijn --}}
<div class="flex items-center gap-2" wire:key="bulk-actions">
@if(count($selectedRows) > 0)
<select wire:model="bulkAction"
class="border border-gray-300 rounded px-3 py-1 text-sm w-48">
<option value="">-- Kies een actie --</option>
@foreach($bulkActions as $key => $action)
<option value="{{ $key }}">{{ $action['label'] }}</option>
@endforeach
</select>

<button wire:click="executeBulkAction"
class="bg-blue-500 hover:bg-blue-600 text-white px-3 py-1 rounded text-sm">
Uitvoeren
</button>
@endif
</div>
</div>

{{-- 📊 Datatable --}}
<table class="border-collapse border border-gray-300 w-full">
<thead>
<tr>
@foreach($columns as $column)
<th class="border border-gray-300 p-2">
<a wire:click="sortBy('{{ $column }}')" class="cursor-pointer">
{{ ucfirst($column) }}
@if ($sortColumn === $column)
<span>{{ $sortDirection === 'asc' ? '▲' : '▼' }}</span>
@endif
</a>
</th>
@endforeach
@if (!empty($actions))
<th class="border border-gray-300 p-2">Acties</th>
@endif
</tr>
</thead>
<tbody>
@foreach($rows as $row)
<tr>
@foreach($columns as $column)
<td class="border border-gray-300 p-2">{{ $row->$column }}</td>
@endforeach
@if (!empty($actions))
<td class="border border-gray-300 p-2">
@foreach($actions as $action)
<a href="{{ route($action['route'], ['id' => $row->id]) }}" class="text-blue-500">{{ $action['label'] }}</a>
<div wire:loading.class="opacity-50">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200 border border-gray-300 rounded-lg">
<thead class="bg-gray-100">
<tr>
{{-- "Selecteer alles" checkbox --}}
<th class="px-4 py-3 text-left text-sm font-semibold text-gray-700 border">
<input type="checkbox" wire:click="toggleSelectAll"
class="h-4 w-4 text-blue-500 border-gray-300 rounded">
</th>
@foreach($columns as $column)
@if(!in_array($column, $hiddenColumns))
{{-- 🟢 Alleen tonen als niet hidden --}}
<th class="px-4 py-3 text-left text-sm font-semibold text-gray-700 border cursor-pointer"
wire:click="sortBy('{{ $column }}')">
<div class="flex items-center">
{{ $columnLabels[$column] ?? ucfirst(str_replace('_', ' ', $column)) }}
@if ($sortColumn === $column)
<span class="ml-2">
@if ($sortDirection === 'asc')
@else
@endif
</span>
@endif
</div>
</th>
@endif
@endforeach
</td>
<th class="px-4 py-3 text-left text-sm font-semibold text-gray-700 border">Acties</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
@foreach($rows as $row)
<tr class="hover:bg-gray-100 transition cursor-pointer"
wire:click.prevent="toggleRowSelection({{ $row->id }})">
<td class="px-4 py-3 border">
<input type="checkbox" wire:model="selectedRows"
value="{{ $row->id }}"
class="h-4 w-4 text-blue-500 border-gray-300 rounded"
onclick="event.stopPropagation();">
</td>

@foreach($columns as $column)
@if(!in_array($column, $hiddenColumns))
{{-- 🟢 Alleen tonen als niet hidden --}}
<td class="px-4 py-3 border text-sm text-gray-700">
{{ $row->$column }}
</td>
@endif
@endforeach

<td class="px-4 py-3 border">
@includeIf($actionsView, ['row' => $row])
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>

{{-- Paginering --}}
<div class="mt-4 flex justify-between items-center">
<span class="text-sm text-gray-600">
Pagina {{ $rows->currentPage() }} van {{ $rows->lastPage() }} - {{ $rows->total() }} resultaten
</span>

<div class="flex gap-2">
{{-- Vorige knop --}}
<button wire:click="previousPage"
class="px-4 py-2 rounded {{ $rows->onFirstPage() ? 'bg-gray-200 text-gray-400 cursor-not-allowed' : 'bg-gray-100 hover:bg-gray-200 text-gray-700' }}"
{{ $rows->onFirstPage() ? 'disabled' : '' }}>
Vorige
</button>

{{-- ✅ Als er 10 of minder pagina’s zijn: toon paginanummers als knoppen --}}
@if ($rows->lastPage() <= 10)
@for ($page = 1; $page <= $rows->lastPage(); $page++)
<button wire:click="gotoPage({{ $page }})"
class="px-4 py-2 rounded {{ $rows->currentPage() == $page ? 'bg-blue-500 text-white' : 'bg-gray-100 hover:bg-gray-200 text-gray-700' }}">
{{ $page }}
</button>
@endfor
@else
{{-- ✅ Meer dan 10 pagina’s: gebruik een dropdown --}}
<select wire:change="gotoPage($event.target.value)"
class="border border-gray-300 rounded px-3 py-1 text-sm w-16">
@for ($page = 1; $page <= $rows->lastPage(); $page++)
<option value="{{ $page }}" {{ $rows->currentPage() == $page ? 'selected' : '' }}>
{{ $page }}
</option>
@endfor
</select>
@endif
</tr>
@endforeach
</tbody>
</table>

{{-- Paginering --}}
<div class="mt-4">
{{ $rows->links() }}

{{-- Volgende knop --}}
<button wire:click="nextPage"
class="px-4 py-2 rounded {{ $rows->hasMorePages() ? 'bg-gray-100 hover:bg-gray-200 text-gray-700' : 'bg-gray-200 text-gray-400 cursor-not-allowed' }}"
{{ $rows->hasMorePages() ? '' : 'disabled' }}>
Volgende
</button>
</div>
</div>
</div>

@if($showSelectAllModal)
<div class="fixed inset-0 bg-gray-900 bg-opacity-50 flex justify-center items-center z-50">
<div class="bg-white p-6 rounded-lg shadow-lg w-96">
<h2 class="text-lg font-semibold mb-4">Selecteer alle rijen?</h2>
<p class="text-gray-700 text-sm mb-4">
Wil je alleen de zichtbare rijen op deze pagina selecteren, of alle rijen in de database?
</p>
<div class="flex justify-end gap-3">
<button wire:click="confirmSelectAll('visible')"
class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded">
Alleen zichtbare
</button>
<button wire:click="confirmSelectAll('all')"
class="bg-green-500 hover:bg-green-600 text-white px-4 py-2 rounded">
Alle rijen
</button>
<button wire:click="cancelSelectAll"
class="bg-gray-300 hover:bg-gray-400 text-gray-700 px-4 py-2 rounded">
Annuleren
</button>
</div>
</div>
</div>
@endif
</div>
7 changes: 3 additions & 4 deletions src/DataTableServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ class DataTableServiceProvider extends ServiceProvider
*/
public function register(): void
{
// Load package views
$this->loadViewsFrom(__DIR__ . '/Resources/views', 'datatable');
// Load package views from the correct
$this->loadViewsFrom(__DIR__ . '/../resources/views/vendor/datatables', 'datatable');
}

/**
Expand All @@ -32,9 +32,8 @@ public function boot(): void
{
// Publish views to allow customization
$this->publishes([
__DIR__ . '/Resources/views' => resource_path('views/vendor/datatables'),
__DIR__ . '/../resources/views' => resource_path('views/vendor/datatables'),
], 'views');

// Register the Livewire component for the DataTable
Livewire::component('datatable', \Ginkelsoft\DataTables\Livewire\DataTableComponent::class);
}
Expand Down
Loading