No content for this heading.
Laravel Simple Data Tables And Forms Documentation
Lightweight dynamic table and form components for Laravel Livewire.
Documentation
Project Info
A lightweight, easy-to-use Laravel package for creating interactive data tables and dynamic forms with Livewire integration.
Overview
Laravel Simple Datatables And Forms provides two powerful components for your Laravel applications:
π Data Tables
Create interactive, feature-rich data tables with minimal code. Includes advanced search, sorting, filtering, grouping, and export capabilities.
π Dynamic Forms
Build dynamic forms with fluent API, multiple field types, validation, and model binding support.
Key Features
- π Advanced Search - Debounced search with minimum character requirements
- π Column Sorting - Click-to-sort functionality for table columns
- π§Ή Filtering & Grouping - Multiple filter types with advanced grouping options
- π€ Data Export - Export to CSV, Excel, and PDF formats
- π Dynamic Forms - Fluent features for building complex forms
- π§ Multiple Field Types - Input, Select, Checkbox, Toggle, Textarea, and more
- β Validation Integration - Built-in Laravel validation support
- π± Responsive Design - Mobile-friendly components
- β‘ Performance Optimized - Intelligent caching and query optimization
- π Security Features - CSRF protection and input sanitization
- π§© Seamless Livewire Integration - Built specifically for Livewire 3.x
Requirements
- PHP 8.2 or higher (compatible with PHP 8.3 and 8.4)
- Laravel 10.x or higher (compatible with Laravel 11.x and 12.x)
- Livewire 3.x or higher
Quick Start
Installation
composer require milenmk/laravel-simple-datatables-and-forms
Publish Configurations
php artisan vendor:publish --tag=laravel-simple-datatables-and-forms-config
Publish Assets
php artisan simple-datatables-and-forms:publish-assets
Include Assets in Your Layout
<head>
@SimpleDatatablesStyle
</head>
<body>
<!-- Your content -->
@SimpleDatatablesScript
</body>
Create Your First Data Table
Generate a table component:
php artisan make:milenmk-datatable UserList User --generate
Or create manually:
use Milenmk\LaravelSimpleDatatablesAndForms\Traits\HasTable;
class UserList extends Component
{
use HasTable;
public function table(Table $table): Table
{
return $table
->query(User::query())
->schema([
TextColumn::make('name')->searchable()->sortable(),
TextColumn::make('email')->searchable(),
ToggleColumn::make('is_active')->label('Active'),
])
->striped()
->paginate();
}
}
Create Your First Form
Generate a form component:
php artisan make:milenmk-form CreateUser create User --generate
Or create manually:
use Milenmk\LaravelSimpleDatatablesAndForms\Traits\HasForm;
class CreateUser extends Component
{
use HasForm;
public function form(Form $form): Form
{
return $form->model(User::class)->schema([
InputField::make('name')->required(),
InputField::make('email')->email()->required(),
SelectField::make('role')
->options(['admin' => 'Admin', 'user' => 'User'])
->required(),
]);
}
}
Tailwind CSS Integration
Add the package views to your Tailwind configuration:
// tailwind.config.js
module.exports = {
content: [
// ... your existing content paths
'./vendor/milenmk/laravel-simple-datatables-and-forms/resources/views/**/*.blade.php',
],
// ... rest of your configuration
};
Contributing
Contributions are welcome! Please visit our GitHub repository to:
- Report bugs or request features
- Submit pull requests
- Browse existing issues
- Join discussions
Support
- GitHub Issues - Bug reports and feature requests
- Email Support - Direct support for complex issues
License
This package is licensed under the MIT License.
Disclaimer
This package is provided "as is", without warranty of any kind. Please thoroughly test in your environment before deploying to production.
Welcome to the comprehensive documentation for Laravel Simple Datatables And Forms. This package provides powerful data table and dynamic form functionality for Laravel applications with seamless Livewire integration.
π Getting Started
New to the Package?
- Installation & Setup - Start here for installation and basic configuration
- Table Quick Start - Create your first data table in minutes
- Form Quick Start - Build your first dynamic form quickly
Quick Reference
Both getting started guides include comprehensive quick reference sections with cheat sheets and common patterns.
π Data Tables Documentation
Comprehensive guides for creating and customizing interactive data tables:
Core Concepts
- Getting Started - Your first table with step-by-step instructions
- Configuration - Complete configuration guide and global options
- Column Types - Complete reference for all available column types
Advanced Features
- Search & Filtering - Advanced search functionality and optimization
- Filters and Grouping - Complex filtering and data grouping
- Actions - Action buttons and Livewire method integration
- Data Export - CSV, Excel, and PDF export capabilities
- Pagination - Pagination setup and customization
- Caching - Performance optimization with intelligent caching
π Dynamic Forms Documentation
Comprehensive guides for creating dynamic forms with validation and model binding:
Core Concepts
- Getting Started - Your first form with step-by-step instructions
- Field Types - Complete reference for all available field types
Advanced Features
- Form Validation - Validation rules, techniques, and best practices
- Form Sections - Organizing forms with sections and responsive layouts
- Model Binding - Advanced model integration and data handling
- Searchable Select - Dynamic and searchable select fields
- Relationship Select - Working with Eloquent relationships
- Form Examples - Practical examples and advanced features
π― Documentation by Use Case
For Beginners
- Package Overview - Understand what the package offers
- Table Getting Started - Create your first table
- Form Getting Started - Create your first form
For Intermediate Users
- Advanced Search - Implement complex search functionality
- Form Validation - Master form validation techniques
- Data Export - Add export capabilities to your tables
- Form Sections - Organize complex forms effectively
For Advanced Users
- Performance Optimization - Optimize with caching strategies
- Model Binding - Handle complex data relationships
- Custom Actions - Create custom Livewire integrations
- Advanced Filtering - Implement complex filtering logic
For Developers & Contributors
- Configuration Deep Dive - Understand all configuration options
- Troubleshooting Guide - Debug common issues
π Documentation Standards
Code Examples
All code examples in this documentation are:
- β Tested and verified to work
- β Following Laravel best practices
- β Including proper namespace imports
- β Using clear, descriptive variable names
- β Commented for complex logic
Configuration References
- Configuration examples reference the published config file at
config/simple-datatables-and-forms.php
- Always check your local configuration for the most current options
- Version-specific features are clearly marked
Browser Compatibility
- All features are tested on modern browsers (Chrome, Firefox, Safari, Edge)
- Mobile responsiveness is built-in and documented
- Accessibility considerations are included where applicable
π οΈ Additional Resources
Package Resources
- GitHub Repository - Source code, issues, and discussions
- Changelog - Version history and breaking changes
- Contributing Guide - How to contribute to the package
- Security Policy - Security reporting and policies
Laravel Ecosystem
- Laravel Documentation - Laravel framework documentation
- Livewire Documentation - Livewire component documentation
- Tailwind CSS Documentation - Styling framework documentation
Community & Support
- GitHub Discussions - Community discussions and Q&A
- GitHub Issues - Bug reports and feature requests
- Email Support - Direct support for complex issues
π Troubleshooting
Common Issues
- Troubleshooting Guide - Comprehensive troubleshooting guide
- Performance Issues Cashing Troubleshooting - Performance-related problems
- Validation Problems Validation Troubleshooting - Form validation issues
Getting Help
- Check the troubleshooting guide - Most common issues are covered
- Search existing GitHub issues - Your problem might already be solved
- Create a new issue - Provide detailed information and code examples
- Join discussions - Connect with the community for advice
π€ Contributing to Documentation
Found an error or want to improve the documentation? We welcome contributions!
How to Contribute
- Fork the repository on GitHub
- Make your changes to the documentation files
- Test your changes - Ensure links work and code examples are correct
- Submit a pull request with a clear description of your improvements
Documentation Guidelines
- Use clear, concise language
- Include working code examples
- Test all links and references
- Follow the existing structure and formatting
- Add screenshots for UI-related features
π Need Help?
Quick Solutions:
- π Search the documentation - Use your browser's search function
- π Check troubleshooting guide - Common issues and solutions
- π¬ Browse discussions - Community Q&A
Still Need Help?
- π Report a bug - Include code examples and error messages
- π‘ Request a feature - Describe your use case
- π§ Email support - For complex integration questions
Package options can be configured via it's configuration file:
<?php
declare(strict_types=1);
return [
/*
|--------------------------------------------------------------------------
| Asset Loading Configuration
|--------------------------------------------------------------------------
|
| Configure how CSS and JavaScript assets are loaded.
|
*/
'assets' => [
// Enable lazy loading of CSS assets
'lazy_load' => true,
// Enable CSS minification
'minify_css' => true,
// Defer JavaScript loading
'defer_js' => true,
],
/*
|--------------------------------------------------------------------------
| Pagination Configuration
|--------------------------------------------------------------------------
|
| Configure default pagination behavior.
|
*/
'pagination' => [
// Default items per page
'per_page' => 10,
// Available pagination options
'options' => [10, 25, 50, 100],
// Show pagination summary
'show_summary' => true,
// Enable pagination by default
'enabled' => true,
],
/*
|--------------------------------------------------------------------------
| Search Configuration
|--------------------------------------------------------------------------
|
| Configure search behavior and optimization settings.
|
*/
'search' => [
// Default search mode: 'like', 'exact', or 'fulltext'
'default_mode' => 'like',
// Enable full-text search when available
'enable_fulltext' => false,
// Minimum characters required to trigger search
'min_characters' => 2,
// Debounce time in milliseconds
'debounce_time' => 300,
],
/*
|--------------------------------------------------------------------------
| Caching Configuration
|--------------------------------------------------------------------------
|
| Configure caching behavior for improved performance.
|
*/
'cache' => [
// Enable caching for column definitions
'enable' => true,
// Cache lifetime in seconds (default: 1 hour)
'lifetime' => 3600,
// Cache key prefix
'prefix' => 'simple_datatables_',
],
/*
|--------------------------------------------------------------------------
| Appearance Configuration
|--------------------------------------------------------------------------
|
| Configure the default appearance of tables.
|
*/
'appearance' => [
// Default theme: 'light', 'dark', or 'auto'
'theme' => 'light',
// Default striped rows
'striped' => true,
// Default hover effect
'hover' => true,
// Default border style: 'all', 'horizontal', 'vertical', 'outer', 'none'
'borders' => 'all',
// Default table size: 'sm', 'md', 'lg'
'size' => 'md',
],
/*
|--------------------------------------------------------------------------
| Responsive Configuration
|--------------------------------------------------------------------------
|
| Configure responsive behavior for different screen sizes.
|
*/
'responsive' => [
// Enable responsive tables
'enable' => true,
// Breakpoints for responsive behavior
'breakpoints' => [
'sm' => 640,
'md' => 768,
'lg' => 1024,
'xl' => 1280,
'2xl' => 1536,
],
],
/*
|--------------------------------------------------------------------------
| Export Configuration
|--------------------------------------------------------------------------
|
| Configure export functionality.
|
*/
'export' => [
// Enable export functionality
'enable' => true,
// Available export formats
'formats' => ['csv', 'excel', 'xlsx', 'xls', 'pdf'],
// Default export format
'default_format' => 'csv',
// Maximum rows for export (0 for unlimited)
'max_rows' => 10000,
// Custom filename prefix (default is 'export')
'filename_prefix' => 'export',
],
/*
|--------------------------------------------------------------------------
| Form Configuration
|--------------------------------------------------------------------------
|
| Configure form behavior and appearance.
|
*/
'form' => [
// Default theme: 'light', 'dark', or 'auto'
'theme' => 'light',
// Default number of columns in forms
'columns' => 2,
// Enable client-side validation
'client_validation' => true,
// Enable real-time validation
'realtime_validation' => false,
// Default field appearance
'field_appearance' => [
'size' => 'md', // 'sm', 'md', 'lg'
'border_radius' => 'md', // 'sm', 'md', 'lg', 'full'
],
// Form animations
'animations' => [
'enable' => true,
'duration' => 300, // milliseconds
],
],
/*
|--------------------------------------------------------------------------
| Filters Configuration
|--------------------------------------------------------------------------
|
| Configure filter behavior and appearance.
|
*/
'filters' => [
// Default number of columns for filter layout
'columns' => 6,
// Enable responsive filter columns
'responsive' => true,
// Responsive breakpoints for filter columns
'responsive_columns' => [
'sm' => 1, // 1 column on small screens
'md' => 2, // 2 columns on medium screens
'lg' => 4, // 4 columns on large screens
'xl' => 6, // 6 columns on extra large screens
],
],
/*
|--------------------------------------------------------------------------
| Security Configuration
|--------------------------------------------------------------------------
|
| Configure security settings.
|
*/
'security' => [
// Enable CSRF protection for all forms
'csrf_protection' => true,
// Enable rate limiting for search operations and form submissions
'rate_limiting' => [
'enable' => true,
'max_attempts' => 60,
'decay_minutes' => 1,
],
// Enable input sanitization
'sanitize_input' => true,
// File upload security settings
'max_file_size' => 10240, // KB (10MB)
'allowed_file_types' => [
'jpg',
'jpeg',
'png',
'gif',
'webp',
'svg',
'pdf',
'doc',
'docx',
'xls',
'xlsx',
'ppt',
'pptx',
'txt',
'csv',
'zip',
'rar',
],
// Content Security Policy settings
'csp' => [
'enable' => true,
'script_src' => "'self' 'unsafe-inline'",
'style_src' => "'self' 'unsafe-inline'",
'img_src' => "'self' data: https:",
],
// Additional security headers
'security_headers' => [
'x_frame_options' => 'DENY',
'x_content_type_options' => 'nosniff',
'x_xss_protection' => '1; mode=block',
'referrer_policy' => 'strict-origin-when-cross-origin',
],
],
];
This guide covers common issues you might encounter when using Laravel Simple Datatables And Forms and their solutions.
Installation Issues
Package Not Found
Problem: Composer cannot find the package.
Could not find package milenmk/laravel-simple-datatables-and-forms
Solution:
# Ensure you're using the correct package name
composer require milenmk/laravel-simple-datatables-and-forms
# If still having issues, clear composer cache
composer clear-cache
composer install
Version Conflicts
Problem: Composer reports version conflicts with Laravel or Livewire.
Solution:
# Check your Laravel and Livewire versions
composer show laravel/framework
composer show livewire/livewire
# Update to compatible versions
composer update laravel/framework livewire/livewire
# Or specify compatible versions in composer.json
"laravel/framework": "^10.0|^11.0|^12.0",
"livewire/livewire": "^3.0"
Asset Issues
Styles Not Loading
Problem: Table appears unstyled or broken.
Symptoms:
- Table has no styling
- Buttons appear as plain text
- Layout is broken
Solutions:
-
Check Asset Directives:
{{-- In your layout file --}} <head> @SimpleDatatablesStyle </head> <body> {{-- Your content --}} @SimpleDatatablesScript </body>
-
Publish Assets:
php artisan simple-datatables-and-forms:publish-assets
-
Clear Cache:
php artisan view:clear php artisan cache:clear
-
Check Tailwind Configuration:
// tailwind.config.js module.exports = { content: [ './vendor/milenmk/laravel-simple-datatables-and-forms/resources/views/**/*.blade.php', // ... your other paths ], };
JavaScript Not Working
Problem: Interactive features (search, filters, actions) don't work.
Solutions:
-
Check Script Placement:
{{-- Place before closing </body> tag --}} @SimpleDatatablesScript
-
Check for JavaScript Errors:
- Open browser developer tools (F12)
- Check Console tab for errors
- Common errors: Livewire not loaded, Alpine.js conflicts
-
Livewire Integration:
{{-- Ensure Livewire is loaded first --}} @livewireStyles @SimpleDatatablesStyle @livewireScripts @SimpleDatatablesScript
Table Display Issues
Table Not Showing
Problem: Table component renders but shows no content.
Debugging Steps:
-
Check Component Registration:
// Ensure your component extends Component and uses HasTable use Livewire\Component; use Milenmk\LaravelSimpleDatatablesAndForms\Traits\HasTable; class UserList extends Component { use HasTable; }
-
Verify Query:
public function table(Table $table): Table { // Debug your query $query = User::query(); dd($query->get()); // Temporary debug return $table ->query($query) ->schema([/* columns */]); }
-
Check Blade Template:
<div> {{ $this->table }} </div>
Empty Table with Data
Problem: Database has data but table shows "No records found".
Solutions:
-
Check Query Scope:
// Make sure your query isn't too restrictive public function table(Table $table): Table { return $table ->query(User::query()) // Remove any restrictive where clauses temporarily ->schema([/* columns */]); }
-
Verify Column Names:
// Ensure column names match database fields TextColumn::make('name'), // Should match 'name' column in database
-
Check Model Configuration:
// Verify your model is properly configured class User extends Model { protected $table = 'users'; // Correct table name protected $fillable = ['name', 'email']; // Include necessary fields }
Search Issues
Search Not Working
Problem: Search input appears but doesn't filter results.
Solutions:
-
Mark Columns as Searchable:
TextColumn::make('name') ->searchable(), // Add this
-
Check Database Indexes:
// Add indexes for better search performance Schema::table('users', function (Blueprint $table) { $table->index(['name', 'email']); });
-
Verify Search Configuration:
// config/simple-datatables-and-forms.php 'search' => [ 'min_characters' => 2, // Minimum characters to trigger search 'debounce_time' => 300, // Debounce time in milliseconds ],
Slow Search Performance
Problem: Search takes too long to execute.
Solutions:
-
Add Database Indexes:
-- Add indexes for searchable columns CREATE INDEX idx_users_name ON users(name); CREATE INDEX idx_users_email ON users(email);
-
Limit Search Columns:
TextColumn::make('name') ->searchable(['name']), // Only search specific columns
-
Enable Full-Text Search (MySQL):
-- Create full-text index ALTER TABLE users ADD FULLTEXT(name, email);
// config/simple-datatables-and-forms.php 'search' => [ 'enable_fulltext' => true, ],
Form Issues
Form Not Displaying
Problem: Form component renders but shows no form fields.
Solutions:
-
Check HasForm Trait:
use Milenmk\LaravelSimpleDatatablesAndForms\Traits\HasForm; class CreateUser extends Component { use HasForm; public function mount(): void { $this->mountForm(); // Don't forget this } }
-
Verify Form Schema:
public function form(Form $form): Form { return $form ->schema([ InputField::make('name')->required(), // Add more fields ]); }
Form Validation Not Working
Problem: Form submits without validation or validation messages don't appear.
Solutions:
-
Call Validation in Save Method:
public function save() { $this->validate(); // Add this line // Your save logic }
-
Check Validation Rules:
InputField::make('email') ->email() ->rules(['required', 'email', 'unique:users,email']) ->required(),
-
Display Validation Errors:
@if ($errors->any()) <div class="alert alert-danger"> <ul> @foreach ($errors->all() as $error) <li>{{ $error }}</li> @endforeach </ul> </div> @endif {{ $this->form }}
Performance Issues
Slow Table Loading
Problem: Tables take too long to load, especially with large datasets.
Solutions:
-
Enable Pagination:
public function table(Table $table): Table { return $table ->query(User::query()) ->schema([/* columns */]) ->paginate(25); // Limit results per page }
-
Optimize Database Queries:
public function table(Table $table): Table { return $table ->query( User::query() ->select(['id', 'name', 'email']) // Only select needed columns ->with(['profile']) // Eager load relationships ) ->schema([/* columns */]); }
-
Enable Caching:
public function table(Table $table): Table { return $table ->query(User::query()) ->schema([/* columns */]) ->cache(3600); // Cache for 1 hour }
Memory Issues
Problem: PHP runs out of memory when processing large datasets.
Solutions:
-
Increase Memory Limit:
// config/simple-datatables-and-forms.php 'export' => [ 'memory_limit' => '512M', ],
-
Use Chunking for Exports:
public function table(Table $table): Table { return $table ->query(User::query()) ->schema([/* columns */]) ->export([ 'chunk_size' => 1000, ]); }
-
Limit Export Rows:
'export' => [ 'max_rows' => 10000, ],
Export Issues
Export Not Working
Problem: Export button appears but downloads don't work.
Solutions:
-
Check Required Packages:
# For Excel export composer require phpoffice/phpspreadsheet # For PDF export composer require barryvdh/laravel-dompdf
-
Verify Export Configuration:
// config/simple-datatables-and-forms.php 'export' => [ 'enable' => true, 'formats' => ['csv', 'excel', 'pdf'], ],
-
Check File Permissions:
# Ensure storage directory is writable chmod -R 775 storage/ chown -R www-data:www-data storage/
Export Timeout
Problem: Large exports timeout before completing.
Solutions:
-
Increase Timeout:
'export' => [ 'timeout' => 300, // 5 minutes ],
-
Use Queue for Large Exports:
'export' => [ 'queue' => true, 'queue_threshold' => 5000, ],
Livewire Integration Issues
Component Not Updating
Problem: Changes don't reflect in the component.
Solutions:
-
Check Wire Directives:
{{-- Ensure proper wire directives --}} <input wire:model.live="search" />
-
Verify Component Properties:
class UserList extends Component { use HasTable; // Make sure properties are public or have proper getters public $search = ''; }
-
Clear Livewire Cache:
php artisan livewire:clear-cache
Action Methods Not Found
Problem: Clicking actions results in "Method not found" errors.
Solutions:
-
Verify Method Names:
// In your action definition DeleteAction::make('delete')->action('deleteUser'), // Ensure method exists in component public function deleteUser($id) { // Method implementation }
-
Check Method Visibility:
// Methods must be public public function deleteUser($id) // β Correct { // Implementation } private function deleteUser($id) // β Wrong - not accessible { // Implementation }
Common Error Messages
"Class not found" Errors
Class 'Milenmk\LaravelSimpleDatatablesAndForms\Traits\HasTable' not found
Solution: Ensure package is properly installed and autoloaded:
composer dump-autoload
"Method does not exist" Errors
Method Livewire\Component::table does not exist
Solution: Add the HasTable trait:
use Milenmk\LaravelSimpleDatatablesAndForms\Traits\HasTable;
class YourComponent extends Component
{
use HasTable; // Add this line
}
"View not found" Errors
View [livewire.your-component] not found
Solution: Create the missing view file or check the view path:
public function render()
{
return view('livewire.your-component'); // Ensure this file exists
}
Debug Mode
Enable debug mode for detailed error information in your config/app.php
file
// config/simple-datatables-and-forms.php
'debug' => env('APP_DEBUG', false),
Or temporarily in your component:
public function table(Table $table): Table
{
// Add debug information
logger('Table query:', ['query' => User::query()->toSql()]);
return $table
->query(User::query())
->schema([/* columns */]);
}
Getting Help
If you're still experiencing issues:
-
**Check the GitHub Issues **: Laravel Simple Datatables And Forms Issues
-
Create a Minimal Reproduction: Create a simple example that demonstrates the issue
-
Provide System Information:
- PHP version
- Laravel version
- Livewire version
- Package version
- Browser and version (for frontend issues)
-
Include Error Messages: Copy the complete error message and stack trace
-
Share Relevant Code: Include the component code and any related configuration
Prevention Tips
- Keep Dependencies Updated: Regularly update Laravel, Livewire, and the package
- Use Version Constraints: Specify compatible versions in composer.json
- Test in Development: Always test new features in development before production
- Monitor Performance: Use Laravel Telescope or similar tools to monitor performance
- Backup Before Updates: Always backup your application before major updates
For more specific issues, refer to the individual documentation sections:
No content for this heading.
This guide will help you create your first data table using Laravel Simple Datatables And Forms.
Prerequisites
Before you begin, ensure you have:
- Laravel 10.x or higher installed
- Livewire 3.x installed and configured
- Laravel Simple Datatables package installed And Forms
Creating Your First Table
Method 1: Using Artisan Command (Recommended)
The fastest way to create a table is using the provided Artisan command:
# Basic table generation
php artisan make:milenmk-datatable UserList User
# Generate with auto-generated columns based on model
php artisan make:milenmk-datatable UserList User --generate
This creates:
- A Livewire component at
app/Livewire/UserList.php
- A Blade view at
resources/views/livewire/user-list.blade.php
- Auto-generated columns (when using
--generate
flag)
Method 2: Manual Creation
Step 1: Create a Livewire Component
<?php
namespace App\Livewire;
use Livewire\Component;
use Milenmk\LaravelSimpleDatatablesAndForms\Traits\HasTable;
use Milenmk\LaravelSimpleDatatablesAndForms\Table\Table;
use Milenmk\LaravelSimpleDatatablesAndForms\Table\Columns\TextColumn;
use Milenmk\LaravelSimpleDatatablesAndForms\Table\Columns\ActionColumn;
use Milenmk\LaravelSimpleDatatablesAndForms\Table\Actions\EditAction;
use Milenmk\LaravelSimpleDatatablesAndForms\Table\Actions\DeleteAction;
use App\Models\User;
class UserList extends Component
{
use HasTable;
public function table(Table $table): Table
{
return $table
->query(User::query())
->heading('Users Management')
->schema([
TextColumn::make('id')->label('ID')->sortable(),
TextColumn::make('name')->label('Full Name')->searchable()->sortable(),
TextColumn::make('email')->label('Email Address')->searchable()->sortable(),
TextColumn::make('created_at')
->label('Created')
->format(fn($value) => $value->format('M d, Y'))
->sortable(),
ActionColumn::make('actions')
->label('Actions')
->actions([
EditAction::make('edit')
->label('Edit')
->icon('heroicon-o-pencil-square')
->url(fn($row) => route('users.edit', $row)),
DeleteAction::make('delete')
->label('Delete')
->icon('heroicon-o-trash')
->action('deleteUser')
->requiresConfirmation(),
]),
])
->striped()
->paginate();
}
public function deleteUser($id)
{
$user = User::find($id);
if ($user) {
$user->delete();
$this->notification()->success('User deleted successfully');
}
}
public function render()
{
return view('livewire.user-list');
}
}
Step 2: Create the Blade View
Create resources/views/livewire/user-list.blade.php
:
<div>
<div class="mb-6">
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">Users</h1>
<p class="text-gray-600 dark:text-gray-400">Manage your application users</p>
</div>
{{ $this->table }}
</div>
Step 3: Add Route
Add a route to display your table:
// routes/web.php
use App\Livewire\UserList;
Route::get('/users', UserList::class)->name('users.index');
Understanding the Table Structure
Basic Table Configuration
public function table(Table $table): Table
{
return $table
->query(User::query()) // Base query
->heading('Users Management') // Table title
->schema([ // Column definitions
// Columns go here
])
->striped() // Alternating row colors
->paginate() // Enable pagination
->perPage(25); // Items per page
}
Column Types
TextColumn
The most common column type for displaying text data:
TextColumn::make('name')
->label('Full Name')
->searchable() // Enable search on this column
->sortable() // Enable sorting
->format(fn($value) => ucwords($value)), // Custom formatting
ActionColumn
For displaying action buttons:
ActionColumn::make('actions')
->label('Actions')
->actions([
EditAction::make('edit')->url(fn($row) => route('users.edit', $row)),
DeleteAction::make('delete')->action('deleteUser'),
]),
Adding Search and Filters
use Milenmk\LaravelSimpleDatatablesAndForms\Table\Filters\SelectFilter;
use Milenmk\LaravelSimpleDatatablesAndForms\Table\Filters\DateFilter;
public function table(Table $table): Table
{
return $table
->query(User::query())
->schema([
// columns...
])
->filters([
SelectFilter::make('role')
->options([
'admin' => 'Administrator',
'user' => 'Regular User',
])
->label('User Role'),
DateFilter::make('created_at')
->label('Registration Date'),
]);
}
Customizing Appearance
Table Styling Options
public function table(Table $table): Table
{
return $table
->query(User::query())
->schema([/* columns */])
->striped() // Alternating row colors
->hover() // Hover effects
->bordered() // Table borders
->compact() // Smaller padding
->responsive(); // Mobile-friendly
}
Column Alignment and Styling
TextColumn::make('amount')
->label('Amount')
->align('right') // left, center, right
->headerAlign('center') // Header alignment
->color('text-green-600') // Text color
->background('bg-gray-50') // Background color
->weight('font-bold'), // Font weight
Performance Optimization
Enable Caching
public function table(Table $table): Table
{
return $table
->query(User::query())
->schema([/* columns */])
->cache(3600); // Cache for 1 hour
}
Optimize Queries
public function table(Table $table): Table
{
return $table
->query(
User::query()
->with(['profile', 'roles']) // Eager load relationships
->select(['id', 'name', 'email', 'created_at']) // Select only needed columns
)
->schema([/* columns */]);
}
Next Steps
Now that you have a basic table working, explore these advanced features:
- Advanced Search - Implement complex search functionality
- Data Export - Add CSV, Excel, and PDF export capabilities
- Filters and Grouping - Create advanced filtering options
- Actions - Add custom actions and bulk operations
- Configuration - Customize global settings
Troubleshooting
Common Issues
Table not displaying:
- Ensure you've included
@SimpleDatatablesStyle
and@SimpleDatatablesScript
in your layout - Check that the Livewire component is properly registered
Search not working:
- Verify that columns are marked as
searchable()
- Check database indexes for better performance
Styling issues:
- Ensure Tailwind CSS is properly configured
- Check that the package views are included in your Tailwind content paths
Performance issues:
- Enable caching for large datasets
- Use
select()
to limit queried columns - Add database indexes for searchable/sortable columns
For more troubleshooting help, see our GitHub Issues page.
Quick Reference & Common Patterns
Installation & Setup Commands
# Install package
composer require milenmk/laravel-simple-datatables-and-forms
# Publish assets
php artisan simple-datatables-and-forms:publish-assets
# Publish configuration (optional)
php artisan vendor:publish --tag=laravel-simple-datatables-and-forms-config
# Generate table component
php artisan make:milenmk-datatable UserList User --generate
Essential Layout Setup
{{-- In your layout file --}}
<head>
@SimpleDatatablesStyle
</head>
<body>
{{-- Your content --}}
@SimpleDatatablesScript
</body>
Common Column Types Cheat Sheet
// Text Column with all common options
TextColumn::make('name')
->label('Full Name')
->searchable()
->sortable()
->format(fn($value) => ucwords($value))
->color('text-gray-900')
->align('left'),
// Toggle Column
ToggleColumn::make('is_active')
->label('Active')
->onLabel('Yes')
->offLabel('No'),
// Icon Column for boolean states
IconColumn::make('is_verified')
->boolean()
->label('Verified')
->trueIcon('check-circle')
->falseIcon('x-circle')
->trueColor('text-green-600')
->falseColor('text-red-600'),
// Progress Column
ProgressColumn::make('completion')
->label('Progress')
->min(0)
->max(100)
->color('primary'),
// Action Column
ActionColumn::make('actions')
->label('Actions')
->actions([
EditAction::make('edit')
->label('Edit')
->icon('pencil')
->url(fn($row) => route('users.edit', $row)),
DeleteAction::make('delete')
->label('Delete')
->icon('trash')
->action('deleteUser')
->actionView('icon'),
]),
Filters Quick Setup
use Milenmk\LaravelSimpleDatatablesAndForms\Table\Filters\SelectFilter;
use Milenmk\LaravelSimpleDatatablesAndForms\Table\Filters\DateFilter;
use Milenmk\LaravelSimpleDatatablesAndForms\Table\Filters\TernaryFilter;
->filters([
SelectFilter::make('status')
->options(['active' => 'Active', 'inactive' => 'Inactive'])
->label('Status'),
DateFilter::make('created_at')
->label('Registration Date'),
TernaryFilter::make('is_verified')
->label('Email Verified'),
])
Export Configuration
->export([
'formats' => ['csv', 'excel', 'pdf'],
'filename' => 'users_export',
'max_rows' => 10000,
])
Table Styling Options
->striped() // Alternating row colors
->hover() // Hover effects
->bordered() // Table borders
->compact() // Smaller padding
->responsive() // Mobile-friendly
Performance Optimization Patterns
// Eager loading relationships
->query(User::query()->with(['profile', 'roles']))
// Select specific columns only
->query(User::query()->select(['id', 'name', 'email']))
// Enable caching
->cache(3600) // Cache for 1 hour
// Pagination
->paginate(25)
Conditional Logic Examples
// Conditional column styling
TextColumn::make('status')
->color(fn($row) => $row->status === 'active' ? 'text-green-600' : 'text-red-600'),
// Conditional column visibility
TextColumn::make('admin_notes')
->visible(fn() => auth()->user()->isAdmin()),
// Dynamic column content
TextColumn::make('full_name')
->value(fn($row) => $row->first_name . ' ' . $row->last_name),
Common Troubleshooting Solutions
// Fix: Table not loading data
public function table(Table $table): Table
{
return $table
->query(User::query()) // Don't forget the query!
->schema([...]);
}
// Fix: Search not working
TextColumn::make('name')->searchable(), // Mark columns as searchable
// Fix: Performance issues
Schema::table('users', function (Blueprint $table) {
$table->index(['name', 'email']); // Add database indexes
});
Configuration Quick Reference
// config/simple-datatables-and-forms.php
'pagination' => [
'per_page' => 25,
'options' => [10, 25, 50, 100],
],
'search' => [
'min_characters' => 2,
'debounce_time' => 300,
],
'export' => [
'formats' => ['csv', 'excel', 'pdf'],
'max_rows' => 10000,
],
'cache' => [
'enable' => true,
'lifetime' => 3600,
],
Laravel Simple Datatables And Forms provides various column types to display different kinds of data in your tables. Each column type is optimized for specific data types and use cases.
TextColumn
The most versatile column type for displaying text-based data.
Basic Usage
use Milenmk\LaravelSimpleDatatablesAndForms\Table\Columns\TextColumn;
TextColumn::make('name')
->label('Full Name')
->searchable()
->sortable(),
Text Formatting
// Custom formatting
TextColumn::make('title')
->format(fn($value) => ucwords($value)),
// Date formatting
TextColumn::make('created_at')
->format(fn($value) => $value->format('M d, Y')),
// Currency formatting
TextColumn::make('price')
->format(fn($value) => '$' . number_format($value, 2)),
// Custom value calculation
TextColumn::make('full_name')
->value(fn($row) => $row->first_name . ' ' . $row->last_name),
Text Styling
TextColumn::make('status')
->color('text-green-600') // Text color
->background('bg-green-50') // Background color
->weight('font-bold') // Font weight
->align('center') // Text alignment
->wrap(false), // Disable text wrapping
Conditional Styling
TextColumn::make('status')
->color(fn($row) => match($row->status) {
'active' => 'text-green-600',
'inactive' => 'text-red-600',
'pending' => 'text-yellow-600',
default => 'text-gray-600',
})
->background(fn($row) => match($row->status) {
'active' => 'bg-green-50',
'inactive' => 'bg-red-50',
'pending' => 'bg-yellow-50',
default => 'bg-gray-50',
}),
ToggleColumn
Interactive toggle switches for boolean values that can be changed directly in the table.
Basic Usage
use Milenmk\LaravelSimpleDatatablesAndForms\Table\Columns\ToggleColumn;
ToggleColumn::make('is_active')
->label('Active Status')
->onLabel('Active')
->offLabel('Inactive'),
Toggle Configuration
ToggleColumn::make('featured')
->label('Featured')
->color('green') // Toggle color: green, blue, red, yellow
->size('lg') // Size: sm, md, lg
->disabled(fn($row) => $row->is_locked), // Conditionally disable
->updateUsing('toggleFeatured'), // Custom update method
Handling Toggle Updates
// In your Livewire component
public function toggleFeatured($id, $value)
{
$record = YourModel::find($id);
$record->update(['featured' => $value]);
$this->notification()->success('Status updated successfully');
}
CheckBoxColumn
Checkboxes for selection and boolean values.
Basic Usage
use Milenmk\LaravelSimpleDatatablesAndForms\Table\Columns\CheckBoxColumn;
CheckBoxColumn::make('selected')
->label('Select')
->bulk(), // Enable bulk selection
Checkbox Configuration
CheckBoxColumn::make('terms_accepted')
->label('Terms Accepted')
->disabled(fn($row) => $row->is_locked)
->checkedValue(1)
->uncheckedValue(0)
->updateUsing('updateTermsAccepted'),
ProgressColumn
Visual progress bars for numeric values with min/max ranges.
Basic Usage
use Milenmk\LaravelSimpleDatatablesAndForms\Table\Columns\ProgressColumn;
ProgressColumn::make('completion')
->label('Progress')
->min(0)
->max(100)
->color('primary'), // primary, success, warning, danger
Progress Configuration
ProgressColumn::make('score')
->label('Test Score')
->min(0)
->max(100)
->color(fn($value) => match(true) {
$value >= 90 => 'success',
$value >= 70 => 'primary',
$value >= 50 => 'warning',
default => 'danger',
})
->showValue() // Display numeric value on progress bar
->format(fn($value) => $value . '%'),
IconColumn
Display icons, particularly useful for boolean states and status indicators.
Basic Usage
use Milenmk\LaravelSimpleDatatablesAndForms\Table\Columns\IconColumn;
IconColumn::make('is_verified')
->boolean() // Use for boolean values
->label('Verified'),
Boolean Icon Configuration
IconColumn::make('status')
->boolean()
->trueIcon('heroicon-o-check-circle')
->falseIcon('heroicon-o-x-circle')
->trueColor('text-green-600')
->falseColor('text-red-600'),
// Or use the combined method
IconColumn::make('status')
->boolean()
->true('heroicon-o-check-circle', 'text-green-600')
->false('heroicon-o-x-circle', 'text-red-600'),
Custom Icon Logic
IconColumn::make('priority')
->icon(fn($row) => match($row->priority) {
'high' => 'heroicon-o-exclamation-triangle',
'medium' => 'heroicon-o-minus-circle',
'low' => 'heroicon-o-information-circle',
})
->color(fn($row) => match($row->priority) {
'high' => 'text-red-600',
'medium' => 'text-yellow-600',
'low' => 'text-blue-600',
}),
ActionColumn
Display action buttons for row-specific operations.
Basic Usage
use Milenmk\LaravelSimpleDatatablesAndForms\Table\Columns\ActionColumn;
use Milenmk\LaravelSimpleDatatablesAndForms\Table\Actions\EditAction;
use Milenmk\LaravelSimpleDatatablesAndForms\Table\Actions\DeleteAction;
ActionColumn::make('actions')
->label('Actions')
->actions([
EditAction::make('edit')
->url(fn($row) => route('users.edit', $row)),
DeleteAction::make('delete')
->action('deleteRecord'),
]),
Action Grouping
ActionColumn::make('actions')
->groupActions() // Group actions in dropdown
->actions([
EditAction::make('edit'),
DeleteAction::make('delete'),
ViewAction::make('view'),
]),
For detailed information about actions, see the Actions Documentation.
ImageColumn
Display images with various sizing and styling options.
Basic Usage
use Milenmk\LaravelSimpleDatatablesAndForms\Table\Columns\ImageColumn;
ImageColumn::make('avatar')
->label('Profile Picture')
->disk('public') // Storage disk
->size(50) // Size in pixels
->circular(), // Circular images
Image Configuration
ImageColumn::make('product_image')
->label('Product')
->disk('s3')
->size(80)
->square() // Square aspect ratio
->defaultImageUrl('/images/placeholder.png')
->tooltip(fn($row) => $row->name), // Show tooltip on hover
BadgeColumn
Display colored badges for status, categories, or tags.
Basic Usage
use Milenmk\LaravelSimpleDatatablesAndForms\Table\Columns\BadgeColumn;
BadgeColumn::make('status')
->label('Status')
->colors([
'active' => 'success',
'inactive' => 'danger',
'pending' => 'warning',
]),
Badge Configuration
BadgeColumn::make('category')
->label('Category')
->color(fn($value) => match($value) {
'electronics' => 'blue',
'clothing' => 'green',
'books' => 'purple',
default => 'gray',
})
->size('lg') // sm, md, lg
->icon(fn($value) => match($value) {
'electronics' => 'heroicon-o-cpu-chip',
'clothing' => 'heroicon-o-shirt',
'books' => 'heroicon-o-book-open',
}),
Common Column Options
All column types support these common options:
Visibility and Layout
TextColumn::make('name')
->visible(true) // Show/hide column
->hidden() // Hide column but keep for export
->exportOnly() // Only show in exports
->columnSpan(2) // Span multiple columns
->grow() // Allow column to grow
->shrink(), // Allow column to shrink
Alignment and Styling
TextColumn::make('amount')
->align('right') // left, center, right
->headerAlign('center') // Header alignment
->verticalAlign('top') // top, middle, bottom
->width('200px') // Fixed width
->minWidth('100px') // Minimum width
->maxWidth('300px'), // Maximum width
Search and Sort
TextColumn::make('name')
->searchable() // Enable search
->sortable() // Enable sorting
->searchable(['first_name', 'last_name']) // Search multiple columns
->sortUsing('custom_sort_method'), // Custom sort logic
Tooltips and Help
TextColumn::make('code')
->tooltip('Internal reference code')
->tooltip(fn($row) => "Created: {$row->created_at}")
->helperText('This is additional help text')
->copyable() // Allow copying value to clipboard
->copyMessage('Code copied!'), // Custom copy message
Export Control
TextColumn::make('name')
->exportable() // Include in exports
->exportable(false) // Exclude from exports
->exportValue(fn($row) => strtoupper($row->name)) // Custom export value
->exportFormat(fn($value) => $value . ' (exported)'), // Export formatting
Custom Column Types
You can create custom column types by extending the base column class:
use Milenmk\LaravelSimpleDatatablesAndForms\Table\Columns\Column;
class CurrencyColumn extends Column
{
protected string $view = 'components.currency-column';
public function currency(string $currency = 'USD'): static
{
$this->currency = $currency;
return $this;
}
public function format($value): string
{
return number_format($value, 2) . ' ' . $this->currency;
}
}
Then use it in your tables:
CurrencyColumn::make('price')
->currency('EUR')
->label('Price'),
Performance Considerations
Database Optimization
// Use select to limit queried columns
public function table(Table $table): Table
{
return $table
->query(
User::query()->select(['id', 'name', 'email', 'status'])
)
->schema([
TextColumn::make('name'),
TextColumn::make('email'),
BadgeColumn::make('status'),
]);
}
Eager Loading
// Eager load relationships for related data
public function table(Table $table): Table
{
return $table
->query(
User::query()->with(['profile', 'roles'])
)
->schema([
TextColumn::make('name'),
TextColumn::make('profile.bio'),
TagsColumn::make('roles.name'),
]);
}
Caching
// Cache expensive calculations
TextColumn::make('calculated_field')
->value(function ($row) {
return cache()->remember(
"user_{$row->id}_calculation",
3600,
fn() => $this->expensiveCalculation($row)
);
}),
Best Practices
-
Choose the Right Column Type: Use specific column types (BadgeColumn, IconColumn) instead of generic TextColumn when appropriate.
-
Optimize Database Queries: Use
select()
to limit columns and eager load relationships. -
Implement Proper Caching: Cache expensive calculations and database queries.
-
Use Conditional Logic: Apply conditional styling and visibility based on data.
-
Provide Clear Labels: Use descriptive labels that users can understand.
-
Consider Mobile Experience: Test column layouts on mobile devices and use responsive design.
-
Implement Proper Authorization: Hide sensitive columns based on user permissions.
-
Use Export Control: Carefully control what data is included in exports.
For more advanced table features, see:
Laravel Simple Datatables And Forms provides a comprehensive configuration system to customize the behavior and appearance of your tables.
Publishing the Configuration
To publish the configuration file, run:
php artisan vendor:publish --tag=laravel-simple-datatables-and-forms-config
This will create a simple-datatables-and-forms.php
file in your application's config
directory.
Pagination Settings
You can customize pagination settings for your tables through the configuration file:
'pagination' => [
// Default items per page
'per_page' => 10,
// Available pagination options
'options' => [10, 25, 50, 100],
// Show pagination summary
'show_summary' => true,
// Enable pagination by default
'enabled' => true,
],
You can also customize pagination at the component level:
// In your Livewire component
public function table(Table $table): Table
{
return $table
->query(User::query())
->schema([
// Your columns here
])
->paginate(true) // Enable pagination (default)
->perPage(25); // Set items per page
}
// Override per page options
public function perPageOptions(): array
{
return [5, 15, 30, 50, 100]; // Custom options
}
Configuration Options
Asset Loading
Control how CSS and JavaScript assets are loaded:
'assets' => [
// Enable lazy loading of CSS assets
'lazy_load' => true,
// Enable CSS minification
'minify_css' => true,
// Defer JavaScript loading
'defer_js' => true,
],
Search Configuration
Configure search behavior and optimization settings:
'search' => [
// Default search mode: 'like', 'exact', or 'fulltext'
'default_mode' => 'like',
// Enable full-text search when available
'enable_fulltext' => false,
// Minimum characters required to trigger search
'min_characters' => 2,
// Debounce time in milliseconds
'debounce_time' => 300,
],
Caching Configuration
Configure caching behavior for improved performance:
'cache' => [
// Enable caching for column definitions
'enable' => true,
// Cache lifetime in seconds (default: 1 hour)
'lifetime' => 3600,
// Cache key prefix
'prefix' => 'simple_datatables_',
],
Appearance Configuration
Configure the default appearance of tables:
'appearance' => [
// Default theme: 'light', 'dark', or 'auto'
'theme' => 'light',
// Default striped rows
'striped' => true,
// Default hover effect
'hover' => true,
// Default border style: 'all', 'horizontal', 'vertical', 'outer', 'none'
'borders' => 'all',
// Default table size: 'sm', 'md', 'lg'
'size' => 'md',
],
Responsive Configuration
Configure responsive behavior for different screen sizes:
'responsive' => [
// Enable responsive tables
'enable' => true,
// Breakpoints for responsive behavior
'breakpoints' => [
'sm' => 640,
'md' => 768,
'lg' => 1024,
'xl' => 1280,
'2xl' => 1536,
],
],
Export Configuration
Configure export functionality:
'export' => [
// Enable export functionality
'enable' => true,
// Available export formats
'formats' => ['csv', 'excel', 'pdf'],
// Default export format
'default_format' => 'csv',
// Maximum rows for export (0 for unlimited)
'max_rows' => 10000,
// Custom filename prefix (default is 'export')
'filename_prefix' => 'export',
],
Filters Configuration
Configure filter behavior and appearance
'filters' => [
// Default number of columns for filter layout
'columns' => 6,
// Enable responsive filter columns
'responsive' => true,
// Responsive breakpoints for filter columns
'responsive_columns' => [
'sm' => 1, // 1 column on small screens
'md' => 2, // 2 columns on medium screens
'lg' => 4, // 4 columns on large screens
'xl' => 6, // 6 columns on extra large screens
],
],
Security Configuration
Configure security settings:
'security' => [
// Enable CSRF protection for all forms
'csrf_protection' => true,
// Enable rate limiting for search operations
'rate_limiting' => [
'enable' => true,
'max_attempts' => 60,
'decay_minutes' => 1,
],
// Enable input sanitization
'sanitize_input' => true,
],
Laravel Simple Datatables And Forms provides powerful search capabilities that can be customized to fit your needs.
Search Configuration
You can configure search behavior in the simple-datatables-and-forms.php
configuration file:
'search' => [
// Default search mode: 'like', 'exact', or 'fulltext'
'default_mode' => 'like',
// Enable full-text search when available
'enable_fulltext' => false,
// Minimum characters required to trigger search
'min_characters' => 2,
// Debounce time in milliseconds
'debounce_time' => 300,
],
Search Modes
LIKE Search
The default search mode uses SQL LIKE
queries with wildcards. This is compatible with all database systems but may be
slower on large datasets.
// Example of LIKE search in SQL
SELECT * FROM users WHERE name LIKE '%John%'
Exact Match
Exact match search looks for exact matches of the search term. This is faster but less flexible.
// Example of exact match search in SQL
SELECT * FROM users WHERE name = 'John'
Full-Text Search
When enabled and supported by your database, full-text search provides the best performance for large datasets. It requires proper database configuration.
For MySQL:
-- Create a FULLTEXT index
ALTER TABLE users
ADD FULLTEXT (name, email, description);
-- Then the search will use
SELECT *
FROM users
WHERE MATCH(name, email, description) AGAINST('John' IN BOOLEAN MODE)
Note about PostgreSQL Implementation: While the documentation mentions PostgreSQL full-text search, the current
implementation in SearchService.php
does not actually use PostgreSQL's full-text search capabilities. Instead, it
falls back to using ILIKE
for PostgreSQL:
// Current implementation for PostgreSQL in SearchService.php
case 'pgsql':
return $query->where(function ($q) use ($searchableColumns, $search) {
foreach ($searchableColumns as $column) {
$q->orWhereRaw("{$column}::text ILIKE ?", ["%{$search}%"]);
}
});
To implement true PostgreSQL full-text search, you would need to modify the SearchService
class to use:
-- PostgreSQL true full-text search syntax
SELECT *
FROM users
WHERE to_tsvector('english', name || ' ' || email || ' ' || description) @@ to_tsquery('english', 'John')
Implementing Search in Your Components
Search functionality is automatically included in components that use the HasTable
trait. The search input will appear
in the table header.
Customizing Search Behavior
You can customize search behavior for a specific table by overriding methods in your Livewire component:
// Set minimum characters required for search
public function searchMinCharacters(): int
{
return 3; // Override the default value
}
// Set debounce time for search input
public function searchDebounceTime(): int
{
return 500; // Override the default value
}
// Change search mode
public function mount()
{
$this->searchMode = 'fulltext'; // Use full-text search
}
// Customize search placeholder
public function searchPlaceholder(): string
{
return 'Search users...';
}
Customizing Searchable Columns
You can specify which columns are searchable when defining your table schema:
public function table(Table $table): Table
{
return $table
->query(User::query())
->schema([
TextColumn::make('name')
->searchable(), // This column will be included in search
TextColumn::make('email')
->searchable(), // This column will be included in search
TextColumn::make('created_at')
->searchable(false), // This column will NOT be included in search
]);
}
Advanced Search Options
You can customize how each column is searched:
TextColumn::make('name')->searchable(true, function ($query, $searchTerm) {
// Custom search logic
return $query->where(function ($q) use ($searchTerm) {
$q->where('first_name', 'like', "%{$searchTerm}%")->orWhere('last_name', 'like', "%{$searchTerm}%");
});
});
Search Performance Tips
-
Use Indexes: Make sure your searchable columns are properly indexed in the database.
-
Consider Full-Text Search: For large datasets, enable full-text search and create appropriate full-text indexes.
-
Limit Searchable Columns: Only make necessary columns searchable to improve performance.
-
Increase Minimum Characters: Setting a higher minimum character count (3-4) can significantly reduce unnecessary searches.
-
Adjust Debounce Time: Increase the debounce time for slower databases or complex queries.
The FiltersGroup
class provides a more structured way to group filters together, offering better organization and
control over filter grouping compared to the individual group()
method approach.
Overview
The FiltersGroup
class allows you to:
- Group multiple filters together using a dedicated class
- Control group label visibility with
hideGroupLabel()
method - Set custom group labels
- Organize filters in a more readable and maintainable way
Basic Usage
Creating a FiltersGroup
use Milenmk\LaravelSimpleDatatablesAndForms\Table\Filters\FiltersGroup;
use Milenmk\LaravelSimpleDatatablesAndForms\Table\Filters\TernaryFilter;
use Milenmk\LaravelSimpleDatatablesAndForms\Table\Filters\SelectFilter;
public function table(Table $table): Table
{
return $table
->query(User::query())
->filters([
// Using FiltersGroup
FiltersGroup::make('some_name')->schema([
TernaryFilter::make('filter1'),
TernaryFilter::make('filter2'),
TernaryFilter::make('filter3'),
]),
// Individual filter
SelectFilter::make('filter4')
])
->filterColumns(2); // 2 columns: 1 for the group, 1 for filter4
}
Hiding Group Labels
You can hide the group label using the hideGroupLabel()
method:
FiltersGroup::make('status_filters')
->schema([TernaryFilter::make('is_active')->label('Active'), TernaryFilter::make('is_verified')->label('Verified')])
->hideGroupLabel();
Custom Group Labels
Set a custom label for the group:
FiltersGroup::make('user_status')
->label('User Status Filters') // Custom label instead of 'user_status'
->schema([
TernaryFilter::make('is_admin')->label('Administrator'),
TernaryFilter::make('is_verified')->label('Email Verified'),
]);
Methods
make(string $name = ''): static
Creates a new FiltersGroup instance with the given name.
schema(array $filters): static
Sets the filters that belong to this group. Automatically applies the group name to all filters.
label(string $label): static
Sets a custom label for the group.
hideGroupLabel(): static
Hides the group label from display.
showGroupLabel(): static
Shows the group label (default behavior).
getName(): string
Returns the group name.
getLabel(): ?string
Returns the group label (null if hidden).
isLabelHidden(): bool
Checks if the group label should be hidden.
getFilters(): array
Returns all filters in this group.
addFilter(BaseFilter $filter): static
Adds a single filter to the group.
Complete Example
public function table(Table $table): Table
{
return $table
->query(User::query())
->filters([
// Suspension Status Group (no label)
FiltersGroup::make('suspension_status')->schema([
TernaryFilter::make('temporary_suspended')
->label('Temporarily Suspended')
->query(function ($query, $value) {
if ($value === true) {
$query->whereNotNull('suspended_at')
->whereNotNull('suspended_until');
}
})
->toggle(),
TernaryFilter::make('permanently_suspended')
->label('Permanently Suspended')
->query(function ($query, $value) {
if ($value === true) {
$query->whereNotNull('suspended_at')
->whereNull('suspended_until');
}
})
->toggle(),
])->hideGroupLabel(),
// User Status Group (with custom label)
FiltersGroup::make('user_status')
->label('User Status Filters')
->schema([
TernaryFilter::make('is_admin')
->label('Administrator')
->toggle(),
TernaryFilter::make('email_verified')
->label('Email Verified')
->toggle(),
]),
// Individual filters
SelectFilter::make('plan')
->label('Plan')
->relationship('plan', 'name'),
SelectFilter::make('status')
->label('Status')
->options(['active' => 'Active', 'inactive' => 'Inactive']),
])
->filterColumns(4); // 4 columns total
}
Comparison with Individual group() Method
Old Approach (still supported)
->filters([
TernaryFilter::make('filter1')->group('some_name'),
TernaryFilter::make('filter2')->group('some_name'),
TernaryFilter::make('filter3')->group('some_name'),
SelectFilter::make('filter4')
])
New FiltersGroup Approach
->filters([
FiltersGroup::make('some_name')->schema([
TernaryFilter::make('filter1'),
TernaryFilter::make('filter2'),
TernaryFilter::make('filter3'),
])->hideGroupLabel(),
SelectFilter::make('filter4')
])
Benefits
- Better Organization: Filters are visually grouped in your code
- Label Control: Easy control over group label visibility
- Maintainability: Easier to manage related filters together
- Flexibility: Mix FiltersGroup with individual filters
- Backward Compatibility: Works alongside the existing
group()
method
Notes
- The
FiltersGroup
automatically calls thegroup()
method on all filters in its schema - Both approaches (FiltersGroup and individual group() method) can be used together
- Group labels can be customized or hidden as needed
- The underlying filter grouping logic remains the same
Laravel Simple Datatables And Forms provides a flexible action system that allows you to add interactive buttons to your tables. These actions can either navigate to URLs or trigger Livewire methods in your component.
Basic Usage
Actions are typically added to tables using the ActionColumn
class:
use Milenmk\LaravelSimpleDatatablesAndForms\Table\Columns\ActionColumn;
use Milenmk\LaravelSimpleDatatablesAndForms\Table\Actions\EditAction;
use Milenmk\LaravelSimpleDatatablesAndForms\Table\Actions\DeleteAction;
use Milenmk\LaravelSimpleDatatablesAndForms\Table\Actions\ViewAction;
public function table(Table $table): Table
{
return $table
->query(User::query())
->schema([
// Other columns...
ActionColumn::make('actions')
->label('Actions')
->actions([
EditAction::make('edit')
->label('Edit')
->icon('heroicon-o-pencil-square')
->url(fn($row) => route('users.edit', $row)),
DeleteAction::make('delete')
->label('Delete')
->icon('heroicon-o-trash')
->action('deleteUser'),
ViewAction::make('view')
->label('View')
->icon('heroicon-o-eye')
->url(fn($row) => route('users.show', $row)),
]),
]);
}
Action Types
The package includes several pre-built action types:
- EditAction: For editing records
- DeleteAction: For deleting records
- ViewAction: For viewing record details
- CustomAction: For custom functionality
You can also create your own action types by extending the BaseAction
class.
URL-Based Actions
URL-based actions navigate to a specific URL when clicked:
EditAction::make('edit')->label('Edit')->icon('heroicon-o-pencil-square')->url(fn($row) => route('users.edit', $row));
The url
method accepts either a string or a closure. When using a closure, the current record is passed as a
parameter, allowing you to generate dynamic URLs based on the record data.
Livewire Actions
Livewire actions trigger methods in your Livewire component when clicked:
DeleteAction::make('delete')->label('Delete')->icon('heroicon-o-trash')->action('deleteUser');
The action
method specifies the name of the method to call in your Livewire component. The record ID is automatically
passed to this method:
// In your Livewire component
public function deleteUser($id)
{
$user = User::find($id);
if ($user) {
$user->delete();
$this->notification()->success('User deleted successfully');
}
}
Action Styles
Actions can be displayed in different styles:
// Icon style (just the icon)
EditAction::make('edit')->icon('heroicon-o-pencil-square')->actionView('icon');
// Badge style (colored badge with text)
DeleteAction::make('delete')->label('Delete')->actionView('badge');
// Button style (full button with icon and text)
ViewAction::make('view')->label('View')->icon('heroicon-o-eye')->actionView('button');
// Default style (text link with optional icon)
CustomAction::make('custom')->label('Custom Action')->icon('heroicon-o-cog');
Customizing Action Appearance
Icons
You can set an icon for an action using the icon
method:
EditAction::make('edit')->icon('heroicon-o-pencil-square');
The package uses Blade Icons, so you can use any icon from the Heroicons set or other icon sets that you've installed.
Colors
You can customize the color of an action using the color
method:
DeleteAction::make('delete')->color('danger');
Available color options include:
primary
(default)secondary
success
danger
warning
info
Labels
You can set a label for an action using the label
method:
EditAction::make('edit')->label('Edit User');
The label is displayed as text for badge, button, and default styles, and as a tooltip for icon style.
Grouping Actions
If you have multiple actions, you can group them to save space:
ActionColumn::make('actions')
->groupActions()
->actions([EditAction::make('edit'), DeleteAction::make('delete'), ViewAction::make('view')]);
This will display a dropdown menu with all the actions.
Conditional Actions
You can conditionally show or hide actions based on the record data:
ActionColumn::make('actions')->actions([
EditAction::make('edit')->visible(fn($row) => $row->status === 'active'),
DeleteAction::make('delete')->visible(fn($row) => auth()->user()->can('delete', $row)),
]);
Confirmation Modals
For destructive or important actions, you can add confirmation modals to prevent accidental clicks:
DeleteAction::make('delete')
->label('Delete')
->icon('heroicon-o-trash')
->action('deleteUser')
->requiresConfirmation()
->modalHeading('Delete User')
->modalDescription('Are you sure you want to delete this user?')
->modalContent('This action cannot be undone.')
->modalConfirmationButtonLabel('Delete User');
Confirmation Modal Options
requiresConfirmation()
: Enables the confirmation modalmodalHeading()
: Sets the modal titlemodalDescription()
: Sets the modal description textmodalContent()
: Sets additional content in the modal bodymodalConfirmationButtonLabel()
: Customizes the confirmation button text
Dynamic Confirmation Content
All modal methods accept closures for dynamic content based on the record:
DeleteAction::make('delete')
->requiresConfirmation()
->modalHeading(fn($record) => "Delete {$record->name}")
->modalDescription(fn($record) => "Are you sure you want to delete {$record->name}?")
->modalConfirmationButtonLabel(fn($record) => "Delete {$record->name}");
HTML Content in Modals
You can use HTML content in modals by returning an HtmlString
:
use Illuminate\Support\HtmlString;
DeleteAction::make('delete')
->requiresConfirmation()
->modalConfirmationButtonLabel(fn($record) => new HtmlString("<strong>Delete {$record->name}</strong>"));
Creating Custom Actions
You can create custom actions by extending the BaseAction
class:
use Milenmk\LaravelSimpleDatatablesAndForms\Table\Actions\BaseAction;
class ArchiveAction extends BaseAction
{
public string|Closure|bool|null $icon = 'heroicon-o-archive-box';
public function label(string|array|null $value): self
{
$this->label = $value ?? __('Archive');
return $this;
}
}
Then use it in your table:
ActionColumn::make('actions')->actions([ArchiveAction::make('archive')->action('archiveRecord')]);
Best Practices
-
Use Descriptive Labels: Make sure your action labels clearly describe what the action does.
-
Add Icons: Icons help users quickly identify actions without having to read the labels.
-
Use Appropriate Styles: Choose the right style for each action based on its importance and frequency of use.
-
Group Related Actions: If you have many actions, group them to avoid cluttering the UI.
-
Add Confirmation for Destructive Actions: For actions like delete, consider adding a confirmation dialog.
-
Implement Proper Authorization: Make sure users can only see and use actions they have permission for.
-
Handle Errors Gracefully: In your Livewire action methods, handle errors and provide feedback to the user.
Example: Complete CRUD Implementation
Here's a complete example of implementing CRUD actions in a table:
// In your Livewire component
use Milenmk\LaravelSimpleDatatablesAndForms\Table\Columns\ActionColumn;
use Milenmk\LaravelSimpleDatatablesAndForms\Table\Actions\EditAction;
use Milenmk\LaravelSimpleDatatablesAndForms\Table\Actions\DeleteAction;
use Milenmk\LaravelSimpleDatatablesAndForms\Table\Actions\ViewAction;
public function table(Table $table): Table
{
return $table
->query(User::query())
->schema([
// Other columns...
ActionColumn::make('actions')
->label('Actions')
->actions([
ViewAction::make('view')
->icon('heroicon-o-eye')
->actionView('icon')
->url(fn($row) => route('users.show', $row)),
EditAction::make('edit')
->icon('heroicon-o-pencil-square')
->actionView('icon')
->url(fn($row) => route('users.edit', $row)),
DeleteAction::make('delete')
->icon('heroicon-o-trash')
->actionView('icon')
->action('deleteUser')
->requiresConfirmation()
->modalHeading('Delete User')
->modalDescription('Are you sure you want to delete this user?')
->modalConfirmationButtonLabel('Delete User'),
]),
]);
}
// Action handler method
public function deleteUser($id)
{
$user = User::find($id);
if (!$user) {
$this->notification()->error('User not found');
return;
}
try {
$user->delete();
$this->notification()->success('User deleted successfully');
} catch (\Exception $e) {
$this->notification()->error('Failed to delete user: ' . $e->getMessage());
}
}
This implementation provides a complete set of CRUD actions for managing users, with appropriate icons and styles for each action.
Laravel Simple Datatables And Forms provides flexible pagination options to help manage large datasets efficiently.
Basic Configuration
Pagination is enabled by default. You can configure it in the simple-datatables-and-forms.php
configuration file:
'pagination' => [
// Default items per page
'per_page' => 10,
// Available pagination options
'options' => [10, 25, 50, 100],
// Show pagination summary
'show_summary' => true,
// Enable pagination by default
'enabled' => true,
],
Enabling/Disabling Pagination
You can enable or disable pagination for a specific table:
public function table(Table $table): Table
{
return $table
->query(User::query())
->schema([
// Your columns here
])
->paginate(true); // Enable pagination (default)
// or
->paginate(false); // Disable pagination
}
Customizing Items Per Page
You can set the number of items to display per page:
public function table(Table $table): Table
{
return $table
->query(User::query())
->schema([
// Your columns here
])
->perPage(25); // Set items per page
}
Customizing Per Page Options
You can customize the available per page options by implementing the perPageOptions
method in your Livewire component:
public function perPageOptions(): array
{
return [5, 15, 30, 50, 100]; // Custom options
}
Pagination Summary
By default, the package shows a pagination summary that displays the current range of records being shown and the total number of records. You can customize this behavior in the configuration file:
'pagination' => [
// ...
'show_summary' => true, // Show pagination summary
],
Pagination Controls
The package provides standard pagination controls that allow users to navigate between pages. These controls include:
- First page button
- Previous page button
- Page number buttons
- Next page button
- Last page button
Customizing Pagination Appearance
You can customize the appearance of pagination controls by publishing the package views:
php artisan vendor:publish --tag=laravel-simple-datatables-and-forms-views
Then edit the pagination component in
resources/views/vendor/laravel-simple-datatables-and-forms/components/pagination.blade.php
.
Handling Pagination State
The pagination state is automatically managed by the package. When a user changes the page or items per page, the table is refreshed with the new data.
Pagination with Filters and Sorting
Pagination works seamlessly with filters and sorting. When a user applies a filter or sorts a column, the pagination is reset to the first page to avoid confusion.
Pagination with multiple table in one view
If you have multiple tables in one view file (presume as nested components i.e. each table comes from a separate Livewire component), changing the par page value from the dropdown will apply it for the current table. However, since it is saved as parameter in the URL, upon refreshing the page, all other tables will be affected too. To prevent this, use unique parameters inside each component like this:
#[Url(as: 'unique_value_per_page')]
public int $perPage = 10;
public function updatedPerPage(): void
{
$this->resetPage();
}
public function table(....
Performance Considerations
For large datasets, consider implementing the following optimizations:
-
Use Database Pagination: The package uses Laravel's built-in pagination, which is efficient for large datasets.
-
Index Your Columns: Make sure columns used for sorting are properly indexed in your database.
-
Limit Eager Loading: Be careful with eager loading relationships, as it can impact pagination performance.
-
Consider Caching: For very large datasets, consider implementing caching as described in the Caching documentation.
Example: Complete Pagination Implementation
Here's a complete example of implementing pagination in a table:
// In your Livewire component
public function table(Table $table): Table
{
return $table
->query(User::query())
->schema([
// Your columns here
])
->paginate(true) // Enable pagination
->perPage(15); // Set items per page
}
// Custom per page options
public function perPageOptions(): array
{
return [10, 15, 25, 50, 100];
}
This implementation provides a paginated table with custom per page options, allowing users to efficiently navigate through large datasets.
Laravel Simple Datatables And Forms provides comprehensive export functionality that allows users to export table data in multiple formats including CSV, Excel, and PDF.
Quick Start
Export functionality is automatically enabled when you use the HasTable
trait. Users will see an export button in the
table header that allows them to download data in their preferred format.
use Milenmk\LaravelSimpleDatatablesAndForms\Traits\HasTable;
class UserList extends Component
{
use HasTable; // Export functionality is automatically available
public function table(Table $table): Table
{
return $table->query(User::query())->schema([
TextColumn::make('name')->exportable(),
TextColumn::make('email')->exportable(),
// Other columns...
]);
}
}
Configuration
Global Export Settings
Configure export behavior in config/simple-datatables-and-forms.php
:
'export' => [
// Enable export functionality globally
'enable' => true,
// Available export formats
'formats' => ['csv', 'excel', 'xlsx', 'xls', 'pdf'],
// Default export format
'default_format' => 'csv',
// Maximum rows for export (0 for unlimited)
'max_rows' => 10000,
// Custom filename prefix
'filename_prefix' => 'export',
// Include timestamps in filenames
'include_timestamp' => true,
// Date format for timestamps
'timestamp_format' => 'Y-m-d_H-i-s',
],
Per-Table Export Configuration
You can customize export settings for individual tables:
public function table(Table $table): Table
{
return $table
->query(User::query())
->schema([/* columns */])
->export([
'formats' => ['csv', 'excel'], // Only allow CSV and Excel
'filename' => 'users_export', // Custom filename
'max_rows' => 5000, // Limit to 5000 rows
]);
}
Required Dependencies
For Excel Export (XLS/XLSX)
composer require phpoffice/phpspreadsheet
For PDF Export
composer require barryvdh/laravel-dompdf
Note: If these packages are not installed, the export will gracefully fall back to CSV format while maintaining the requested file extension.
Column Export Control
Making Columns Exportable
By default, all visible columns are included in exports. You can control this behavior:
TextColumn::make('name')
->exportable(), // Explicitly mark as exportable
TextColumn::make('email')
->exportable(false), // Exclude from exports
TextColumn::make('internal_notes')
->exportOnly(), // Only show in exports, not in table
TextColumn::make('formatted_date')
->hidden() // Hidden columns are excluded from exports
->exportable(), // Unless explicitly marked as exportable
Custom Export Values
Provide different values for export vs. display:
TextColumn::make('status')
->value(fn($row) => $row->status->getLabel()) // Display value
->exportValue(fn($row) => $row->status->value), // Export value
TextColumn::make('created_at')
->format(fn($value) => $value->diffForHumans()) // Display: "2 days ago"
->exportValue(fn($row) => $row->created_at->format('Y-m-d H:i:s')), // Export: "2024-01-15 14:30:00"
Export Formats
CSV Export
CSV is the default format and requires no additional dependencies:
// Automatically available - no configuration needed
Excel Export
Supports both XLS and XLSX formats:
public function table(Table $table): Table
{
return $table
->query(User::query())
->schema([/* columns */])
->export([
'formats' => ['xlsx', 'xls'],
'excel' => [
'sheet_name' => 'Users',
'include_headers' => true,
'auto_size_columns' => true,
],
]);
}
PDF Export
Generate PDF exports with custom styling:
public function table(Table $table): Table
{
return $table
->query(User::query())
->schema([/* columns */])
->export([
'formats' => ['pdf'],
'pdf' => [
'orientation' => 'landscape', // or 'portrait'
'paper_size' => 'A4',
'title' => 'User Report',
'include_logo' => true,
'logo_path' => public_path('images/logo.png'),
],
]);
}
Advanced Export Features
Custom Export Queries
Modify the query used for exports:
public function getExportQuery()
{
return User::query()
->with(['profile', 'roles']) // Include relationships for export
->where('status', 'active') // Only export active users
->orderBy('created_at', 'desc');
}
Export Preprocessing
Process data before export:
public function preprocessExportData($data)
{
return $data->map(function ($row) {
// Add calculated fields
$row->full_address = $row->address . ', ' . $row->city . ', ' . $row->country;
// Format sensitive data
$row->phone = $this->formatPhoneNumber($row->phone);
return $row;
});
}
Custom Export Headers
Define custom headers for exports:
public function getExportHeaders(): array
{
return [
'name' => 'Full Name',
'email' => 'Email Address',
'created_at' => 'Registration Date',
'status' => 'Account Status',
];
}
Export Events
Listen to export events for logging or additional processing:
// In your component
public function exportStarted($format, $filename)
{
Log::info("Export started: {$format} format, filename: {$filename}");
}
public function exportCompleted($format, $filename, $rowCount)
{
Log::info("Export completed: {$rowCount} rows exported to {$filename}");
}
public function exportFailed($format, $error)
{
Log::error("Export failed: {$format} format, error: {$error}");
}
Security Considerations
Access Control
Implement proper authorization for exports:
public function table(Table $table): Table
{
return $table
->query(User::query())
->schema([/* columns */])
->export([
'authorize' => fn() => auth()->user()->can('export-users'),
]);
}
Data Sanitization
Sanitize sensitive data in exports:
TextColumn::make('ssn')
->exportValue(fn($row) => '***-**-' . substr($row->ssn, -4)),
TextColumn::make('credit_card')
->exportValue(fn($row) => '**** **** **** ' . substr($row->credit_card, -4)),
Performance Optimization
Large Dataset Handling
For large datasets, consider chunking:
public function table(Table $table): Table
{
return $table
->query(User::query())
->schema([/* columns */])
->export([
'chunk_size' => 1000, // Process 1000 rows at a time
'memory_limit' => '512M', // Increase memory limit
'timeout' => 300, // 5 minute timeout
]);
}
Background Exports
For very large exports, consider using queued jobs:
public function table(Table $table): Table
{
return $table
->query(User::query())
->schema([/* columns */])
->export([
'queue' => true, // Use queue for large exports
'queue_threshold' => 10000, // Queue if more than 10k rows
'notification_email' => auth()->user()->email,
]);
}
Troubleshooting
Common Issues
Memory limit exceeded:
// Increase memory limit in export configuration
'export' => [
'memory_limit' => '1G',
'chunk_size' => 500,
],
Timeout errors:
// Increase timeout for large exports
'export' => [
'timeout' => 600, // 10 minutes
],
Missing dependencies:
# Install required packages
composer require phpoffice/phpspreadsheet barryvdh/laravel-dompdf
Permission errors:
// Ensure proper file permissions
'export' => [
'temp_path' => storage_path('app/exports'),
],
Debug Mode
Enable debug mode for troubleshooting:
'export' => [
'debug' => true, // Enable detailed error logging
],
Examples
Basic Export Setup
public function table(Table $table): Table
{
return $table
->query(User::query())
->schema([
TextColumn::make('name')->exportable(),
TextColumn::make('email')->exportable(),
TextColumn::make('created_at')
->format(fn($value) => $value->diffForHumans())
->exportValue(fn($row) => $row->created_at->format('Y-m-d')),
]);
}
Advanced Export Configuration
public function table(Table $table): Table
{
return $table
->query(User::query())
->schema([/* columns */])
->export([
'formats' => ['csv', 'xlsx', 'pdf'],
'filename' => 'users_' . date('Y-m-d'),
'authorize' => fn() => auth()->user()->can('export-users'),
'excel' => [
'sheet_name' => 'Active Users',
'auto_size_columns' => true,
],
'pdf' => [
'orientation' => 'landscape',
'title' => 'User Directory',
],
]);
}
Customizing Export Formats
You can customize which export formats are available for a specific table by passing them to the table component:
// In your Livewire component
public function render()
{
return view('livewire.my-table', [
'exportFormats' => ['csv', 'xlsx', 'pdf'], // Specify available formats
]);
}
Implementing Custom Export Logic
If you need custom export logic, you can override the export
method in your Livewire component:
public function export(string $format = 'csv')
{
// Your custom export logic here
// Example: Add additional columns or transform data
$table = app(\Milenmk\LaravelSimpleDatatablesAndForms\Table\Table::class);
$this->table($table);
$query = $table->getQuery();
// Apply your custom transformations
// Then use the export service
$exportService = app(\Milenmk\LaravelSimpleDatatablesAndForms\Services\ExportService::class);
// Choose the appropriate export method based on format
return match($format) {
'excel' => $exportService->toExcel($query, $table),
'xlsx' => $exportService->toXlsx($query, $table),
'xls' => $exportService->toXls($query, $table),
'pdf' => $exportService->toPdf($query, $table),
default => $exportService->toCsv($query, $table),
};
}
Export Formats
CSV Export
CSV export is the default and most lightweight option. It exports data as a comma-separated values file. This format doesn't require any additional packages.
Excel Export (XLS and XLSX)
The package supports both older XLS format and newer XLSX format for Excel exports:
- XLS: The older Excel format, compatible with Excel 97-2003
- XLSX: The modern Excel format based on Open XML, compatible with Excel 2007 and later
Both formats require the PhpSpreadsheet package:
composer require phpoffice/phpspreadsheet
If PhpSpreadsheet is not installed, the export will fall back to CSV format with an Excel extension.
PDF Export
PDF export creates a portable document format file that maintains consistent formatting across different devices and platforms. This format is ideal for reports that need to be printed or shared formally.
PDF export requires the Laravel DomPDF package:
composer require barryvdh/laravel-dompdf
If DomPDF is not installed, the export will fall back to CSV format with a PDF extension.
Customizing the Export Button
You can customize the export button by publishing the component view:
php artisan vendor:publish --tag=laravel-simple-datatables-and-forms-views
Then edit the resources/views/vendor/laravel-simple-datatables-and-forms/components/export.blade.php
file.
Customizing Export Filenames
You can customize the filename of exported files by implementing the exportFilename
method in your Livewire component:
public function exportFilename(string $format): string
{
return 'users-report-' . date('Y-m-d') . '.' . $format;
}
Customizing PDF Template
The package includes a default PDF template at resources/views/exports/pdf.blade.php
. You can publish and customize
this template:
php artisan vendor:publish --tag=laravel-simple-datatables-and-forms-views
Then edit the resources/views/vendor/laravel-simple-datatables-and-forms/exports/pdf.blade.php
file to match your
design requirements.
Customizing the Export Button
You can customize the export button by publishing the component view:
php artisan vendor:publish --tag=laravel-simple-datatables-and-forms-views
Then edit the resources/views/vendor/laravel-simple-datatables-and-forms/components/export.blade.php
file.
Customizing Export Filenames
You can customize the filename of exported files by implementing the exportFilename
method in your Livewire component:
public function exportFilename(string $format): string
{
return 'users-report-' . date('Y-m-d') . '.' . $format;
}
Laravel Simple Datatables And Forms implements caching strategies to improve performance, especially for tables with complex configurations or large datasets.
Cache Configuration
You can configure caching behavior in the simple-datatables-and-forms.php
configuration file:
'cache' => [
// Enable caching for column definitions
'enable' => true,
// Cache lifetime in seconds (default: 1 hour)
'lifetime' => 3600,
// Cache key prefix
'prefix' => 'simple_datatables_',
],
What Gets Cached
The package caches several types of data:
- Column Definitions: The structure and configuration of table columns
- Filter Options: Options for select filters that don't change frequently
- Query Results: For specific combinations of filters, sorting, and pagination
Using the Cache Service
You can use the cache service directly in your components:
use Milenmk\LaravelSimpleDatatablesAndForms\Services\CacheService;
public function someMethod()
{
$cacheService = app(CacheService::class);
// Get cached data or compute it if not cached
$result = $cacheService->remember('my_cache_key', function() {
// This will only execute if the data is not in cache
return $this->expensiveOperation();
});
// Store data in cache
$cacheService->put('another_key', $value);
// Get data from cache (with default fallback)
$data = $cacheService->get('some_key', 'default_value');
// Remove data from cache
$cacheService->forget('some_key');
// Clear all cache for this package
$cacheService->clear();
}
Cache Keys
Cache keys are automatically prefixed with the value from the configuration. You should use descriptive keys that include relevant parameters:
$key = "users_table_columns_{$this->componentName}";
$filterKey = "filter_options_department_{$departmentId}";
$queryKey = "users_query_page{$page}_sort{$sortField}_{$sortDir}_search{$search}";
Cache Invalidation
Cache is automatically invalidated when:
- The cache lifetime expires
- You manually clear the cache
Automatic Cache Invalidation
You can set up automatic cache invalidation by implementing model observers or using Laravel's model events:
// In your AppServiceProvider or a dedicated observer
public function boot()
{
User::observe(UserObserver::class);
// Or using closures
User::created(function ($user) {
app(CacheService::class)->forget('users_table_data');
});
User::updated(function ($user) {
app(CacheService::class)->forget('users_table_data');
});
User::deleted(function ($user) {
app(CacheService::class)->forget('users_table_data');
});
}
Manual Cache Invalidation
You should manually clear relevant cache entries when:
- Table structure changes
- Filter options change
- Underlying data changes significantly
// In your model observer or event listener
public function saved(User $user)
{
app(CacheService::class)->forget('users_table_data');
}
// In your controller after a bulk operation
public function bulkDelete()
{
// Perform bulk delete
User::whereIn('id', $ids)->delete();
// Clear cache
app(CacheService::class)->forget('users_table_data');
}
Selective Cache Invalidation
For more granular control, you can invalidate specific cache entries based on the affected data:
// In your model observer
public function saved(User $user)
{
$cacheService = app(CacheService::class);
// Clear specific user cache
$cacheService->forget("user_data_{$user->id}");
// Clear department-specific cache if department changed
if ($user->isDirty('department_id')) {
$cacheService->forget("department_users_{$user->department_id}");
if ($user->getOriginal('department_id')) {
$cacheService->forget("department_users_{$user->getOriginal('department_id')}");
}
}
// Only clear global cache for significant changes
if ($user->isDirty(['role', 'is_active'])) {
$cacheService->forget('users_table_data');
}
}
Cache Tags (Not Currently Implemented)
Important Note: While Laravel supports cache tags with certain cache drivers (like Redis or Memcached), the current
implementation of CacheService
in this package does not include tag support. The following is an example of how you
might implement tag support if needed:
// This functionality is NOT currently available in the package
// You would need to extend the CacheService class to implement this
// Example of how you might implement tag support:
public function someMethod()
{
// Using Laravel's cache facade directly for tags
Cache::tags(['users', "user-{$userId}"])->put('key', $value, 3600);
// Retrieve with tags
$value = Cache::tags(['users', "user-{$userId}"])->get('key');
// Invalidate by tag
Cache::tags(['users'])->flush(); // Clear all user-related cache
Cache::tags(["user-{$userId}"])->flush(); // Clear specific user cache
}
If you need tag support, consider extending the CacheService
class to add this functionality or use Laravel's Cache
facade directly in your application code.
Performance Tips
-
Adjust Cache Lifetime: Set appropriate cache lifetime based on how frequently your data changes.
-
Cache Selectively: Cache expensive operations but don't cache everything.
-
Use Query Caching: For read-heavy applications, consider using Laravel's query cache in addition to this package' s caching.
-
Monitor Cache Size: Large caches can consume significant memory. Monitor your cache size and adjust as needed.
-
Consider Redis: For production environments, consider using Redis as your cache driver for better performance.
No content for this heading.
This guide will help you create your first dynamic form using Laravel Simple Datatables And Forms.
Prerequisites
Before you begin, ensure you have:
- Laravel 10.x or higher installed
- Livewire 3.x installed and configured
- Laravel Simple Datatables And Forms package installed
Creating Your First Form
Method 1: Using Artisan Command (Recommended)
The fastest way to create a form is using the provided Artisan command:
# Basic form generation
php artisan make:milenmk-form Admin/CreateUser User
# Generate with auto-generated fields based on model
php artisan make:milenmk-form Admin/CreateUser User --generate
# Generate for editing existing records
php artisan make:milenmk-form Admin/EditUser User --generate
This creates:
- A Livewire component at
app/Livewire/Admin/CreateUser.php
- A Blade view at
resources/views/livewire/admin/create-user.blade.php
- Auto-generated form fields (when using
--generate
flag)
Method 2: Manual Creation
Step 1: Create a Livewire Component
<?php
namespace App\Livewire\Admin;
use Livewire\Component;
use Milenmk\LaravelSimpleDatatablesAndForms\Traits\HasForm;
use Milenmk\LaravelSimpleDatatablesAndForms\Form\Form;
use Milenmk\LaravelSimpleDatatablesAndForms\Form\Fields\InputField;
use Milenmk\LaravelSimpleDatatablesAndForms\Form\Fields\SelectField;
use Milenmk\LaravelSimpleDatatablesAndForms\Form\Fields\TextareaField;
use Milenmk\LaravelSimpleDatatablesAndForms\Form\Fields\ToggleField;
use App\Models\User;
class CreateUser extends Component
{
use HasForm;
public function mount(): void
{
$this->mountForm();
}
public function form(Form $form): Form
{
return $form
->model(User::class)
->heading([
'title' => 'Create New User',
'description' => 'Add a new user to the system with the required information.',
])
->columns(2) // Two-column layout
->schema([
InputField::make('name')
->label('Full Name')
->placeholder('Enter the user\'s full name')
->required()
->columnSpan(2), // Span across both columns
InputField::make('email')->label('Email Address')->email()->placeholder('user@example.com')->required(),
InputField::make('phone')->label('Phone Number')->tel()->placeholder('+1 (555) 123-4567'),
SelectField::make('role')
->label('User Role')
->options([
'admin' => 'Administrator',
'manager' => 'Manager',
'user' => 'Regular User',
])
->emptyOption('Select a role')
->required(),
SelectField::make('department')
->label('Department')
->options([
'hr' => 'Human Resources',
'it' => 'Information Technology',
'sales' => 'Sales',
'marketing' => 'Marketing',
])
->emptyOption('Select department'),
TextareaField::make('bio')
->label('Biography')
->placeholder('Brief description about the user...')
->rows(4)
->columnSpan(2),
ToggleField::make('is_active')
->label('Active User')
->onLabel('Active')
->offLabel('Inactive')
->default(true),
ToggleField::make('email_verified')
->label('Email Verified')
->onLabel('Verified')
->offLabel('Unverified')
->default(false),
]);
}
public function save()
{
// Validate the form data
$this->validate();
// Create the user
$user = User::create($this->formData);
// Flash success message
session()->flash('message', 'User created successfully!');
// Redirect or reset form
return redirect()->route('users.index');
}
public function render()
{
return view('livewire.admin.create-user');
}
}
Step 2: Create the Blade View
Create resources/views/livewire/admin/create-user.blade.php
:
<div class="mx-auto max-w-4xl">
<div class="rounded-lg bg-white shadow-lg dark:bg-gray-800">
<div class="p-6">
@if (session()->has('message'))
<div class="mb-4 rounded border border-green-400 bg-green-100 p-4 text-green-700">
{{ session('message') }}
</div>
@endif
<form wire:submit.prevent="save">
{{ $this->form }}
<div class="mt-6 flex justify-end space-x-3">
<button
type="button"
onclick="window.history.back()"
class="rounded-md bg-gray-300 px-4 py-2 text-gray-700 transition-colors hover:bg-gray-400"
>
Cancel
</button>
<button
type="submit"
class="rounded-md bg-blue-600 px-4 py-2 text-white transition-colors hover:bg-blue-700"
>
Create User
</button>
</div>
</form>
</div>
</div>
</div>
Step 3: Add Routes
Add routes to display and handle your form:
// routes/web.php
use App\Livewire\Admin\CreateUser;
Route::get('/admin/users/create', CreateUser::class)->name('admin.users.create');
Understanding Form Structure
Basic Form Configuration
public function form(Form $form): Form
{
return $form
->model(User::class) // Associated model
->heading([ // Form title and description
'title' => 'Create User',
'description' => 'Add a new user to the system.',
])
->columns(2) // Number of columns
->schema([ // Field definitions
// Fields go here
]);
}
Form Layout Options
public function form(Form $form): Form
{
return $form
->model(User::class)
->columns(3) // 3-column layout
->columnSpanFull() // Make all fields span full width by default
->compact() // Reduce spacing
->schema([
InputField::make('name')
->columnSpan(2), // This field spans 2 columns
InputField::make('email')
->columnSpan(1), // This field spans 1 column
TextareaField::make('bio')
->columnSpanFull(), // This field spans all columns
]);
}
Common Field Types
Input Fields
// Text input
InputField::make('name')
->label('Full Name')
->placeholder('Enter name')
->required(),
// Email input with validation
InputField::make('email')
->email()
->required(),
// Password input
InputField::make('password')
->password()
->minLength(8)
->required(),
// Number input with constraints
InputField::make('age')
->number()
->min(18)
->max(100),
// Date input
InputField::make('birth_date')
->date()
->before('today'),
// URL input
InputField::make('website')
->url()
->placeholder('https://example.com'),
Select Fields
// Basic select
SelectField::make('country')
->options([
'us' => 'United States',
'uk' => 'United Kingdom',
'ca' => 'Canada',
])
->emptyOption('Select country')
->required(),
// Multiple select
SelectField::make('skills')
->multiple()
->options([
'php' => 'PHP',
'javascript' => 'JavaScript',
'python' => 'Python',
]),
// Using Enum
SelectField::make('status')
->options(UserStatus::class), // Enum class
Other Field Types
// Textarea
TextareaField::make('description')
->rows(5)
->maxLength(1000)
->autosize(),
// Checkbox
CheckboxField::make('terms')
->label('I agree to the terms and conditions')
->required(),
// Toggle switch
ToggleField::make('is_active')
->label('Active')
->onLabel('Enabled')
->offLabel('Disabled')
->default(true),
Form Validation
Field-Level Validation
InputField::make('email')
->email()
->rules(['required', 'email', 'unique:users,email'])
->required(), // Visual indicator
InputField::make('age')
->number()
->rules(['required', 'integer', 'min:18', 'max:120']),
Custom Validation Messages
public function getValidationMessages(): array
{
return [
'formData.email.required' => 'The email address is required.',
'formData.email.unique' => 'This email address is already taken.',
'formData.age.min' => 'You must be at least 18 years old.',
];
}
Real-time Validation
public function form(Form $form): Form
{
return $form
->model(User::class)
->realtimeValidation() // Enable real-time validation
->schema([
// fields...
]);
}
Working with Models
Creating Records
public function save()
{
$this->validate();
$user = User::create($this->formData);
session()->flash('message', 'User created successfully!');
return redirect()->route('users.index');
}
Editing Existing Records
public User $user;
public function mount(User $user): void
{
$this->user = $user;
$this->mountForm();
$this->loadFormModel($user);
}
public function save()
{
$this->validate();
$this->user->update($this->formData);
session()->flash('message', 'User updated successfully!');
}
Form Sections
Organize your form with sections:
use Milenmk\LaravelSimpleDatatablesAndForms\Form\Components\Section;
public function form(Form $form): Form
{
return $form
->model(User::class)
->schema([
Section::make('Personal Information')
->description('Basic user details')
->schema([
InputField::make('name')->required(),
InputField::make('email')->email()->required(),
InputField::make('phone')->tel(),
]),
Section::make('Account Settings')
->description('User permissions and preferences')
->schema([
SelectField::make('role')->required(),
ToggleField::make('is_active')->default(true),
ToggleField::make('email_verified'),
]),
]);
}
Styling and Customization
Field Icons
InputField::make('email')
->prefixIcon('heroicon-o-envelope')
->suffixIcon('heroicon-o-at-symbol'),
InputField::make('phone')
->prefixIcon('heroicon-o-phone'),
Field Help Text
InputField::make('password')
->password()
->helperText('Password must be at least 8 characters long')
->minLength(8),
Conditional Fields
SelectField::make('user_type')
->options(['individual' => 'Individual', 'business' => 'Business'])
->reactive(), // Make field reactive
InputField::make('company_name')
->label('Company Name')
->visible(fn() => $this->formData['user_type'] === 'business'),
Next Steps
Now that you have a basic form working, explore these advanced features:
- Field Types - Complete reference for all available field types
- Form Validation - Advanced validation techniques
- Form Sections - Organize complex forms with sections
- Model Binding - Advanced model integration
- Form Examples - Practical examples and advanced features
Troubleshooting
Common Issues
Form not displaying:
- Ensure you've called
$this->mountForm()
in yourmount()
method - Check that you've included the form assets in your layout
Validation not working:
- Verify that you're calling
$this->validate()
in your save method - Check that validation rules are properly defined
Data not saving:
- Ensure your model has the correct
$fillable
properties - Check that form field names match your model attributes
Styling issues:
- Ensure Tailwind CSS is properly configured
- Check that the package views are included in your Tailwind content paths
For more troubleshooting help, see our GitHub Issues page.
Quick Reference & Common Patterns
Installation & Setup Commands
# Install package
composer require milenmk/laravel-simple-datatables-and-forms
# Publish assets
php artisan simple-datatables-and-forms:publish-assets
# Publish configuration (optional)
php artisan vendor:publish --tag=laravel-simple-datatables-and-forms-config
# Generate form component
php artisan make:milenmk-form CreateUser User --generate
# Generate nested form component
php artisan make:milenmk-form Admin/CreateUser User --generate
Essential Component Structure
use Milenmk\LaravelSimpleDatatablesAndForms\Traits\HasForm;
use Milenmk\LaravelSimpleDatatablesAndForms\Form\Form;
use Milenmk\LaravelSimpleDatatablesAndForms\Form\Fields\InputField;
class CreateUser extends Component
{
use HasForm;
public function mount(): void
{
$this->mountForm();
}
public function form(Form $form): Form
{
return $form
->model(User::class)
->schema([InputField::make('name')->required(), InputField::make('email')->email()->required()]);
}
public function save()
{
$this->validate();
User::create($this->formData);
$this->notification()->success('User created successfully!');
}
}
Common Field Types Cheat Sheet
// Input Fields
InputField::make('name')
->label('Full Name')
->placeholder('Enter your name')
->required()
->maxLength(255),
InputField::make('email')
->email()
->required()
->placeholder('user@example.com'),
InputField::make('password')
->password()
->minLength(8)
->required(),
InputField::make('age')
->numeric()
->min(18)
->max(100),
InputField::make('website')
->url()
->placeholder('https://example.com'),
InputField::make('birth_date')
->date()
->required(),
// Select Fields
SelectField::make('role')
->options(['admin' => 'Admin', 'user' => 'User'])
->emptyOption('Select role')
->required(),
SelectField::make('permissions')
->options(['read' => 'Read', 'write' => 'Write', 'delete' => 'Delete'])
->multiple()
->required(),
SelectField::make('user_id')
->relationship('user', 'name')
->searchable()
->required(),
// Textarea Fields
TextareaField::make('bio')
->label('Biography')
->rows(4)
->maxLength(500)
->placeholder('Tell us about yourself...'),
TextareaField::make('description')
->autoResize()
->minRows(3)
->maxRows(10),
// Toggle & Checkbox Fields
ToggleField::make('is_active')
->label('Active')
->default(true)
->onLabel('Active')
->offLabel('Inactive'),
CheckboxField::make('terms')
->label('I agree to the terms and conditions')
->required(),
CheckboxField::make('interests')
->options([
'sports' => 'Sports',
'music' => 'Music',
'travel' => 'Travel',
])
->multiple(),
Form Layout & Styling
// Column layouts
->columns(2) // Two-column form
// Field spanning multiple columns
InputField::make('description')
->columnSpan(2), // Spans 2 columns
// Field spanning full width
InputField::make('notes')
->columnSpanFull(), // Spans all columns
// Responsive columns
->columns([
'sm' => 1,
'md' => 2,
'lg' => 3,
])
// Field with custom styling
InputField::make('amount')
->prefix('$')
->suffix('USD')
->placeholder('0.00'),
// Field with help text
InputField::make('username')
->helperText('Must be unique and contain only letters and numbers')
->required(),
Form Sections
use Milenmk\LaravelSimpleDatatablesAndForms\Form\Components\Section;
->schema([
Section::make('Personal Information')
->description('Basic personal details')
->schema([
InputField::make('first_name')->required(),
InputField::make('last_name')->required(),
InputField::make('email')->email()->required(),
])
->columns(2),
Section::make('Account Settings')
->schema([
SelectField::make('role')->required(),
ToggleField::make('is_active')->default(true),
]),
])
Validation Quick Reference
// Basic validation rules
InputField::make('name')->required(),
InputField::make('email')->email()->required(),
InputField::make('age')->numeric()->min(18)->max(100),
InputField::make('username')->minLength(3)->maxLength(20),
InputField::make('website')->url(),
InputField::make('birth_date')->date()->before('today'),
// Custom validation in component
protected function rules(): array
{
return [
'formData.username' => ['required', 'unique:users,username'],
'formData.email' => ['required', 'email', 'unique:users,email'],
'formData.password' => ['required', 'min:8', 'confirmed'],
];
}
// Custom validation messages
protected function messages(): array
{
return [
'formData.username.unique' => 'This username is already taken.',
'formData.email.unique' => 'This email is already registered.',
];
}
// Real-time validation
InputField::make('email')
->email()
->required()
->live(), // Validates on input change
// Debounced validation
InputField::make('username')
->required()
->live(onBlur: true), // Validates on blur
Model Binding & Data Handling
// Create form
public function form(Form $form): Form
{
return $form
->model(User::class)
->schema([...]);
}
// Edit form
public function mount(User $user): void
{
$this->mountForm($user);
}
public function form(Form $form): Form
{
return $form
->model($this->record ?? User::class)
->schema([...]);
}
// Save with relationships
public function save()
{
$this->validate();
$user = User::create($this->formData);
// Sync many-to-many relationships
if (isset($this->formData['roles'])) {
$user->roles()->sync($this->formData['roles']);
}
$this->notification()->success('User created successfully!');
}
Conditional Logic
// Show field based on another field's value
InputField::make('company')
->visible(fn() => $this->formData['user_type'] === 'business'),
// Show field based on multiple conditions
InputField::make('tax_id')
->visible(fn() =>
$this->formData['user_type'] === 'business' &&
$this->formData['country'] === 'US'
),
// Dynamic select options
SelectField::make('city')
->options(fn() => $this->getCitiesForCountry($this->formData['country'] ?? null))
->searchable(),
Common Troubleshooting Solutions
// Fix: Form not saving
public function save()
{
$this->validate(); // Don't forget this!
User::create($this->formData);
}
// Fix: Form not displaying
public function mount(): void
{
$this->mountForm(); // Don't forget this!
}
// Fix: Validation not working
protected function rules(): array
{
return [
'formData.email' => ['required', 'email'], // Use formData prefix
];
}
// Fix: Field not reactive
SelectField::make('country')
->live() // Make field reactive for conditional logic
->options($countries),
Form Notifications
// Success notification
$this->notification()->success('Form saved successfully!');
// Error notification
$this->notification()->error('Please fix the errors below.');
// Warning notification
$this->notification()->warning('Some fields need attention.');
// Info notification
$this->notification()->info('Form auto-saved.');
The Laravel Simple Datatables And Forms package includes comprehensive form functionality, similar to Filament Forms, allowing you to easily create dynamic, configurable forms in your Livewire components.
Quick Start
1. Use the HasForm Trait
Add the HasForm
trait to your Livewire component:
<?php
namespace App\Livewire;
use Livewire\Component;
use Milenmk\LaravelSimpleDatatablesAndForms\Traits\HasForm;
use Milenmk\LaravelSimpleDatatablesAndForms\Form\Form;
class CreateUser extends Component
{
use HasForm;
public function mount(): void
{
$this->mountForm();
}
public function form(Form $form): Form
{
return $form->schema([
// Your form fields here
]);
}
public function render()
{
return view('livewire.create-user');
}
}
2. Add Form to Your View
In your Blade template:
<form wire:submit.prevent="save">
{{ $this->form }}
<button type="submit">Save</button>
</form>
Available Form Fields
InputField
use Milenmk\LaravelSimpleDatatablesAndForms\Form\Fields\InputField;
InputField::make('name')
->label('Full Name')
->placeholder('Enter your name')
->required()
->maxLength(255),
// Different input types
InputField::make('email')->email(),
InputField::make('password')->password(),
InputField::make('age')->number()->min(0)->max(120),
InputField::make('website')->url(),
InputField::make('phone')->tel(),
InputField::make('birth_date')->date(),
InputField::make('start_time')->time(),
InputField::make('appointment')->datetime(),
SelectField
use Milenmk\LaravelSimpleDatatablesAndForms\Form\Fields\SelectField;
SelectField::make('country')
->label('Country')
->options([
'us' => 'United States',
'uk' => 'United Kingdom',
'ca' => 'Canada',
])
->emptyOption('Select a country')
->required(),
// Multiple selection
SelectField::make('skills')
->multiple()
->options($skillsArray),
// Using Enum
SelectField::make('status')
->options(UserStatus::class), // Enum class
CheckboxField
use Milenmk\LaravelSimpleDatatablesAndForms\Form\Fields\CheckboxField;
CheckboxField::make('terms')
->label('I agree to the terms and conditions')
->required(),
CheckboxField::make('newsletter')
->label('Subscribe to newsletter')
->checkedValue(1)
->uncheckedValue(0)
->inline(),
ToggleField
use Milenmk\LaravelSimpleDatatablesAndForms\Form\Fields\ToggleField;
ToggleField::make('is_active')
->label('Active')
->onLabel('Enabled')
->offLabel('Disabled')
->color('green')
->size('lg'),
TextareaField
use Milenmk\LaravelSimpleDatatablesAndForms\Form\Fields\TextareaField;
TextareaField::make('description')
->label('Description')
->rows(5)
->maxLength(1000)
->autosize()
->placeholder('Enter description...'),
Form Configuration
Setting Form Columns
public function form(Form $form): Form
{
return $form
->columns(3) // 3 columns layout
->schema([
// fields...
]);
}
Form Heading
public function form(Form $form): Form
{
return $form
->heading([
'title' => 'User Profile',
'description' => 'Manage your profile information.',
])
->schema([
// fields...
]);
}
Working with Models
Loading Model Data
public function mount(User $user = null): void
{
$this->mountForm();
if ($user->exists) {
$this->loadFormModel($user);
}
}
Saving Data
public function save(): void
{
$validatedData = $this->validate($this->getValidationRulesFromForm());
if ($this->formModel) {
// Update existing model
$this->formModel->update($this->formData);
} else {
// Create new model
User::create($this->formData);
}
session()->flash('message', 'Saved successfully!');
}
Generating Forms with Artisan
Generate a new form component:
php artisan make:milenmk-form Admin/CreateUser create User
# With auto-generated fields
php artisan make:milenmk-form Admin/CreateUser create User --generate
This creates:
- A Livewire component with the HasForm trait
- A Blade view template
- Auto-generated form fields (when using --generate)
Configuration
The form behavior can be configured in config/simple-datatables-and-forms.php
:
'form' => [
'theme' => 'light', // 'light', 'dark', 'auto'
'columns' => 2, // Default form columns
'client_validation' => true,
'realtime_validation' => false,
// ... other options
],
Styling
The package includes default Tailwind CSS styles. You can customize the appearance by:
- Publishing the views:
php artisan vendor:publish --tag=laravel-simple-datatables-and-forms-views
- Customizing the CSS classes in the published view files
- Adding custom CSS to your application
See Also
- Field Types - Detailed documentation for all field types
- Form Validation - Validation rules and techniques
- Form Sections - Organizing forms with sections
- Model Binding - Advanced model integration
This document provides comprehensive information about all available form field types in Laravel Simple Datatables And Forms.
InputField
The most versatile field type for various input types.
Basic Usage
use Milenmk\LaravelSimpleDatatablesAndForms\Form\Fields\InputField;
InputField::make('name')->label('Full Name')->placeholder('Enter your name')->required();
Input Types
// Text input (default)
InputField::make('name'),
// Email input with validation
InputField::make('email')->email(),
// Password input (hidden)
InputField::make('password')->password(),
// Number input with min/max
InputField::make('age')->number()->min(0)->max(120),
// URL input
InputField::make('website')->url(),
// Telephone input
InputField::make('phone')->tel(),
// Date input
InputField::make('birth_date')->date(),
// Time input
InputField::make('start_time')->time(),
// Datetime input
InputField::make('appointment')->datetime(),
// Search input
InputField::make('search_term')->search(),
Available Methods
InputField::make('field_name')
->label('Field Label') // Field label
->placeholder('Enter value...') // Placeholder text
->default('default_value') // Default value
->required() // Mark as required
->disabled() // Disable the field
->readonly() // Make read-only
->maxLength(255) // Maximum character length
->minLength(3) // Minimum character length
->pattern('[A-Za-z]+') // Regex pattern
->step(0.01) // Step for number inputs
->min(0) // Minimum value for numbers
->max(100) // Maximum value for numbers
->helperText('Additional information') // Helper text below field
->columnSpan('2') // Column span in grid
->attributes(['data-custom' => 'value']); // Custom HTML attributes
SelectField
Dropdown selection field with single or multiple options.
Basic Usage
use Milenmk\LaravelSimpleDatatablesAndForms\Form\Fields\SelectField;
SelectField::make('country')
->label('Country')
->options([
'us' => 'United States',
'uk' => 'United Kingdom',
'ca' => 'Canada',
])
->required();
Multiple Selection
SelectField::make('skills')
->label('Skills')
->multiple()
->options([
'php' => 'PHP',
'javascript' => 'JavaScript',
'python' => 'Python',
]);
Using Enums
enum UserStatus: string
{
case ACTIVE = 'active';
case INACTIVE = 'inactive';
case PENDING = 'pending';
}
SelectField::make('status')->options(UserStatus::class);
Available Methods
SelectField::make('field_name')
->label('Field Label')
->options($optionsArray) // Options array or Enum class
->emptyOption('Choose an option...') // Empty option text
->multiple() // Allow multiple selections
->searchable() // Make options searchable
->default('default_value') // Default selection
->disabled() // Disable the field
->required() // Mark as required
->helperText('Choose from the list') // Helper text
->columnSpan('2'); // Column span
CheckboxField
Simple checkbox for boolean values or consent.
Basic Usage
use Milenmk\LaravelSimpleDatatablesAndForms\Form\Fields\CheckboxField;
CheckboxField::make('terms')->label('I agree to the terms and conditions')->required();
Custom Values
CheckboxField::make('newsletter')
->label('Subscribe to newsletter')
->checkedValue(1) // Value when checked
->uncheckedValue(0) // Value when unchecked
->default(true); // Default checked state
Available Methods
CheckboxField::make('field_name')
->label('Checkbox Label')
->checkedValue('yes') // Value when checked (default: 1)
->uncheckedValue('no') // Value when unchecked (default: 0)
->inline() // Display inline
->default(true) // Default checked state
->disabled() // Disable checkbox
->required() // Mark as required
->helperText('Check to confirm'); // Helper text
ToggleField
Modern toggle switch for boolean values.
Basic Usage
use Milenmk\LaravelSimpleDatatablesAndForms\Form\Fields\ToggleField;
ToggleField::make('is_active')
->label('Active Status')
->default(true)
Custom Labels and Colors
ToggleField::make('notifications')
->label('Email Notifications')
->onLabel('Enabled')
->offLabel('Disabled')
->color('green')
->size('lg');
Available Methods
ToggleField::make('field_name')
->label('Toggle Label')
->onLabel('On') // Label for on state
->offLabel('Off') // Label for off state
->onValue('enabled') // Value when on (default: 1)
->offValue('disabled') // Value when off (default: 0)
->color('green') // Color: 'primary', 'green', 'red'
->size('lg') // Size: 'sm', 'md', 'lg'
->default(true) // Default state
->disabled() // Disable toggle
->helperText('Toggle to change'); // Helper text
TextareaField
Multi-line text input for longer content.
Basic Usage
use Milenmk\LaravelSimpleDatatablesAndForms\Form\Fields\TextareaField;
TextareaField::make('description')->label('Description')->placeholder('Enter description...')->rows(5);
Auto-resizing
TextareaField::make('content')
->label('Content')
->autosize() // Auto-resize based on content
->maxLength(1000); // Character limit
Available Methods
TextareaField::make('field_name')
->label('Textarea Label')
->placeholder('Enter text...') // Placeholder text
->rows(4) // Number of rows
->cols(50) // Number of columns
->maxLength(500) // Maximum character length
->minLength(10) // Minimum character length
->autosize() // Auto-resize based on content
->disabled() // Disable textarea
->readonly() // Make read-only
->required() // Mark as required
->default('Default content') // Default content
->helperText('Provide detailed info') // Helper text
->columnSpan('full'); // Column span
Common Field Properties
All fields share these common properties and methods:
Validation
// Basic validation
InputField::make('email')
->rules(['required', 'email', 'unique:users'])
// Custom validation messages
InputField::make('name')
->rules(['required', 'min:3'])
->validationMessages([
'required' => 'Name is required',
'min' => 'Name must be at least 3 characters'
])
Conditional Display
InputField::make('other_reason')->label('Other Reason')->visible(fn($get) => $get('reason') === 'other');
Column Spanning
// Span specific number of columns
InputField::make('address')->columnSpan('2')
// Span full width
TextareaField::make('notes')->columnSpan('full')
Custom Attributes
InputField::make('username')->attributes([
'autocomplete' => 'username',
'class' => 'custom-class',
'data-validation' => 'true',
'x-data' => '{ focused: false }',
]);
Helper Text and Hints
InputField::make('password')
->password()
->helperText('Password must be at least 8 characters long')
->hint('Use a mix of letters, numbers, and symbols');
Creating Custom Fields
You can create custom field types by extending the base Field
class:
use Milenmk\LaravelSimpleDatatablesAndForms\Form\Fields\Field;
class ColorField extends Field
{
protected string $view = 'custom.fields.color';
public function render(): string
{
return view($this->view, ['field' => $this])->render();
}
public function color(string $type = 'full'): static
{
$this->inputType = 'color';
return $this;
}
}
Then use it in your forms:
ColorField::make('brand_color')->label('Brand Color')->default('#3B82F6');
Laravel Simple Datatables And Forms provides comprehensive validation features that integrate seamlessly with Laravel's validation system.
Basic Validation
Field-Level Validation Rules
Add validation rules directly to form fields:
use Milenmk\LaravelSimpleDatatablesAndForms\Form\Fields\InputField;
use Milenmk\LaravelSimpleDatatablesAndForms\Form\Fields\SelectField;
public function form(Form $form): Form
{
return $form->schema([
InputField::make('name')
->label('Full Name')
->rules(['required', 'string', 'min:2', 'max:255'])
->required(), // Visual indicator
InputField::make('email')
->label('Email Address')
->email()
->rules(['required', 'email', 'unique:users,email'])
->required(),
InputField::make('age')
->label('Age')
->number()
->rules(['required', 'integer', 'min:18', 'max:120']),
SelectField::make('role')
->label('Role')
->options(['admin' => 'Admin', 'user' => 'User'])
->rules(['required', 'in:admin,user'])
->required(),
]);
}
Automatic Validation Rule Generation
Some field types automatically generate validation rules:
// InputField generates rules based on its configuration
InputField::make('email')
->email() // Adds 'email' rule
->required() // Adds 'required' rule
->maxLength(255) // Adds 'max:255' rule
->minLength(3), // Adds 'min:3' rule
InputField::make('age')
->number() // Adds 'numeric' rule
->min(18) // Adds 'min:18' rule
->max(65), // Adds 'max:65' rule
SelectField::make('status')
->options(['active' => 'Active', 'inactive' => 'Inactive'])
->rules(['in:active,inactive']), // Auto-generated from options
Custom Validation Messages
Field-Level Messages
InputField::make('password')
->password()
->rules(['required', 'min:8', 'regex:/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$/'])
->validationMessages([
'required' => 'Password is required.',
'min' => 'Password must be at least 8 characters.',
'regex' => 'Password must contain uppercase, lowercase, and numeric characters.'
]),
Component-Level Messages
Override validation messages at the component level:
class CreateUserForm extends Component
{
use HasForm;
protected function getValidationMessages(): array
{
return [
'formData.email.required' => 'Email address is mandatory.',
'formData.email.unique' => 'This email is already registered.',
'formData.name.min' => 'Name must be at least 2 characters long.',
];
}
}
Real-Time Validation
Enable real-time validation for immediate feedback:
// In your component
class CreateUserForm extends Component
{
use HasForm;
// Enable real-time validation for specific fields
protected $listeners = [
'formData.email' => 'validateEmail',
'formData.username' => 'validateUsername',
];
public function validateEmail()
{
$this->validateOnly('formData.email', [
'formData.email' => ['required', 'email', 'unique:users,email'],
]);
}
public function validateUsername()
{
$this->validateOnly('formData.username', [
'formData.username' => ['required', 'string', 'unique:users,username'],
]);
}
}
Wire:Model with Validation
// Enable real-time validation on blur
InputField::make('email')
->email()
->rules(['required', 'email', 'unique:users'])
->attributes(['wire:model.blur' => 'formData.email']),
Advanced Validation
Conditional Validation Rules
InputField::make('other_reason')
->label('Other Reason')
->rules(function ($get) {
return $get('reason') === 'other'
? ['required', 'string', 'min:10']
: [];
})
->visible(fn ($get) => $get('reason') === 'other'),
Cross-Field Validation
InputField::make('password_confirmation')
->password()
->label('Confirm Password')
->rules(['required', 'same:formData.password']),
Custom Validation Rules
use Illuminate\Validation\Rule;
SelectField::make('username')
->rules([
'required',
'string',
Rule::unique('users')->ignore($this->formModel?->id),
new CustomUsernameRule(),
]),
Form Submission Validation
Basic Form Validation
public function save()
{
// Get validation rules from form fields
$rules = $this->getValidationRulesFromForm();
// Validate the form data
$validatedData = $this->validate($rules);
// Process the validated data
if ($this->formModel) {
$this->formModel->update($this->formData);
} else {
User::create($this->formData);
}
$this->notification()->success('Saved successfully!');
}
Custom Validation Logic
public function save()
{
// Custom validation with additional rules
$customRules = [
'formData.terms' => 'accepted',
'formData.age' => ['required', function ($attribute, $value, $fail) {
if ($value < 21 && $this->formData['alcohol_consent']) {
$fail('You must be 21 or older to consent to alcohol.');
}
}],
];
$rules = array_merge($this->getValidationRulesFromForm(), $customRules);
$this->validate($rules);
// Process form...
}
Validation Error Display
Field-Level Error Display
Errors are automatically displayed below each field:
{{-- Automatically rendered by the field --}}
<div class="form-field-wrapper">
<input type="text" name="name" />
@error('formData.name')
<p class="field-error">{{ $message }}</p>
@enderror
</div>
Form-Level Error Summary
Display all errors at the top of the form:
<form wire:submit.prevent="save">
{{-- Error summary --}}
@if ($errors->any())
<div class="error-summary">
<h3>Please correct the following errors:</h3>
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
{{ $this->form }}
<button type="submit">Save</button>
</form>
Client-Side Validation
HTML5 Validation
Form fields automatically include HTML5 validation attributes:
InputField::make('email')
->email() // Adds type="email"
->required() // Adds required attribute
->maxLength(255), // Adds maxlength="255"
InputField::make('age')
->number() // Adds type="number"
->min(18) // Adds min="18"
->max(65), // Adds max="65"
Alpine.js Integration
For custom client-side validation:
InputField::make('username')
->attributes([
'x-data' => '{ valid: true }',
'x-on:blur' => 'valid = $el.value.length >= 3',
'x-bind:class' => '!valid ? "border-red-500" : "border-gray-300"'
])
->helperText('Username must be at least 3 characters'),
Configuration
Configure validation behavior in your component:
class UserForm extends Component
{
use HasForm;
// Customize validation behavior
protected bool $validateOnBlur = true;
protected bool $showValidationErrors = true;
protected string $errorMessageStyle = 'inline'; // 'inline', 'summary', 'both'
public function mount(): void
{
$this->mountForm();
// Enable real-time validation globally
if ($this->validateOnBlur) {
$this->enableRealtimeValidation();
}
}
protected function enableRealtimeValidation(): void
{
// Add listeners for all form fields
$form = $this->getFormInstance();
foreach ($form->getFields() as $field) {
$this->listeners["formData.{$field->name}"] = 'validate' . str($field->name)->studly();
}
}
}
Validation Examples
User Registration Form
public function form(Form $form): Form
{
return $form
->heading([
'title' => 'Create Account',
'description' => 'All fields are required.'
])
->schema([
InputField::make('name')
->label('Full Name')
->rules(['required', 'string', 'min:2', 'max:255'])
->placeholder('John Doe')
->required(),
InputField::make('email')
->label('Email Address')
->email()
->rules(['required', 'email', 'unique:users,email'])
->placeholder('john@example.com')
->required(),
InputField::make('username')
->label('Username')
->rules([
'required',
'string',
'min:3',
'max:20',
'alpha_dash',
'unique:users,username'
])
->validationMessages([
'alpha_dash' => 'Username can only contain letters, numbers, dashes and underscores.',
'unique' => 'This username is already taken.'
])
->helperText('3-20 characters, letters, numbers, dashes and underscores only')
->required(),
InputField::make('password')
->label('Password')
->password()
->rules([
'required',
'string',
'min:8',
'regex:/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]+$/'
])
->validationMessages([
'regex' => 'Password must contain at least one uppercase letter, one lowercase letter, one number and one special character.'
])
->helperText('Minimum 8 characters with mixed case, numbers and symbols')
->required(),
InputField::make('password_confirmation')
->label('Confirm Password')
->password()
->rules(['required', 'same:formData.password'])
->required(),
CheckboxField::make('terms')
->label('I agree to the Terms of Service and Privacy Policy')
->rules(['accepted'])
->required(),
]);
}
Profile Update Form with Conditional Validation
public function form(Form $form): Form
{
return $form->schema([
InputField::make('current_password')
->label('Current Password')
->password()
->rules(function () {
return $this->isChangingPassword()
? ['required', 'current_password']
: [];
})
->visible(fn () => $this->isChangingPassword()),
InputField::make('new_password')
->label('New Password')
->password()
->rules(function () {
return $this->isChangingPassword()
? ['required', 'string', 'min:8', 'different:current_password']
: [];
})
->visible(fn () => $this->isChangingPassword()),
ToggleField::make('change_password')
->label('Change Password')
->live(),
]);
}
protected function isChangingPassword(): bool
{
return $this->formData['change_password'] ?? false;
}
See Also
- Field Types - Available form field types and their options
- Form Sections - Organizing forms into sections
- Model Binding - Advanced model integration techniques
Organize complex forms into logical sections for better user experience and maintainability. Form sections help break down large forms into manageable groups of related fields.
Basic Section Usage
Creating Sections
use Milenmk\LaravelSimpleDatatablesAndForms\Form\Form;
use Milenmk\LaravelSimpleDatatablesAndForms\Form\Section;
use Milenmk\LaravelSimpleDatatablesAndForms\Form\Fields\InputField;
use Milenmk\LaravelSimpleDatatablesAndForms\Form\Fields\SelectField;
public function form(Form $form): Form
{
return $form->schema([
Section::make('Personal Information')
->description('Basic personal details')
->schema([
InputField::make('first_name')
->label('First Name')
->required(),
InputField::make('last_name')
->label('Last Name')
->required(),
InputField::make('email')
->label('Email')
->email()
->required(),
InputField::make('phone')
->label('Phone Number')
->tel(),
]),
Section::make('Address Information')
->description('Your current address')
->schema([
InputField::make('street_address')
->label('Street Address')
->columnSpan('full'),
InputField::make('city')
->label('City'),
SelectField::make('state')
->label('State')
->options($stateOptions),
InputField::make('postal_code')
->label('Postal Code'),
]),
Section::make('Account Settings')
->schema([
InputField::make('username')
->label('Username')
->required(),
InputField::make('password')
->label('Password')
->password()
->required(),
]),
]);
}
Section Configuration
Section Styling and Layout
Section::make('Profile Settings')
->description('Manage your profile information')
->icon('user') // Icon for section header
->collapsible() // Make section collapsible
->collapsed() // Start collapsed
->columns(3) // Number of columns in section
->compact() // Reduced padding/spacing
->aside() // Side-by-side layout with main content
->schema([
// Section fields...
]),
Conditional Sections
Section::make('Company Information')
->description('Required for business accounts')
->visible(fn ($get) => $get('account_type') === 'business')
->schema([
InputField::make('company_name')
->label('Company Name')
->required(),
InputField::make('tax_id')
->label('Tax ID')
->required(),
]),
Advanced Section Features
Collapsible Sections
Section::make('Advanced Options')
->description('Optional advanced configuration')
->collapsible()
->collapsed() // Start collapsed
->persistCollapsed() // Remember collapsed state
->schema([
SelectField::make('timezone')
->label('Timezone')
->options($timezoneOptions),
InputField::make('api_key')
->label('API Key')
->helperText('Optional: For advanced integrations'),
]),
Aside Sections
Create side-by-side layouts with aside sections:
public function form(Form $form): Form
{
return $form
->columns(3)
->schema([
// Main content (spans 2 columns)
Section::make('Main Information')
->columnSpan(2)
->schema([
InputField::make('title')
->label('Title')
->columnSpan('full'),
TextareaField::make('content')
->label('Content')
->columnSpan('full')
->rows(10),
]),
// Sidebar (spans 1 column)
Section::make('Publication Settings')
->aside()
->columnSpan(1)
->schema([
SelectField::make('status')
->label('Status')
->options([
'draft' => 'Draft',
'published' => 'Published',
'archived' => 'Archived',
]),
InputField::make('publish_date')
->label('Publish Date')
->date(),
ToggleField::make('featured')
->label('Featured Article'),
]),
]);
}
Nested Sections
Sections can contain other sections for complex forms:
Section::make('User Configuration')
->schema([
Section::make('Basic Settings')
->columns(2)
->schema([
InputField::make('display_name')
->label('Display Name'),
SelectField::make('language')
->label('Language')
->options($languages),
]),
Section::make('Notification Preferences')
->columns(1)
->schema([
ToggleField::make('email_notifications')
->label('Email Notifications'),
ToggleField::make('sms_notifications')
->label('SMS Notifications'),
Section::make('Email Types')
->visible(fn ($get) => $get('email_notifications'))
->schema([
CheckboxField::make('marketing_emails')
->label('Marketing emails'),
CheckboxField::make('security_alerts')
->label('Security alerts'),
]),
]),
]),
Section Headers and Descriptions
Rich Section Headers
Section::make('Payment Information')
->description('Secure payment processing powered by Stripe')
->icon('credit-card')
->headerActions([
Action::make('verify')
->label('Verify Payment Method')
->action('verifyPayment'),
])
->schema([
// Payment fields...
]),
Custom Section Content
Section::make('Terms and Conditions')
->description('Please review and accept our terms')
->content(view('forms.sections.terms-content'))
->schema([
CheckboxField::make('accept_terms')
->label('I have read and accept the terms and conditions')
->required(),
]),
Section Validation
Section-Level Validation
Section::make('Shipping Address')
->description('Required for physical products')
->visible(fn ($get) => $get('requires_shipping'))
->schema([
InputField::make('shipping_name')
->label('Full Name')
->rules(function ($get) {
return $get('requires_shipping') ? ['required'] : [];
}),
InputField::make('shipping_address')
->label('Address')
->rules(function ($get) {
return $get('requires_shipping') ? ['required'] : [];
}),
])
->validate(function ($get) {
if ($get('requires_shipping')) {
// Custom section validation logic
return [
'shipping_name' => 'required|string',
'shipping_address' => 'required|string',
];
}
return [];
}),
Responsive Sections
Mobile-Friendly Sections
Section::make('Product Details')
->columns([
'default' => 1, // 1 column on mobile
'sm' => 2, // 2 columns on small screens
'md' => 3, // 3 columns on medium screens
'lg' => 4, // 4 columns on large screens
])
->schema([
InputField::make('name')
->label('Product Name')
->columnSpan([
'default' => 'full',
'md' => 2,
]),
InputField::make('price')
->label('Price')
->number()
->columnSpan(1),
InputField::make('sku')
->label('SKU')
->columnSpan(1),
]),
Section Examples
User Profile Form with Sections
public function form(Form $form): Form
{
return $form
->heading([
'title' => 'Edit Profile',
'description' => 'Update your profile information and settings.',
])
->schema([
Section::make('Profile Information')
->description('Your public profile information')
->icon('user')
->columns(2)
->schema([
InputField::make('first_name')
->label('First Name')
->required(),
InputField::make('last_name')
->label('Last Name')
->required(),
InputField::make('email')
->label('Email')
->email()
->required()
->columnSpan('full'),
TextareaField::make('bio')
->label('Bio')
->maxLength(500)
->columnSpan('full'),
]),
Section::make('Account Settings')
->description('Manage your account preferences')
->icon('cog')
->collapsible()
->schema([
SelectField::make('timezone')
->label('Timezone')
->options($timezoneOptions),
SelectField::make('language')
->label('Language')
->options([
'en' => 'English',
'es' => 'Spanish',
'fr' => 'French',
]),
ToggleField::make('email_notifications')
->label('Email Notifications'),
ToggleField::make('marketing_emails')
->label('Marketing Emails')
->visible(fn ($get) => $get('email_notifications')),
]),
Section::make('Security')
->description('Update your password and security settings')
->icon('shield')
->collapsible()
->collapsed()
->schema([
InputField::make('current_password')
->label('Current Password')
->password(),
InputField::make('new_password')
->label('New Password')
->password()
->rules(['nullable', 'min:8', 'confirmed']),
InputField::make('new_password_confirmation')
->label('Confirm New Password')
->password(),
]),
]);
}
E-commerce Product Form
public function form(Form $form): Form
{
return $form
->columns(3)
->schema([
// Main product information
Section::make('Product Details')
->columnSpan(2)
->schema([
InputField::make('name')
->label('Product Name')
->required()
->columnSpan('full'),
TextareaField::make('description')
->label('Description')
->required()
->columnSpan('full'),
InputField::make('sku')
->label('SKU')
->required(),
SelectField::make('category_id')
->label('Category')
->options($categories)
->required(),
InputField::make('price')
->label('Price')
->number()
->step(0.01)
->required(),
InputField::make('weight')
->label('Weight (kg)')
->number()
->step(0.01),
]),
// Sidebar settings
Section::make('Settings')
->aside()
->columnSpan(1)
->schema([
SelectField::make('status')
->label('Status')
->options([
'active' => 'Active',
'inactive' => 'Inactive',
'draft' => 'Draft',
])
->default('draft'),
ToggleField::make('featured')
->label('Featured Product'),
ToggleField::make('track_inventory')
->label('Track Inventory'),
InputField::make('stock_quantity')
->label('Stock Quantity')
->number()
->visible(fn ($get) => $get('track_inventory')),
]),
// SEO Section
Section::make('SEO Settings')
->columnSpan('full')
->collapsible()
->collapsed()
->schema([
InputField::make('meta_title')
->label('Meta Title')
->maxLength(60)
->helperText('Recommended: 50-60 characters'),
TextareaField::make('meta_description')
->label('Meta Description')
->maxLength(160)
->helperText('Recommended: 150-160 characters'),
InputField::make('slug')
->label('URL Slug')
->helperText('Auto-generated if left blank'),
]),
]);
}
Multi-Step Form with Sections
class MultiStepForm extends Component
{
use HasForm;
public int $currentStep = 1;
public int $totalSteps = 3;
public function form(Form $form): Form
{
return $form->schema([
Section::make('Step 1: Personal Information')
->visible(fn() => $this->currentStep === 1)
->schema([
InputField::make('first_name')->label('First Name')->required(),
InputField::make('last_name')->label('Last Name')->required(),
InputField::make('email')->label('Email')->email()->required(),
]),
Section::make('Step 2: Company Information')
->visible(fn() => $this->currentStep === 2)
->schema([
InputField::make('company_name')->label('Company Name')->required(),
SelectField::make('industry')->label('Industry')->options($industries)->required(),
]),
Section::make('Step 3: Review & Confirm')->visible(fn() => $this->currentStep === 3)->schema([
// Review content
]),
]);
}
public function nextStep()
{
if ($this->currentStep < $this->totalSteps) {
$this->currentStep++;
}
}
public function previousStep()
{
if ($this->currentStep > 1) {
$this->currentStep--;
}
}
}
Styling and Customization
Custom Section Styles
You can customize section appearance by publishing the views and modifying the CSS classes:
php artisan vendor:publish --tag=laravel-simple-datatables-and-forms-views
Then modify the section template in
resources/views/vendor/laravel-simple-datatables-and-forms/components/form/section.blade.php
.
Custom Section Templates
Create custom section templates for specific use cases:
Section::make('Special Section')
->view('custom.form-sections.special')
->schema([
// Fields...
]),
See Also
- Field Types - Available form field types and their options
- Form Validation - Validation rules and techniques
- Model Binding - Advanced model integration techniques
Laravel Simple Datatables And Forms provides powerful model binding features that allow you to seamlessly integrate forms with Eloquent models for both creating and updating records.
Basic Model Binding
Setting Form Model
use App\Models\User;
use Milenmk\LaravelSimpleDatatablesAndForms\Form\Form;
public function form(Form $form): Form
{
return $form
->model(User::class) // Set the model class
->schema([
InputField::make('name')
->label('Full Name')
->required(),
InputField::make('email')
->email()
->required(),
SelectField::make('role')
->options(['admin' => 'Admin', 'user' => 'User'])
->required(),
]);
}
Loading Existing Model Data
class EditUser extends Component
{
use HasForm;
public User $user;
public function mount(User $user): void
{
$this->mountForm();
$this->loadFormModel($user);
}
public function form(Form $form): Form
{
return $form
->model(User::class)
->schema([
InputField::make('name')->label('Full Name')->required(),
InputField::make('email')->email()->required(),
]);
}
}
Advanced Model Operations
Creating New Records
class CreateUser extends Component
{
use HasForm;
public function save()
{
// Get validation rules from form
$rules = $this->getValidationRulesFromForm();
$validated = $this->validate($rules);
// Create new model instance
$user = User::create($this->formData);
// Optional: Set the created model
$this->loadFormModel($user);
$this->notification()->success('User created successfully!');
// Redirect or reset form
return redirect()->route('users.index');
}
public function form(Form $form): Form
{
return $form
->model(User::class)
->schema([
InputField::make('name')->label('Full Name')->required(),
InputField::make('email')->email()->required(),
InputField::make('password')->password()->required(),
]);
}
}
Updating Existing Records
class EditUser extends Component
{
use HasForm;
public User $user;
public function mount(User $user): void
{
$this->mountForm();
$this->loadFormModel($user);
}
public function save()
{
$rules = $this->getValidationRulesFromForm();
$validated = $this->validate($rules);
// Update the existing model
$this->formModel->update($this->formData);
$this->notification()->success('User updated successfully!');
}
public function form(Form $form): Form
{
return $form->model(User::class)->schema([
InputField::make('name')->label('Full Name')->required(),
InputField::make('email')
->email()
->rules(['required', 'email', Rule::unique('users')->ignore($this->formModel?->id)]),
]);
}
}
Relationship Handling
Belongs To Relationships
public function form(Form $form): Form
{
return $form
->model(Post::class)
->schema([
InputField::make('title')
->label('Post Title')
->required(),
SelectField::make('user_id')
->label('Author')
->options(User::pluck('name', 'id'))
->required(),
SelectField::make('category_id')
->label('Category')
->options(Category::pluck('name', 'id'))
->required(),
]);
}
Many-to-Many Relationships
public function form(Form $form): Form
{
return $form
->model(Post::class)
->schema([
InputField::make('title')
->label('Post Title')
->required(),
SelectField::make('tags')
->label('Tags')
->multiple()
->options(Tag::pluck('name', 'id'))
->relationship('tags'), // Handle pivot table automatically
]);
}
// In your save method
public function save()
{
$validated = $this->validate($this->getValidationRulesFromForm());
if ($this->formModel) {
// Update existing post
$this->formModel->update($this->formData);
// Sync many-to-many relationships
if (isset($this->formData['tags'])) {
$this->formModel->tags()->sync($this->formData['tags']);
}
} else {
// Create new post
$post = Post::create(Arr::except($this->formData, ['tags']));
// Attach tags
if (isset($this->formData['tags'])) {
$post->tags()->attach($this->formData['tags']);
}
}
}
Has Many Relationships (Nested Forms)
public function form(Form $form): Form
{
return $form
->model(User::class)
->schema([
InputField::make('name')
->label('User Name')
->required(),
Section::make('Addresses')
->schema([
Repeater::make('addresses')
->relationship('addresses')
->schema([
InputField::make('street')
->label('Street Address')
->required(),
InputField::make('city')
->label('City')
->required(),
SelectField::make('type')
->label('Address Type')
->options([
'home' => 'Home',
'work' => 'Work',
'other' => 'Other'
]),
])
->minItems(1)
->maxItems(5),
]),
]);
}
Dynamic Model Selection
Polymorphic Models
class CommentForm extends Component
{
use HasForm;
public string $commentableType;
public int $commentableId;
public function mount(string $type, int $id): void
{
$this->commentableType = $type;
$this->commentableId = $id;
$this->mountForm();
}
public function form(Form $form): Form
{
return $form
->model(Comment::class)
->schema([
HiddenField::make('commentable_type')->default($this->commentableType),
HiddenField::make('commentable_id')->default($this->commentableId),
TextareaField::make('content')->label('Comment')->required(),
InputField::make('author_name')->label('Your Name')->required(),
]);
}
public function save()
{
$validated = $this->validate($this->getValidationRulesFromForm());
Comment::create($this->formData);
$this->resetForm();
$this->notification()->success('Comment added successfully!');
}
}
Conditional Model Binding
public function form(Form $form): Form
{
$modelClass = $this->formData['type'] === 'user' ? User::class : Company::class;
return $form
->model($modelClass)
->schema([
SelectField::make('type')
->label('Account Type')
->options([
'user' => 'Personal',
'company' => 'Business'
])
->required()
->live(), // Trigger form rebuild on change
// Personal fields
Section::make('Personal Information')
->visible(fn ($get) => $get('type') === 'user')
->schema([
InputField::make('first_name')
->label('First Name')
->required(),
InputField::make('last_name')
->label('Last Name')
->required(),
]),
// Company fields
Section::make('Company Information')
->visible(fn ($get) => $get('type') === 'company')
->schema([
InputField::make('company_name')
->label('Company Name')
->required(),
InputField::make('tax_id')
->label('Tax ID')
->required(),
]),
]);
}
Model Validation Integration
Using Model Rules
class User extends Model
{
public static function rules(int $id = null): array
{
return [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'email', Rule::unique('users')->ignore($id)],
'password' => $id ? ['nullable', 'min:8'] : ['required', 'min:8'],
];
}
}
// In your form component
public function save()
{
$rules = $this->formModel?->exists
? User::rules($this->formModel->id)
: User::rules();
$this->validate(array_combine(
array_map(fn($key) => "formData.{$key}", array_keys($rules)),
array_values($rules)
));
if ($this->formModel) {
$this->formModel->update($this->formData);
} else {
User::create($this->formData);
}
}
Model Events Integration
class CreateUser extends Component
{
use HasForm;
public function save()
{
$validated = $this->validate($this->getValidationRulesFromForm());
DB::transaction(function () {
$user = User::create($this->formData);
// Model events will be triggered automatically
// But you can also trigger custom events
event(new UserRegistered($user));
// Send welcome email
Mail::to($user->email)->send(new WelcomeEmail($user));
$this->loadFormModel($user);
});
$this->notification()->success('User created and welcome email sent!');
}
}
File Uploads with Models
Single File Upload
public function form(Form $form): Form
{
return $form
->model(User::class)
->schema([
InputField::make('name')
->label('Full Name')
->required(),
FileUploadField::make('avatar')
->label('Profile Picture')
->image()
->maxSize(2048) // 2MB
->directory('avatars')
->visibility('public'),
]);
}
public function save()
{
$validated = $this->validate($this->getValidationRulesFromForm());
// Handle file upload
if (isset($this->formData['avatar']) && $this->formData['avatar'] instanceof UploadedFile) {
$avatarPath = $this->formData['avatar']->store('avatars', 'public');
$this->formData['avatar'] = $avatarPath;
}
if ($this->formModel) {
$this->formModel->update($this->formData);
} else {
User::create($this->formData);
}
}
Multiple File Uploads
public function form(Form $form): Form
{
return $form
->model(Post::class)
->schema([
InputField::make('title')
->label('Post Title')
->required(),
FileUploadField::make('attachments')
->label('Attachments')
->multiple()
->maxFiles(5)
->acceptedFileTypes(['pdf', 'doc', 'docx'])
->directory('post-attachments'),
]);
}
public function save()
{
$validated = $this->validate($this->getValidationRulesFromForm());
if ($this->formModel) {
$post = $this->formModel;
$post->update(Arr::except($this->formData, ['attachments']));
} else {
$post = Post::create(Arr::except($this->formData, ['attachments']));
}
// Handle file attachments
if (isset($this->formData['attachments'])) {
foreach ($this->formData['attachments'] as $file) {
$path = $file->store('post-attachments', 'public');
$post->attachments()->create([
'filename' => $file->getClientOriginalName(),
'path' => $path,
'size' => $file->getSize(),
'mime_type' => $file->getMimeType(),
]);
}
}
}
Mass Assignment Protection
Using Fillable
// In your User model
class User extends Model
{
protected $fillable = [
'name',
'email',
'password',
'role',
'is_active',
];
}
// In your form component
public function save()
{
$validated = $this->validate($this->getValidationRulesFromForm());
// Only fillable attributes will be mass assigned
User::create($this->formData);
}
Manual Assignment for Sensitive Fields
public function save()
{
$validated = $this->validate($this->getValidationRulesFromForm());
$user = new User();
// Mass assign safe fields
$user->fill(Arr::except($this->formData, ['password', 'role']));
// Manually handle sensitive fields
if (!empty($this->formData['password'])) {
$user->password = Hash::make($this->formData['password']);
}
// Handle role with permission check
if (auth()->user()->can('assign-roles')) {
$user->role = $this->formData['role'];
}
$user->save();
}
Complex Model Interactions
Wizard-Style Multi-Model Forms
class RegistrationWizard extends Component
{
use HasForm;
public int $step = 1;
public User $user;
public Profile $profile;
public function form(Form $form): Form
{
return match ($this->step) {
1 => $this->userForm($form),
2 => $this->profileForm($form),
3 => $this->preferencesForm($form),
default => $form,
};
}
private function userForm(Form $form): Form
{
return $form
->model(User::class)
->schema([
InputField::make('name')->label('Full Name')->required(),
InputField::make('email')->email()->required(),
InputField::make('password')->password()->required(),
]);
}
private function profileForm(Form $form): Form
{
return $form
->model(Profile::class)
->schema([
InputField::make('phone')->label('Phone Number')->tel(),
TextareaField::make('bio')->label('Biography')->maxLength(500),
InputField::make('birth_date')->label('Date of Birth')->date(),
]);
}
public function nextStep()
{
// Validate current step
$rules = $this->getValidationRulesFromForm();
$this->validate($rules);
// Save current step data
match ($this->step) {
1 => $this->saveUser(),
2 => $this->saveProfile(),
};
$this->step++;
}
private function saveUser()
{
$this->user = User::create($this->formData);
$this->resetForm();
}
private function saveProfile()
{
$this->user->profile()->create($this->formData);
$this->resetForm();
}
}
Performance Optimization
Lazy Loading Relationships
public function form(Form $form): Form
{
return $form
->model(Post::class)
->schema([
SelectField::make('category_id')
->label('Category')
->options(function () {
// Lazy load options only when needed
return Category::active()->pluck('name', 'id');
})
->searchable(),
SelectField::make('tags')
->label('Tags')
->multiple()
->options(function () {
return Tag::popular()->pluck('name', 'id');
}),
]);
}
Caching Form Options
use Illuminate\Support\Facades\Cache;
public function form(Form $form): Form
{
return $form
->model(Product::class)
->schema([
SelectField::make('category_id')
->label('Category')
->options(function () {
return Cache::remember('categories-options', 3600, function () {
return Category::active()->pluck('name', 'id');
});
}),
]);
}
See Also
- Field Types - Available form field types and their options
- Form Validation - Validation rules and techniques
- Form Sections - Organizing forms into sections
The Laravel Simple DataTables And Forms package now includes enhanced searchable select fields that provide a modern, user-friendly alternative to standard HTML select elements.
Features
- Search Functionality: Type to filter options in real-time
- Multiple Selection: Select multiple options with visual tags
- Keyboard Navigation: Full keyboard support for accessibility
- Dark Mode: Automatic dark mode support
- Responsive Design: Works on all screen sizes
- Alpine.js Integration: Lightweight, no external dependencies
- Livewire Compatible: Seamless integration with Livewire forms
Basic Usage
Simple Searchable Select
SelectField::make('country')
->label('Country')
->options([
'us' => 'United States',
'ca' => 'Canada',
'uk' => 'United Kingdom',
// ... more options
])
->searchable() // Enable search functionality
->placeholder('Search for a country...')
->required();
Multiple Selection
SelectField::make('skills')
->label('Skills')
->options([
'php' => 'PHP',
'javascript' => 'JavaScript',
'python' => 'Python',
// ... more options
])
->multiple() // Enable multiple selection
->searchable()
->placeholder('Select multiple skills...');
Configuration Options
Available Methods
Method | Description | Example |
---|---|---|
searchable() |
Enable search functionality | ->searchable() |
multiple() |
Enable multiple selection | ->multiple() |
placeholder() |
Set placeholder text | ->placeholder('Search...') |
emptyOption() |
Add empty option for single select | ->emptyOption('Choose one') |
options() |
Set available options | ->options(['key' => 'value']) |
Advanced Configuration
SelectField::make('categories')
->label('Categories')
->options(Category::pluck('name', 'id')->toArray())
->searchable()
->multiple()
->placeholder('Search and select categories...')
->helperText('You can select multiple categories')
->required();
User Interface
Single Select
- Displays selected option text
- Shows placeholder when no selection
- Dropdown with search input
- Checkmark indicates selected option
Multiple Select
- Shows selected options as removable tags
- Displays count when many options selected
- Individual remove buttons on tags
- Clear all button when selections exist
Keyboard Navigation
Key | Action |
---|---|
Enter or Space |
Open/close dropdown |
β β |
Navigate through options |
Enter |
Select highlighted option |
Escape |
Close dropdown |
Backspace |
Remove last selected (multiple) |
Styling
The searchable select uses Tailwind CSS classes and follows the package's design system:
- Consistent with other form fields
- Dark mode support
- Focus states and transitions
- Responsive design
- Accessibility features
Custom Styling
You can customize the appearance by modifying the CSS classes in your published views or by adding custom CSS:
/* Custom searchable select styling */
.searchable-select-trigger {
/* Custom trigger button styles */
}
.searchable-select-dropdown {
/* Custom dropdown styles */
}
.searchable-select-tag {
/* Custom tag styles for multiple select */
}
JavaScript Integration
The searchable select is powered by an Alpine.js component that automatically registers when the package JavaScript is loaded:
@SimpleDatatablesScript
Manual Registration
If you need to register the component manually:
document.addEventListener('alpine:init', () => {
Alpine.data('searchableSelect', searchableSelect);
});
Examples
E-commerce Product Form
SelectField::make('category_id')
->label('Product Category')
->options(Category::pluck('name', 'id')->toArray())
->searchable()
->required()
->helperText('Choose the main category for this product'),
SelectField::make('tags')
->label('Product Tags')
->options(Tag::pluck('name', 'id')->toArray())
->multiple()
->searchable()
->placeholder('Add relevant tags...')
User Management Form
SelectField::make('roles')
->label('User Roles')
->options(Role::pluck('name', 'id')->toArray())
->multiple()
->searchable()
->placeholder('Assign roles to user...')
->required(),
SelectField::make('department_id')
->label('Department')
->options(Department::pluck('name', 'id')->toArray())
->searchable()
->emptyOption('Select department')
Browser Support
- Chrome 90+
- Firefox 88+
- Safari 14+
- Edge 90+
Performance Considerations
- Options are filtered client-side for fast response
- Large option lists (1000+) may impact performance
- Consider server-side filtering for very large datasets
- Lazy loading options is recommended for relationship-based selects
Accessibility
The searchable select includes full accessibility support:
- ARIA labels and descriptions
- Keyboard navigation
- Screen reader compatibility
- Focus management
- High contrast support
Migration from Regular Selects
To convert existing select fields to searchable selects, simply add the ->searchable()
method:
// Before
SelectField::make('status')
->options(['active' => 'Active', 'inactive' => 'Inactive'])
// After
SelectField::make('status')
->options(['active' => 'Active', 'inactive' => 'Inactive'])
->searchable() // Add this line
The regular select will still be used as a fallback if JavaScript is disabled.
Troubleshooting
Common Issues
- Dropdown not appearing: Ensure
@SimpleDatatablesScript
is included - Styling issues: Check that
@SimpleDatatablesStyle
is included - Alpine.js conflicts: Ensure Alpine.js is loaded before the package script
- Livewire sync issues: Use
@entangle().defer
for better performance
Debug Mode
Enable debug mode to see console logs:
// Add to your app.js
window.searchableSelectDebug = true;
Future Enhancements
Planned features for future releases:
- Server-side search for large datasets
- Custom option templates
- Grouping options
- Async option loading
- Virtual scrolling for performance
The Laravel Simple DataTables And Forms package now supports relationship-based select fields that automatically load options from Eloquent relationships with advanced search and customization capabilities.
Features
- Eloquent Relationship Integration: Automatically load options from model relationships
- Custom Option Labels: Use closures to format how options are displayed to users
- Multi-Column Search: Search across multiple database columns simultaneously
- Query Modifications: Apply custom constraints and ordering to relationship queries
- Multiple Selection: Select multiple related records with visual tags
- Performance Optimized: Efficient querying with proper relationship handling
- Validation Integration: Automatic validation rules for relationship fields
Basic Usage
Simple Relationship Select
SelectField::make('user_id')
->label('User')
->relationship('user', 'name') // relationship method, display column
->searchable()
->required();
With Form Model Context
public function form(Form $form): Form
{
return $form
->model(Product::class) // Required for relationship context
->sections([
Section::make('basic_info')
->fields([
SelectField::make('category_id')
->label('Category')
->relationship('category', 'name')
->searchable()
->required(),
])
]);
}
Advanced Features
Custom Option Labels
Use closures to customize how options are displayed:
SelectField::make('user_id')
->label('User')
->relationship('user', 'name')
->getOptionLabelFromRecordUsing(fn(User $record) => $record->full_name)
->searchable();
Complex Label Formatting
SelectField::make('user_id')
->label('User')
->relationship('user', 'name')
->getOptionLabelFromRecordUsing(function (User $record) {
return "{$record->name} ({$record->email}) - {$record->department}";
})
->searchable();
Multi-Column Search
Specify which columns to search when filtering options:
SelectField::make('user_id')
->label('User')
->relationship('user', 'name')
->searchable(['users.name', 'users.email', 'users.first_name', 'users.last_name'])
->getOptionLabelFromRecordUsing(fn(User $record) => "{$record->name} - {$record->email}");
Multiple Selection
Enable multiple selection for many-to-many relationships:
SelectField::make('tags')
->label('Product Tags')
->relationship('tags', 'name')
->multiple()
->searchable(['tags.name', 'tags.description'])
->getOptionLabelFromRecordUsing(fn(Tag $record) => "{$record->name} ({$record->type})")
->placeholder('Search and select multiple tags...');
Query Modifications
Apply custom constraints to the relationship query:
SelectField::make('active_user_id')
->label('Active Users')
->relationship('user', 'name')
->modifyQueryUsing(function ($query) {
return $query->where('is_active', true)->where('role', '!=', 'admin')->orderBy('name');
})
->searchable(['users.name', 'users.email']);
Complex Query Modifications
SelectField::make('recent_products')
->label('Recent Products')
->relationship('products', 'name')
->modifyQueryUsing(function ($query) {
return $query
->where('created_at', '>=', now()->subDays(30))
->where('is_published', true)
->with('category')
->orderBy('created_at', 'desc');
})
->multiple()
->searchable(['products.name', 'products.sku'])
->getOptionLabelFromRecordUsing(
fn(Product $record) => "{$record->name} (SKU: {$record->sku}) - {$record->category->name}",
);
Complete Example
Here's a comprehensive example showing all features:
SelectField::make('user_id')
->label('User')
->relationship('user', 'name')
->getOptionLabelFromRecordUsing(function (User $record) {
return "{$record->name} ({$record->email}) - {$record->department}";
})
->searchable(['users.name', 'users.email', 'users.first_name', 'users.last_name', 'users.department'])
->modifyQueryUsing(function ($query) {
return $query->where('is_active', true)->whereNotNull('email_verified_at')->orderBy('name');
})
->placeholder('Search by name, email, or department...')
->required()
->helperText('Only active, verified users are shown');
Model Requirements
Form Model
The form must have a model class set for relationship context:
public function form(Form $form): Form
{
return $form
->model(Product::class) // This is required
->sections([...]);
}
Relationship Methods
Your model must have the relationship method defined:
class Product extends Model
{
public function user()
{
return $this->belongsTo(User::class);
}
public function category()
{
return $this->belongsTo(Category::class);
}
public function tags()
{
return $this->belongsToMany(Tag::class);
}
}
Database Structure
Ensure your database has the necessary foreign key columns:
// products table
Schema::table('products', function (Blueprint $table) {
$table->foreignId('user_id')->constrained();
$table->foreignId('category_id')->constrained();
});
// product_tag pivot table for many-to-many
Schema::create('product_tag', function (Blueprint $table) {
$table->foreignId('product_id')->constrained();
$table->foreignId('tag_id')->constrained();
});
Validation
Relationship select fields automatically generate appropriate validation rules:
// Single relationship
'formData.user_id' => 'required|exists:users,id'
// Multiple relationship
'formData.tags' => 'nullable|array'
'formData.tags.*' => 'exists:tags,id'
Custom Validation
You can add additional validation rules:
SelectField::make('user_id')
->relationship('user', 'name')
->rules(['required', 'exists:users,id', 'different:current_user_id'])
->searchable();
Performance Considerations
Eager Loading
For better performance with custom labels, consider eager loading:
SelectField::make('user_id')
->relationship('user', 'name')
->modifyQueryUsing(fn($query) => $query->with('department', 'role'))
->getOptionLabelFromRecordUsing(function (User $record) {
return "{$record->name} - {$record->department->name} ({$record->role->name})";
});
Large Datasets
For very large datasets, consider:
- Pagination: Implement server-side pagination for options
- Caching: Cache frequently accessed relationship options
- Indexing: Ensure searchable columns are properly indexed
// Example with query optimization
SelectField::make('user_id')
->relationship('user', 'name')
->modifyQueryUsing(function ($query) {
return $query
->select('id', 'name', 'email') // Only select needed columns
->where('is_active', true)
->limit(100) // Limit results for performance
->orderBy('name');
})
->searchable(['users.name']);
Error Handling
The relationship select fields include comprehensive error handling:
// Logs errors and returns empty array if relationship fails
try {
$options = $field->getRelationshipOptions();
} catch (\Throwable $e) {
\Log::error('SelectField relationship options failed: ' . $e->getMessage());
return [];
}
Security Considerations
- Model Validation: Only allows relationships on properly configured models
- Query Sanitization: All search inputs are sanitized
- Access Control: Respects model scopes and query constraints
- Validation: Automatic validation ensures only valid related records can be selected
Troubleshooting
Common Issues
- Relationship not found: Ensure the relationship method exists on your model
- Empty options: Check that the relationship returns records and the display column exists
- Search not working: Verify searchable columns exist and are properly named
- Performance issues: Consider adding database indexes on searchable columns
Debug Mode
Enable debug logging to troubleshoot issues:
// In your form component
public function mount()
{
if (app()->environment('local')) {
\Log::info('Form model class: ' . $this->getFormInstance()->getModelClass());
}
$this->mountForm();
}
Browser Compatibility
- Chrome 90+
- Firefox 88+
- Safari 14+
- Edge 90+
Future Enhancements
Planned features for future releases:
- Server-side Search: Real-time search with AJAX for large datasets
- Dependent Selects: Automatic filtering based on other field values
- Relationship Caching: Intelligent caching of relationship options
- Custom Templates: Customizable option and tag templates
- Bulk Operations: Bulk select/deselect functionality
This document demonstrates the new form features including icons, collapsible sections, and enhanced security.
1. Input Fields with Icons
Prefix Icons
use Milenmk\LaravelSimpleDatatablesAndForms\Form\Fields\InputField;
// Using Heroicon
InputField::make('phone')
->label('Phone Number')
->prefixIcon('heroicon-o-phone')
->placeholder('Enter your phone number');
// Using custom SVG file
InputField::make('email')->label('Email Address')->prefixIcon('/images/icons/email.svg')->type('email');
// Using image file
InputField::make('website')->label('Website')->prefixIcon('/images/icons/globe.png')->type('url');
Suffix Icons
InputField::make('password')->label('Password')->type('password')->suffixIcon('heroicon-o-eye-slash');
InputField::make('search')->label('Search')->type('search')->suffixIcon('heroicon-o-magnifying-glass');
Both Prefix and Suffix Icons
InputField::make('amount')
->label('Amount')
->type('number')
->prefixIcon('heroicon-o-currency-dollar')
->suffixIcon('heroicon-o-calculator');
2. Textarea Fields with Icons
use Milenmk\LaravelSimpleDatatablesAndForms\Form\Fields\TextareaField;
TextareaField::make('description')
->label('Description')
->prefixIcon('heroicon-o-document-text')
->rows(4)
->placeholder('Enter description...');
TextareaField::make('notes')->label('Notes')->suffixIcon('heroicon-o-pencil-square')->rows(3);
3. Collapsible Form Sections
Basic Collapsible Section
use Milenmk\LaravelSimpleDatatablesAndForms\Form\Sections\Section;
use Milenmk\LaravelSimpleDatatablesAndForms\Form\Fields\InputField;
public function form(Form $form): Form
{
return $form
->sections([
Section::make('personal_info')
->label('Personal Information')
->description('Basic personal details')
->icon('heroicon-o-user')
->collapsible()
->collapsed(false) // Start expanded
->schema([
InputField::make('first_name')
->label('First Name')
->prefixIcon('heroicon-o-user')
->required(),
InputField::make('last_name')
->label('Last Name')
->prefixIcon('heroicon-o-user')
->required(),
InputField::make('email')
->label('Email')
->type('email')
->prefixIcon('heroicon-o-envelope')
->required(),
]),
Section::make('contact_info')
->label('Contact Information')
->description('Phone and address details')
->icon('heroicon-o-phone')
->collapsible()
->collapsed(true) // Start collapsed
->schema([
InputField::make('phone')
->label('Phone')
->type('tel')
->prefixIcon('heroicon-o-phone'),
InputField::make('address')
->label('Address')
->prefixIcon('heroicon-o-map-pin'),
InputField::make('city')
->label('City')
->prefixIcon('heroicon-o-building-office'),
]),
]);
}
Section with Custom Icons
Section::make('job_info')
->label('Job Information')
->description('Employment details')
->icon('/images/icons/briefcase.svg') // Custom SVG
->collapsible()
->schema([
InputField::make('company')->label('Company')->prefixIcon('heroicon-o-building-office-2'),
InputField::make('position')->label('Position')->prefixIcon('heroicon-o-briefcase'),
InputField::make('salary')->label('Salary')->type('number')->prefixIcon('heroicon-o-currency-dollar'),
]);
4. Icon Types Supported
Heroicons (Recommended)
// Outline icons
->prefixIcon('heroicon-o-user')
->prefixIcon('heroicon-o-envelope')
->prefixIcon('heroicon-o-phone')
// Solid icons
->prefixIcon('heroicon-s-user')
->prefixIcon('heroicon-s-envelope')
->prefixIcon('heroicon-s-phone')
// Mini icons
->prefixIcon('heroicon-m-user')
->prefixIcon('heroicon-m-envelope')
->prefixIcon('heroicon-m-phone')
Custom SVG Files
->prefixIcon('/images/icons/custom-icon.svg')
->suffixIcon('/assets/icons/search.svg')
Image Files
->prefixIcon('/images/icons/logo.png')
->suffixIcon('/images/icons/arrow.jpg')
HTML/Blade Components
// Using existing icon components
->prefixIcon('<x-custom-icon name="user" />')
5. Complete Form Example
<?php
namespace App\Livewire\Forms;
use Livewire\Component;
use Milenmk\LaravelSimpleDatatablesAndForms\Form\Form;
use Milenmk\LaravelSimpleDatatablesAndForms\Form\Sections\Section;
use Milenmk\LaravelSimpleDatatablesAndForms\Form\Fields\InputField;
use Milenmk\LaravelSimpleDatatablesAndForms\Form\Fields\TextareaField;
use Milenmk\LaravelSimpleDatatablesAndForms\Form\Fields\SelectField;
use Milenmk\LaravelSimpleDatatablesAndForms\Traits\HasForm;
class UserProfileForm extends Component
{
use HasForm;
public function mount(): void
{
$this->mountForm();
}
public function form(Form $form): Form
{
return $form
->heading([
'title' => 'User Profile',
'description' => 'Manage your personal information and preferences',
])
->sections([
Section::make('basic_info')
->label('Basic Information')
->description('Your personal details')
->icon('heroicon-o-user-circle')
->collapsible()
->collapsed(false)
->columns(2)
->schema([
InputField::make('first_name')
->label('First Name')
->prefixIcon('heroicon-o-user')
->required()
->rules(['required', 'string', 'max:50']),
InputField::make('last_name')
->label('Last Name')
->prefixIcon('heroicon-o-user')
->required()
->rules(['required', 'string', 'max:50']),
InputField::make('email')
->label('Email Address')
->type('email')
->prefixIcon('heroicon-o-envelope')
->suffixIcon('heroicon-o-at-symbol')
->required()
->rules(['required', 'email', 'unique:users,email']),
InputField::make('phone')
->label('Phone Number')
->type('tel')
->prefixIcon('heroicon-o-phone')
->placeholder('+1 (555) 123-4567')
->rules(['nullable', 'string', 'max:20']),
]),
Section::make('address_info')
->label('Address Information')
->description('Your location details')
->icon('heroicon-o-map-pin')
->collapsible()
->collapsed(true)
->columns(1)
->schema([
InputField::make('street_address')
->label('Street Address')
->prefixIcon('heroicon-o-home')
->placeholder('123 Main Street'),
InputField::make('city')
->label('City')
->prefixIcon('heroicon-o-building-office')
->columnSpan('1'),
InputField::make('postal_code')
->label('Postal Code')
->prefixIcon('heroicon-o-map')
->columnSpan('1'),
]),
Section::make('preferences')
->label('Preferences')
->description('Your account preferences')
->icon('heroicon-o-cog-6-tooth')
->collapsible()
->collapsed(true)
->schema([
SelectField::make('timezone')
->label('Timezone')
->options([
'UTC' => 'UTC',
'America/New_York' => 'Eastern Time',
'America/Chicago' => 'Central Time',
'America/Denver' => 'Mountain Time',
'America/Los_Angeles' => 'Pacific Time',
])
->required(),
TextareaField::make('bio')
->label('Biography')
->prefixIcon('heroicon-o-document-text')
->rows(4)
->placeholder('Tell us about yourself...')
->helperText('Maximum 500 characters')
->rules(['nullable', 'string', 'max:500']),
]),
]);
}
public function save()
{
// Apply rate limiting and security checks
$this->validate($this->getValidationRulesFromForm());
// Get sanitized and filtered form data
$formData = $this->getFillableFormData();
// Save the data
auth()->user()->update($formData);
session()->flash('message', 'Profile updated successfully!');
}
public function render()
{
return view('livewire.forms.user-profile-form');
}
}
6. Security Features
All form inputs are automatically:
- Sanitized for XSS protection
- Validated against allowed field types
- Rate Limited to prevent abuse
- CSRF Protected automatically
- Mass Assignment Protected via fillable filtering
Custom Security Configuration
// config/simple-datatables-and-forms.php
'security' => [
'csrf_protection' => true,
'sanitize_input' => true,
'rate_limiting' => [
'enable' => true,
'max_attempts' => 60,
'decay_minutes' => 1,
],
'allowed_file_types' => [
'jpg', 'jpeg', 'png', 'gif', 'pdf', 'doc', 'docx'
],
'max_file_size' => 10240, // KB
],
7. Styling Customization
The form components use CSS classes that can be customized:
/* Icon positioning */
.form-field .relative .absolute {
/* Customize icon positioning */
}
/* Collapsible sections */
.form-section.collapsible {
/* Custom collapsible styling */
}
/* Input with icons */
.form-input.ps-10 {
/* Input with prefix icon */
}
.form-input.pe-10 {
/* Input with suffix icon */
}
8. Form Reactivity
A sample code fo a reactive form is like this:
SelectField::make('type')
->label(__('Type'))
->emptyOption(__('Select Type'))
->reactive(),
SelectField::make('parent_id')
->label(__('Parent'))
->options(Model::whereNull('parent_id')->pluck('name', 'id')->toArray())
->emptyOption(__('Select Parent'))
->hidden(fn (Get $get) => $get('type') === 'heading')
->disabled(fn (Get $get) => $get('type') === 'heading'),
InputField::make('route')
->label(__('Route'))
->hidden(fn (Get $get) => $get('type') === 'heading'),
By adding reactive()
to the first select, we make it available for other fields. Then in each field that depends on
the value of the first one, we use fn(Get $get)
and $get
will contain all values from the form.
As for the input field, if we do not add reactive()
to it, it always remains hidden.
Why? Because of timing and default values:
- InputField by default might render before Livewire has set the initial state of type.
- When the closure runs,
$get('type')
is still null, so the condition$get('type') === 'heading'
might evaluate as true in some frameworks (depending on truthiness checks), or more likely the field is evaluated too early and never updated. SelectField::make('parent_id')
works without addingreactive()
, because itβs designed for reactive option handling, whereas InputField may not re-evaluate itshidden()
callback dynamically unless its own value or the parent state changes.
Select a section from the sidebar to view documentation.