For AI Agents & Developers: Complete technical architecture and implementation guide
| Property | Value |
|---|---|
| Package | artflow-studio/table |
| Type | Livewire Component (Trait-Based) |
| Version | 1.5.2 |
| Developer Site | https://artflow.pk |
| Location | vendor/artflow-studio/table/src/ |
| Main Component | DatatableTrait (Livewire Component) |
| Blade Directive | [@livewire](https://github.com/livewire)('aftable', [...]) |
| Registration | TableServiceProvider::class |
| Auto-Discovery | β Yes (Laravel 5.5+) |
[@livewire](https://github.com/livewire)('aftable', [...])
β
DatatableTrait.php (Main Livewire Component)
βββ Uses 18 Core Traits
βββ Mounted Properties
βββ Reactive Methods
βββ Blade Rendering
β
aftable.blade.php (Template)
βββ Search Box
βββ Column Headers
βββ Table Rows
βββ Pagination
βββ Export/Filters
app/Http/Livewire/
βββ DatatableTrait.php (Main Component)
vendor/artflow-studio/table/src/Traits/
βββ Core/
β βββ HasDataProcessing.php # Data transformation
β βββ HasQueryBuilding.php # Query construction
β βββ HasPagination.php # Pagination logic
β βββ HasSearch.php # Search functionality
β βββ HasSorting.php # Sort handling
β βββ HasAutoOptimization.php # Auto-detection
β βββ HasCountAggregations.php # N+1 prevention
β βββ HasRelationships.php # Relation handling
β βββ HasColumnValidation.php # Column checking
β βββ HasColumnInitialization.php # Column setup
β βββ HasUtilities.php # Helper methods
β
βββ UI/
β βββ HasSortingUI.php # Sort UI elements
β βββ HasColumnVisibility.php # Show/hide columns
β βββ HasActions.php # Action buttons
β βββ HasExport.php # Export features
β
βββ Advanced/
βββ HasAdvancedFiltering.php # Complex filters
βββ HasPerformanceOptimization.php # Cache handling
βββ HasSessionManagement.php # Session isolation
[@livewire](https://github.com/livewire)('aftable', [
'model' => 'App\Models\Item',
'columns' => [
['key' => 'title', 'label' => 'Title'],
['key' => 'category_name', 'label' => 'Category', 'relation' => 'category:name'],
['key' => 'subitems_count', 'label' => 'Sub-items'],
],
'records' => 50,
])
Component Mount
mount() called
β initializeComponent()
β initializeColumns()
β setDefaultSort()
Data Initialization
autoOptimizeColumns()
β Detects relations
β Detects count columns
β Enables sorting/searching
autoDetectCountAggregations()
β Finds _count columns
β Extracts relation names
β Queues for withCount()
Query Building
buildUnifiedQuery()
β applyEagerLoading()
β applyCountAggregations()
β applySearch()
β applySorting()
β paginate()
Rendering
render()
β Load aftable.blade.php
β Pass $records, $columns, etc.
β Display to user
Purpose: Automatically detect and configure columns
// What it does:
protected function autoOptimizeColumns(): void
{
foreach ($this->columns as &$column) {
// Detect if it's a relation column
if (isset($column['relation'])) {
$relation = extractRelationName($column['relation']);
$this->relationsToLoad[] = $relation;
$column['searchable'] = true; // Relations are searchable
$column['sortable'] = true;
}
// Detect if it's a count column
if (str_ends_with($column['key'], '_count')) {
$relation = str_replace('_count', '', $column['key']);
$this->countAggregations[$relation] = $relation;
$column['sortable'] = true; // Counts are sortable
}
// Default text columns are searchable
if (isTextColumn($column)) {
$column['searchable'] = true;
}
}
}
Result: All optimization decisions made automatically - users don't need to configure anything!
Purpose: Prevent N+1 queries when showing relationship counts
// What it does:
protected function autoDetectCountAggregations(): void
{
foreach ($this->columns as $column) {
if (str_ends_with($column['key'], '_count')) {
$relation = str_replace('_count', '', $column['key']);
// Verify relation exists on model
if (hasRelation($this->model, $relation)) {
$this->countAggregations[$relation] = true;
}
}
}
}
protected function applyCountAggregations(Builder $query): Builder
{
if (!empty($this->countAggregations)) {
return $query->withCount(array_keys($this->countAggregations));
}
return $query;
}
Result: Single query loads all counts! No N+1!
Purpose: Display sort indicators and manage sort state
// What it does:
public function getSortIcon(string $columnKey): string
{
if ($this->sortBy === $columnKey) {
return $this->sortDirection === 'asc' ? 'β' : 'β';
}
return ''; // No icon if not sorted
}
public function updateSort(string $columnKey): void
{
if ($this->sortBy === $columnKey) {
// Toggle direction
$this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc';
} else {
// New sort column
$this->sortBy = $columnKey;
$this->sortDirection = 'asc';
}
$this->resetPage();
}
Result: Beautiful sort UI with up/down arrows that users understand!
Purpose: Build optimized Eloquent query
// What it does:
protected function buildUnifiedQuery(): Builder
{
$query = $this->model::query();
// Step 1: Eager load relations (prevent N+1)
foreach ($this->relationsToLoad as $relation) {
$query->with($relation);
}
// Step 2: Add count aggregations (show counts efficiently)
$query = $this->applyCountAggregations($query);
// Step 3: Apply search filters
$query = $this->applySearch($query);
// Step 4: Apply sorting
$query = $this->applySorting($query);
// Step 5: Paginate results
return $query;
}
Result: Single optimized query that handles everything!
Purpose: Search across multiple columns efficiently
// What it does:
protected function applySearch(Builder $query): Builder
{
if (empty($this->search)) {
return $query;
}
return $query->where(function ($q) {
foreach ($this->searchableColumns as $column) {
if ($column['type'] === 'relation') {
// Search in related table
$relation = extractRelationName($column['relation']);
$field = extractColumnName($column['relation']);
$q->orWhereHas($relation, fn($sub) =>
$sub->where($field, 'like', "%{$this->search}%")
);
} else {
// Search in main table
$q->orWhere($column['key'], 'like', "%{$this->search}%");
}
}
});
}
Result: Smart search that works on all column types!
USER INTERACTION
β
ββββββββββββββββββββββββββββββββββββ
β User Action (sort/search/page) β
ββββββββββββββββββββββββββββββββββββ€
β - Click column header β sortBy() β
β - Type search β search() β
β - Click page β gotoPage() β
ββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββββββββββββ
β Livewire Reactivity β
ββββββββββββββββββββββββββββββββββββ€
β wire:click, wire:model, etc. β
β Triggers component method β
ββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββββββββββββ
β Component Method Executes β
ββββββββββββββββββββββββββββββββββββ€
β sortBy = 'name' β
β sortDirection = 'asc' β
β resetPage() β page = 1 β
ββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββββββββββββ
β Livewire Renders β
ββββββββββββββββββββββββββββββββββββ€
β render() method called β
β Blade template updated β
ββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββββββββββββ
β Build Query (OPTIMIZE STEP) β
ββββββββββββββββββββββββββββββββββββ€
β 1. with(relations) [eager]β
β 2. withCount(counts) [N+1] β
β 3. where(search) [find] β
β 4. orderBy(sort) [order]β
β 5. paginate() [page] β
ββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββββββββββββ
β Execute Single Query β
ββββββββββββββββββββββββββββββββββββ€
β SELECT ... FROM products β
β with category, brand β
β withCount variants, reviews β
β WHERE name LIKE '%search%' β
β ORDER BY created_at ASC β
β LIMIT 50 β
ββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββββββββββββ
β Render Table β
ββββββββββββββββββββββββββββββββββββ€
β Display sorted/filtered results β
β Show page numbers β
β Highlight current sort column β
ββββββββββββββββββββββββββββββββββββ
['key' => 'title', 'label' => 'Item Name']
['key' => 'category_name', 'label' => 'Category', 'relation' => 'category:name']
category relationcategory.namecategory.name['key' => 'subitems_count', 'label' => 'Sub-items']
withCount('subitems')[
'key' => 'actions',
'label' => 'Actions',
'actions' => [
['type' => 'button', 'label' => 'Edit', 'href' => '/items/{id}/edit'],
['type' => 'button', 'label' => 'Delete', 'href' => '/items/{id}', 'method' => 'DELETE'],
]
]
[
'key' => 'amount',
'label' => 'Amount',
'sortable' => true, # Allow sorting (auto-enabled)
'searchable' => true, # Allow searching (auto-enabled)
'hidden' => false, # Show/hide column
'value_type' => 'price', # Format: price, date, boolean, etc.
'class' => 'text-right', # Cell CSS classes
'width' => '120px', # Column width
'raw' => false, # Escape HTML (false = escape)
]
Before (N+1 Problem):
Initial Query: SELECT * FROM items β 50 rows
For Each Row: SELECT * FROM categories WHERE id = ? β 50 queries!
TOTAL: 51 queries! β
After (Eager Loading):
Query: SELECT * FROM items WITH categories β 1 query β
Result: Items have categories pre-loaded
TOTAL: 1 query! β
Code:
// Automatically done:
$query->with(['category', 'department', 'supplier']);
Before (N+1 Problem):
Initial Query: SELECT * FROM items β 50 rows
For Each Row: SELECT COUNT(*) FROM subitems WHERE item_id = ? β 50 queries!
TOTAL: 51 queries! β
After (withCount):
Query: SELECT * FROM items, COUNT(*) as subitems_count β 1 query β
Result: Each item has subitems_count pre-
calculated
TOTAL: 1 query! β
Code:
// Automatically done:
$query->withCount(['subitems', 'related', 'attachments']);
Code:
// Export processes in chunks:
$query->chunk(500, function($records) {
foreach ($records as $record) {
// Process each record
$this->exportRow($record);
}
});
// Handles 1M+ records without memory crash β
Code:
// Each session stores its own state:
$sessionKey = 'aftable_' . md5($this->componentId);
session([$sessionKey => $this->state]);
// Other users can't see another user's table state
// Test that relations are auto-detected
public function testAutoDetectsRelations()
{
$component = Livewire::test(DatatableTrait::class, [
'model' => 'App\Models\Product',
'columns' => [
['key' => 'category_name', 'relation' => 'category:name'],
],
]);
$this->assertContains('category', $component->instance()->relationsToLoad);
}
// Test that _count columns work
public function testCountAggregations()
{
$component = Livewire::test(DatatableTrait::class, [
'model' => 'App\Models\Product',
'columns' => [
['key' => 'variants_count', 'label' => 'Variants'],
],
]);
// Query should include withCount
$this->assertStringContainsString('count', $component->instance()->buildUnifiedQuery()->toSql());
}
// Test search filtering
public function testSearch()
{
$component = Livewire::test(DatatableTrait::class, [
'model' => 'App\Models\Product',
'columns' => [
['key' => 'name', 'label' => 'Name'],
],
]);
$component->set('search', 'Test Product');
$results = $component->instance()->records;
$this->assertTrue($results->every(fn($r) => str_contains($r->name, 'Test Product')));
}
// Test sort functionality
public function testSorting()
{
$component = Livewire::test(DatatableTrait::class, [
'model' => 'App\Models\Product',
'columns' => [
['key' => 'name', 'label' => 'Name'],
],
]);
$component->call('updateSort', 'name');
$this->assertEquals('name', $component->instance()->sortBy);
$this->assertEquals('asc', $component->instance()->sortDirection);
}
// Enable query logging in tinker:
DB::enableQueryLog();
// Then render component...
// Check queries:
echo DB::getQueryLog();
Expected Output:
Query 1: SELECT * FROM products WITH category, brand, supplier withCount(variants)...
Total: 1 query β
// In tinker:
$component = app(DatatableTrait::class);
$component->model = 'App\Models\Product';
$component->columns = [
['key' => 'category_name', 'relation' => 'category:name'],
['key' => 'variants_count', 'label' => 'Variants'],
];
$component->autoOptimizeColumns();
dd([
'relationsToLoad' => $component->relationsToLoad,
'countAggregations' => $component->countAggregations,
'columns' => $component->columns,
]);
<!-- In aftable.blade.php template -->
<pre>{{ print_r($columns, true) }}</pre>
<pre>{{ print_r($records, true) }}</pre>
<pre>{{ "Queries: " . DB::getQueryLog() }}</pre>
// In your component or trait:
protected function formatColumnValue($value, $column)
{
return match($column['value_type'] ?? null) {
'price' => '$' . number_format($value, 2),
'date' => \Carbon\Carbon::parse($value)->format('M d, Y'),
'boolean' => $value ? 'β' : 'β',
'custom' => $this->customFormat($value, $column),
default => $value,
};
}
// In your component:
public function addCustomFilter(string $key, \Closure $callback)
{
$this->customFilters[$key] = $callback;
}
// Then in query building:
protected function applyCustomFilters(Builder $query): Builder
{
foreach ($this->customFilters as $callback) {
$query = $callback($query);
}
return $query;
}
// In column config:
['key' => 'custom_action', 'raw' => 'handleCustomAction()']
// Add method:
public function handleCustomAction()
{
// Your logic here
}
DatatableTrait.php
βββ HasDataProcessing
βββ HasQueryBuilding
β βββ HasSearch
β βββ HasSorting
β βββ HasPagination
β βββ HasAutoOptimization
β βββ HasCountAggregations
β βββ HasRelationships
βββ HasColumnInitialization
βββ HasColumnValidation
βββ HasUtilities
βββ HasSortingUI
βββ HasColumnVisibility
βββ HasActions
βββ HasExport
βββ HasAdvancedFiltering
βββ HasPerformanceOptimization
βββ HasSessionManagement
Order Matters: Traits are loaded in dependency order to avoid conflicts!
[@livewire](https://github.com/livewire)('aftable', [...])'model' => 'App\Models\Product''relation' => 'relationName:columnName' formatcolumnName_count for count aggregationsvendor/artflow-studio/table/
βββ src/
β βββ Http/
β β βββ Livewire/
β β βββ DatatableTrait.php [Main Component]
β βββ Traits/
β β βββ Core/
β β β βββ HasDataProcessing.php
β β β βββ HasQueryBuilding.php
β β β βββ HasPagination.php
β β β βββ HasSearch.php
β β β βββ HasSorting.php
β β β βββ HasAutoOptimization.php
β β β βββ HasCountAggregations.php
β β β βββ HasRelationships.php
β β β βββ HasColumnValidation.php
β β β βββ HasColumnInitialization.php
β β β βββ HasUtilities.php
β β βββ UI/
β β β βββ HasSortingUI.php
β β β βββ HasColumnVisibility.php
β β β βββ HasActions.php
β β β βββ HasExport.php
β β βββ Advanced/
β β βββ HasAdvancedFiltering.php
β β βββ HasPerformanceOptimization.php
β β βββ HasSessionManagement.php
β βββ resources/
β β βββ views/
β β βββ aftable.blade.php [Template]
β βββ Providers/
β βββ TableServiceProvider.php [Registration]
βββ composer.json
βββ README.md
βββ DOCUMENTATION_FILES (guides, references, etc.)
AI_USAGE_GUIDE.md (non-technical)<!-- Only show if user can edit -->
[@if](https://github.com/if)(auth()->user()->can('edit', \App\Models\Product::class))
[@livewire](https://github.com/livewire)('aftable', [...])
[@endif](https://github.com/endif)
[@livewire](https://github.com/livewire)('aftable', [
'model' => 'App\Models\Product',
'customQuery' => Product::withTrashed(), # Include deleted
])
[@livewire](https://github.com/livewire)('aftable', [
'model' => 'App\Models\Product',
'customQuery' => Product::active(), # Custom scope
])
[@livewire](https://github.com/livewire)('aftable', [
'model' => 'App\Models\Product',
'customQuery' => Product::whereTenantId(auth()->user()->tenant_id),
])
Version: v1.6 Optimized
Last Updated: November 23, 2025
For: AI Agents, Developers, Architects
Status: Production Ready β
How can I help you explore Laravel packages today?