gorlabs/tailwind-datatables
Laravel package that integrates Yajra DataTables with a Tailwind CSS + Alpine.js UI. Supports server-side processing, search, pagination, sorting, buttons, and rich column customization. Publish config and optional CSS assets for easy theming.
Bu rehber, Laravel projesi oluşturma, Vue ve Inertia.js kurulumu, ayrıca gorlabs/tailwind-datatables paketinin kurulumu ve yapılandırmasını adım adım anlatmaktadır.
İlk olarak yeni bir Laravel projesi oluşturun, lütfen prtoje geliştrimeye bu adımla başlayın her şeyin doğru kurulumla başlaması önemlidir:
laravel new gorlabs-datatable-vue && cd gorlabs-datatable-vue && composer require laravel/breeze --dev && touch resources/js/bootstrap.js && php artisan breeze:install vue && rm resources/js/app.ts && rm vite.config.ts && rm -rf resources/js/pages
package.json karmaşasını düzeltme laravel - vue stack kurulumunda gelen package json dosyası temiz ve düzdelidir, ancak breeze:instal vue komutunu çalıştırdığınız zaman bu scaffold maalesef mevcut düzeni bozmaktadır. zaten kurulum scriptimizle bu eklemenin verdiği zararların büyük bölümü gidelidi şimdi package.json dosyanızı temzilemeniz gerekiyor çünkü bu breeze:installa vue komutru tarafından kirletildi ve çakışmalara nedne olacaktır şu aşamada package.json dosyası şu şekilde olmalıdır
{
"private": true,
"type": "module",
"scripts": {
"build": "vite build",
"build:ssr": "vite build && vite build --ssr",
"dev": "vite",
"format": "prettier --write resources/",
"format:check": "prettier --check resources/",
"lint": "eslint . --fix"
},
"devDependencies": {
"[@eslint](https://github.com/eslint)/js": "^9.19.0",
"[@tailwindcss](https://github.com/tailwindcss)/forms": "^0.5.3",
"[@types](https://github.com/types)/node": "^22.13.5",
"[@vue](https://github.com/vue)/eslint-config-typescript": "^14.3.0",
"autoprefixer": "^10.4.12",
"eslint": "^9.17.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-vue": "^9.32.0",
"postcss": "^8.4.31",
"prettier": "^3.4.2",
"prettier-plugin-organize-imports": "^4.1.0",
"prettier-plugin-tailwindcss": "^0.6.11",
"typescript-eslint": "^8.23.0",
"vue-tsc": "^2.2.4"
},
"dependencies": {
"[@inertiajs](https://github.com/inertiajs)/vue3": "^2.0.0",
"[@tailwindcss](https://github.com/tailwindcss)/vite": "^4.1.1",
"[@vitejs](https://github.com/vitejs)/plugin-vue": "^5.2.1",
"[@vueuse](https://github.com/vueuse)/core": "^12.8.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"concurrently": "^9.0.1",
"laravel-vite-plugin": "^1.0",
"lucide-vue-next": "^0.468.0",
"reka-ui": "^2.2.0",
"tailwind-merge": "^3.2.0",
"tailwindcss": "^4.1.1",
"tw-animate-css": "^1.2.5",
"typescript": "^5.2.2",
"vite": "^6.2.0",
"vue": "^3.5.13",
"ziggy-js": "^2.4.2"
}
}
composer require gorlabs/tailwind-datatables
Paketleri yayımlayın:
composer update
php artisan vendor:publish --tag=tailwind-datatables-views
php artisan vendor:publish --tag=gorlabs-tailwind-datatables-config
php artisan vendor:publish --tag=tailwind-datatables-css
Ardından, resources/js/app.js dosyasının içeriğini aşağıdaki gibi güncelleyin. Bu dosya zaten mevcut olmalıdır.
// resources/js/app.js
import '../css/app.css';
// ********************************************************************
// * DataTables ve İlgili Kütüphaneler (SADECE GLOBAL İHTİYAÇLAR) *
// ********************************************************************
import jQuery from 'jquery';
window.jQuery = jQuery;
window.$ = jQuery;
import 'datatables.net';
import 'datatables.net-buttons';
import 'datatables.net-buttons/js/buttons.html5.js'; // HTML5 export butonları
import 'datatables.net-buttons/js/buttons.print.js'; // Print butonu
import 'datatables.net-buttons/js/buttons.colVis.js'; // Column visibility butonu
import 'datatables.net-responsive';
import JSZip from 'jszip';
import pdfMake from 'pdfmake/build/pdfmake';
import pdfFonts from 'pdfmake/build/vfs_fonts';
window.JSZip = JSZip;
window.pdfMake = pdfMake;
window.pdfMake.vfs = pdfFonts.vfs;
import Swal from 'sweetalert2';
window.Swal = Swal;
import dayjs from 'dayjs';
window.dayjs = dayjs;
import Alpine from 'alpinejs';
import { registerGorlabsDatatablesAlpineComponents } from '../../vendor/gorlabs/tailwind-datatables/resources/js/crud-datatable';
// ********************************************************************
// * Mevcut Inertia.js ve Vue başlatma kodları (DEĞİŞTİRME) *
// ********************************************************************
import { createInertiaApp } from '[@inertiajs](https://github.com/inertiajs)/vue3';
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
import { createApp, h } from 'vue';
import { ZiggyVue } from 'ziggy-js';
const appName = import.meta.env.VITE_APP_NAME || 'Laravel';
createInertiaApp({
title: (title) => `${title} - ${appName}`,
resolve: (name) =>
resolvePageComponent(
`./Pages/${name}.vue`,
import.meta.glob('./Pages/**/*.vue'),
),
setup({ el, App, props, plugin }) {
return createApp({ render: () => h(App, props) })
.use(plugin)
.use(ZiggyVue)
.mount(el);
},
progress: {
color: '#4B5563',
},
});
// Yeni Eklenecek Kısım: Alpine.js'in yüklenmesini bekleyip bileşenleri kaydet
// Bu event listener, Alpine.js CDN'den yüklendiğinde ve hazır olduğunda tetiklenir.
registerGorlabsDatatablesAlpineComponents(Alpine); // Bileşenleri kaydet
Alpine.start();
postcss.config.js içeriğini güncelleyin
export default {
plugins: {
'[@tailwindcss](https://github.com/tailwindcss)/postcss': {},
autoprefixer: {},
},
};
vite.config.js dosyasının içeriğini aşağıdaki gibi güncelleyin. Bu dosya zaten mevcut olmalıdır.
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import vue from '[@vitejs](https://github.com/vitejs)/plugin-vue';
export default defineConfig({
plugins: [
laravel({
input: [
'resources/css/app.css',
'resources/js/app.js'
],
refresh: true,
}),
vue({
template: {
transformAssetUrls: {
base: null,
includeAbsolute: false,
},
},
}),
],
build: {
minify: true, // Minificaiton open
sourcemap: true, // Hata ayıklama için sourcemap oluştur
},
server: {
cors: true,
},
});
X. resources/js/types/global.d.ts içeriğini güncelleyin
//resources/js/types/globals.d.ts
import { AppPageProps } from '@/types/index';
import type Swal from 'sweetalert2'; // type import kullan
import type dayjs from 'dayjs'; // type import kullan
import type Alpine from 'alpinejs'; // type import kullan
import type * as pdfMake from 'pdfmake/build/pdfmake'; // type import kullan
import type * as JSZip from 'jszip'; // type import kullan
// Extend ImportMeta interface for Vite...
declare module 'vite/client' {
interface ImportMetaEnv {
readonly VITE_APP_NAME: string;
[key: string]: string | boolean | undefined;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
readonly glob: <T>(pattern: string) => Record<string, () => Promise<T>>;
}
}
declare module '[@inertiajs](https://github.com/inertiajs)/core' {
interface PageProps extends InertiaPageProps, AppPageProps {}
}
declare module 'vue' {
interface ComponentCustomProperties {
$inertia: typeof Router;
$page: Page;
$headManager: ReturnType<typeof createHeadManager>;
}
}
declare global {
interface Window {
GorlabsDatatables: {
date: (format: string) => (data: any, type: string, row: any) => string;
statusBadge: (publishedText: string, draftText: string) => (data: any, type: string, row: any) => string;
actions: (updateUrlPrefix: string, deleteUrlPrefix: string) => (data: any, type: string, row: any) => string;
// Diğer GorlabsDatatables metotlarını da buraya ekleyin
};
Swal: any; // SweetAlert2 için de benzer şekilde ekleyebilirsiniz
openFormModal: (title: string, url: string, item: any | null) => void; // Global fonksiyon için de
}
}
// ********************************************************************
// * Yeni Eklenecek Kısım: Global Window Objektif Tanımları *
// ********************************************************************
declare global {
interface Window {
Swal: typeof Swal;
dayjs: typeof dayjs;
Alpine: typeof Alpine;
pdfMake: typeof pdfMake;
JSZip: typeof JSZip;
jQuery: JQuery;
$: JQuery;
crudDataTable: (config: any) => any;
}
}
php artisan make:migration create_posts_table
Oluşan dosya yolu: database/migrations/YYYY_MM_DD_HHMMSS_create_posts_table.php (buradaki YYYY_MM_DD_HHMMSS kısmı tarih ve saate göre değişecektir). İçeriğini aşağıdaki gibi güncelleyin:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('content');
$table->timestamp('published_at')->nullable(); // Yayım tarihi
$table->boolean('is_published')->default(false); // Durum (yayınlandı mı?)
$table->string('status')->default('draft'); // Örnek olarak eklendi
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('posts');
}
};
Model Oluşturma Post modelini ve PostFactory.php yi oluşturun ve içeriklerini aşağıdakilerle değiştirin:
php artisan make:model Post -f
Oluşan dosya yolu: app/Models/Post.php. İçeriğini aşağıdaki gibi güncelleyin:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
use HasFactory;
protected $fillable = [
'title',
'content',
'published_at',
'is_published',
'status',
];
protected $casts = [
'is_published' => 'boolean',
'published_at' => 'datetime',
];
}
PostFactory.php içeriği aşağıdaki gib olmalı
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* [@extends](https://github.com/extends) \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Post>
*/
class PostFactory extends Factory
{
/**
* Define the model's default state.
*
* [@return](https://github.com/return) array<string, mixed>
*/
public function definition(): array
{
return [
'title' => fake()->sentence(rand(3, 8)),
'content' => fake()->paragraphs(rand(1, 3), true),
'published_at' => fake()->optional(0.8)->dateTimeThisYear(), // %80 ihtimalle bir tarih verir
'is_published' => fake()->boolean(90), // %90 ihtimalle true
'status' => fake()->randomElement(['draft', 'published', 'archived']),
];
}
}
php artisan make:seeder PostSeeder
PostSeeder.php içeriği
<?php
namespace Database\Seeders;
use App\Models\Post;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class PostSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
// 50 adet sahte Post oluştur
Post::factory()->count(50)->create();
}
}
DatabaseSeeder.php içeriği
<?php
namespace Database\Seeders;
use App\Models\User;
// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Database\Factories\PostFactory;use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*/
public function run(): void
{
// User::factory(10)->create();
User::factory()->create([
'name' => 'Test User',
'email' => 'test@example.com',
]);
$this->call([PostSeeder::class,]);
}
}
DataTable Sınıfı Oluşturma PostsDataTable sınıfını oluşturun ve içeriğini aşağıdakilerle değiştirin:
php artisan datatable:make PostsDataTable --model=Post
Oluşan dosya yolu: app/DataTables/PostsDataTable.php. İçeriğini aşağıdaki gibi güncelleyin:
<?php
namespace App\DataTables;
use App\Models\Post;
use Illuminate\Database\Eloquent\Builder as QueryBuilder;
use Yajra\DataTables\EloquentDataTable;
use Yajra\DataTables\Html\Builder as HtmlBuilder;
use Yajra\DataTables\Html\Button;
use Yajra\DataTables\Html\Column;
use Yajra\DataTables\Services\DataTable;
class PostsDataTable extends DataTable
{
public function query(Post $model): QueryBuilder
{
return $model->newQuery()->select('id', 'title', 'content', 'is_published', 'published_at', 'status', 'created_at', 'updated_at');
}
public function dataTable(QueryBuilder $query): EloquentDataTable
{
return (new EloquentDataTable($query))
->setRowId('id')
->addColumn('select_checkbox', function(Post $post) {
return '';
})
->addColumn('actions', function(Post $post) {
return '';
})
->editColumn('is_published', function(Post $post) {
return $post->is_published ? 1 : 0;
})
->editColumn('published_at', function(Post $post) {
return $post->published_at ? $post->published_at->format('Y-m-d H:i:s') : null;
})
->editColumn('title', function(Post $post) {
$title = $post->title;
if (strlen($title) > 24) {
return substr($title, 0, 24) . ' ...';
}
return $title;
})
->editColumn('content', function(Post $post) {
$content = $post->content;
if (strlen($content) > 34) {
return substr($content, 0, 34) . ' ...';
}
return $content;
});
}
public function html(): HtmlBuilder
{
return $this->builder()
->setTableId('posts-table')
->columns($this->getColumns())
->minifiedAjax()
->orderBy(1)
->select(false)
->buttons([
Button::make('create'),
Button::make('export'),
Button::make('print'),
Button::make('reset'),
Button::make('reload')
]);
}
public function getColumns(): array
{
$columns = [
Column::computed('select_checkbox')
->title('<input type="checkbox" id="select-all-checkbox" class="rounded border-gray-300 text-indigo-600 shadow-sm focus:ring-indigo-500">')
->exportable(false)
->printable(false)
->width(10)
->addClass('text-center')
->orderable(false)
->searchable(false)
->footer('')
->responsivePriority(1),
Column::make('id')->responsivePriority(2),
Column::make('title')->responsivePriority(3),
Column::make('content')->responsivePriority(5),
Column::make('published_at')
->title('Published At')
->width(150)
->responsivePriority(4),
Column::make('is_published')
->title('Published')
->width(100)
->responsivePriority(4),
Column::make('status')->responsivePriority(4),
];
$columns[] = Column::computed('actions')
->title('ACTIONS')
->exportable(false)
->printable(false)
->width(120)
->addClass('text-center')
->orderable(false)
->searchable(false)
->footer('')
->responsivePriority(1);
return $columns;
}
protected function filename(): string
{
return 'Posts_' . date('YmdHis');
}
}
Controller Oluşturma PostController sınıfını oluşturun ve içeriğini aşağıdakilerle değiştirin:
php artisan make:controller PostController
Oluşan dosya yolu: app/Http/Controllers/PostController.php. İçeriğini aşağıdaki gibi güncelleyin:
<?php
namespace App\Http\Controllers;
use App\DataTables\PostsDataTable;
use App\Models\Post;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Inertia\Inertia;
class PostController extends Controller
{
public function index(PostsDataTable $postsDataTable)
{
$columns = $postsDataTable->getColumns();
$formattedColumns = collect($columns)->map(function ($column) {
return [
'data' => $column->data,
'name' => $column->name,
'title' => $column->title,
'orderable' => $column->orderable,
'searchable' => $column->searchable,
'className' => $column->className ?? '',
];
})->toArray();
return Inertia::render('Posts/Index', [
'columns' => $formattedColumns,
]);
}
public function ajaxData(PostsDataTable $dataTable)
{
Log::info('DataTables AJAX isteği parametreleri:', request()->all());
return $dataTable->dataTable($dataTable->query(new Post()))->make(true);
}
public function create()
{
return view('tailwind-datatables::datatables.form', ['post' => new Post()]);
}
public function store(Request $request)
{
$request->validate([
'title' => 'required|string|max:255',
'content' => 'nullable|string',
'is_published' => 'nullable|boolean',
'published_at' => 'nullable|date',
]);
$data = $request->except(['published_at']);
$data['published_at'] = $request->input('published_at') ? now()->parse($request->input('published_at')) : null;
$data['is_published'] = (bool) $request->input('is_published', 0);
$post = Post::create($data);
return response()->json(['success' => 'Post başarıyla oluşturuldu.', 'post' => $post]);
}
public function edit(Post $post)
{
$post->published_at_formatted = $post->published_at ? $post->published_at->format('Y-m-d\TH:i') : '';
return view('tailwind-datatables::datatables.form', compact('post'));
}
public function update(Request $request, Post $post)
{
$request->validate([
'title' => 'required|string|max:255',
'content' => 'nullable|string',
'is_published' => 'boolean|nullable',
'published_at' => 'nullable|date',
]);
$data = $request->except(['published_at']);
$data['is_published'] = (bool) $request->input('is_published', 0);
$data['published_at'] = $request->input('published_at') ? now()->parse($request->input('published_at')) : null;
$post->update($data);
return response()->json(['success' => 'Post başarıyla güncellendi.', 'post' => $post]);
}
public function destroy(Post $post)
{
try {
$post->delete();
return response()->json(['success' => 'Post başarıyla silindi.']);
} catch (\Exception $e) {
Log::error('Post silinirken hata oluştu: ' . $e->getMessage(), ['post_id' => $post->id]);
return response()->json(['error' => 'Post silinirken bir hata oluştu.'], 500);
}
}
}
mkdir -p resources/js/Pages/Posts
touch resources/js/Pages/Posts/Index.vue
Oluşan dosya yolu: resources/js/Pages/Posts/Index.vue. İçeriğini aşağıdaki gibi güncelleyin:
Kod snippet'i
<script setup lang="ts">
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout.vue';
import { Head } from '[@inertiajs](https://github.com/inertiajs)/vue3';
import { onMounted, ref } from 'vue';
// Backend'den gelen columns prop'unu tanımla
const props = defineProps<{
columns: Array<{
data: string;
name: string;
title: string;
orderable: boolean;
searchable: boolean;
className?: string; // className opsiyonel olabilir
}>;
}>();
console.log('Props Columns:', props.columns);
// DataTables'ın HTML yapısı için bir ref oluştur
const dataTableContainer = ref<HTMLElement | null>(null);
// crudDataTable config'i props'tan gelen kolonları kullanarak tanımlıyoruz.
const datatableConfig = {
datatableId: 'posts-table',
ajaxUrl: route('posts.data'),
columns: props.columns, // Kolonları props'tan alıyoruz!
perPage: 10,
perPageSelect: [10, 25, 50, 100, -1],
addNewButtonText: 'Add New Post',
addEditUrl: route('posts.create'),
updateUrlPrefix: route('posts.update', ''),
deleteUrlPrefix: route('posts.destroy', ''),
initialFormState: {},
responsive: true, // Responsive özelliği burada true olarak ayarlı
};
// DataTables instance'ını saklamak için bir ref
const dataTableInstance = ref<any>(null);
const $ = window.$ as any;
onMounted(() => {
// jQuery ve DataTabl...
How can I help you explore Laravel packages today?