Milk Admin

TableBuilder Class Documentation

The TableBuilder class provides a fluent, field-first interface for creating and managing dynamic tables. It uses a modern chaining pattern where you configure each field individually, making your code more readable and maintainable.

System Overview

TableBuilder acts as a wrapper that combines:

  • ModelList: Database connection and query management
  • ListStructure: Column structure and configuration
  • PageInfo: Pagination and display information

It provides a single, chainable API for building tables with less code and better readability.

Method Reference Summary

Category Method Description
Query Building select(array|string $columns) Select specific columns
where(string $condition, array $params = [], string $operator = 'AND') Add WHERE condition
whereIn(string $field, array $values) WHERE IN clause
whereLike(string $field, string $value, string $position = 'both') LIKE search condition
whereBetween(string $field, $min, $max) BETWEEN condition
join(string $table, string $condition, string $type = 'INNER') JOIN tables
leftJoin(string $table, string $condition) LEFT JOIN tables
rightJoin(string $table, string $condition) RIGHT JOIN tables
groupBy(string $field) GROUP BY clause
having(string $condition, array $params = []) HAVING clause
orderBy(string $field, string $direction = 'ASC') Set ordering
limit(int $limit) Set row limit
queryCustomCallback(callable $callback) Custom query logic
Field Configuration
(Field-First)
field(string $key) Start configuring a field
label(string $label) Set field label
type(string $type) Set field type
options(array $options) Set select options
fn(callable $fn) Custom formatter function
link(string $link, array $options = []) Convert to clickable link
file(array $options = []) Display as file download links
image(array $options = []) Display as image thumbnails
truncate(int $length, string $suffix = '...') Truncate text with suffix
hide() Hide field
noSort() Disable sorting
sortBy(string $realField) Map to real database field
class(string $classes) Set CSS classes (see Styling docs)
moveBefore(string $fieldName) Move current field before another field
Column Management reorderColumns(array $order) Reorder all columns by array of column names
hideColumns(array $keys) Hide multiple columns at once
resetFields() Hide all existing columns from the model
Actions setPage(string $page) Set page name for action links
setDefaultActions(array $customActions = []) Auto-create Edit/Delete actions
addAction(string $key, array $config) Add a single row action
setActions(array $actions) Configure custom row actions
setBulkActions(array $actions) Configure bulk actions
Output getData() Get complete data array
getTable() Get HTML table
getResponse() Get HTML + additional data (handles JSON automatically)
render() Get HTML table directly
getFunctionsResults() Get action callback results

Basic Usage

DO NOT wrap the table output in a div with the table ID. The TableBuilder already includes a wrapper with the correct ID. Adding another div will create duplicate IDs and break filters!


WRONG in VIEW:
<div id="posts_table">
    <?php echo $table_html; ?>
</div>
CORRECT in VIEW:
<?php echo $table_html; ?>

Why? The TableBuilder's getResponse()['html'] already contains the full table HTML with the proper wrapper and ID. Just echo it directly!

Constructor and Factory Method

$table = \Builders\TableBuilder::create($model, 'table_id');

Simple Table Creation

// CONTROLLER
$model = new \Models\PostModel();

$tableBuilder = \Builders\TableBuilder::create($model, 'posts_table')
    ->limit(20)
    ->orderBy('created_at', 'desc')
    ->setDefaultActions()
    // Field-first pattern: configure title field
    ->field('title')
        ->link('?page='.$this->page.'&action=edit&id=%id%');

$response = $tableBuilder->getResponse();
Response::render('view.php', $response);

// VIEW (view.php)
// <?php echo $table_html; ?>  ← Just this! Don't wrap it!

Query Building Methods

TableBuilder supports all standard query operations. Here are the most common:

Default Pagination Limit

You can set a global default for the number of rows per page by defining $conf['page_info_limit'] in your milkadmin_local/config.php file:

$conf['page_info_limit'] = 50;  // Default rows per page

This will apply to all tables unless overridden with the limit() method. See Milkadmin Local Configuration for more details.

$table = \Builders\TableBuilder::create($model, 'posts_table')
    // Basic operations
    ->select(['id', 'title', 'status', 'created_at'])
    ->where('status = ?', ['published'])
    ->orderBy('created_at', 'desc')
    ->limit(50)

    // Advanced operations
    ->whereIn('status', ['active', 'pending'])
    ->whereLike('title', 'search_term', 'both')  // LIKE '%search_term%'
    ->whereBetween('created_at', '2024-01-01', '2024-12-31')
    ->leftJoin('categories', 'posts.category_id = categories.id')

    ->getTable();

📘 For comprehensive query documentation: See Abstract Model - Query Methods

Field-First Pattern

The field-first pattern provides a clean, readable way to configure each table column. Instead of calling methods with the field name as a parameter, you first select the field with field() and then chain configuration methods.

Why Field-First?
  • Better Readability: All configurations for a field are grouped together
  • Clearer Intent: Easy to see what each field does at a glance
  • IDE Support: Better autocomplete and method suggestions
  • Consistent: Matches modern fluent API patterns

Basic Field Configuration

$table = \Builders\TableBuilder::create($model, 'posts_table')
    // Configure each field with method chaining
    ->field('id')
        ->label('ID')
        ->hide()  // Hide this field

    ->field('title')
        ->label('Article Title')
        ->link('?page=posts&action=edit&id=%id%')
        ->truncate(80)

    ->field('status')
        ->label('Status')
        ->type('select')
        ->options([
            'draft' => 'Draft',
            'published' => 'Published',
            'archived' => 'Archived'
        ])

    ->field('created_at')
        ->label('Publication Date')
        ->type('datetime')

    ->getTable();

Available Field Methods

Method Description Example
label(string $label) Set display label for the field ->field('created_at')->label('Published')
type(string $type) Set field type (text, select, date, html, etc.) ->field('status')->type('select')
options(array $options) Set options for select fields ->field('status')->options(['active' => 'Active'])
fn(callable $fn) Custom formatter function ->field('name')->fn(fn($row) => strtoupper($row['name']))
hide() Hide field from display ->field('password')->hide()
noSort() Disable sorting for this field ->field('actions')->noSort()
sortBy(string $real_field) Map virtual field to database field for sorting ->field('doctor_name')->sortBy('doctor.name')

Display Formatting Methods

Links - link() method

The link() method converts a field to a clickable link with placeholder support.

$table = \Builders\TableBuilder::create($model, 'posts_table')
    // Basic link
    ->field('title')
        ->link('?page=posts&action=edit&id=%id%')

    // Link with options
    ->field('author')
        ->link('/profile/%author_id%', [
            'target' => '_blank',
            'class' => 'text-primary fw-bold'
        ])

    // Multiple placeholders
    ->field('full_name')
        ->link('/user/%id%?tab=profile&ref=%category%')

    // Link with fetch (AJAX loading)
    ->field('lessons')
        ->link('?page=courses&action=lessons&entity_id=%id%', [
            'data-fetch' => 'post'
        ])

    ->getTable();

// Supported placeholders:
// %id% - Primary key or 'id' field
// %field_name% - Any column value (e.g., %title%, %status%, %created_at%)

// Available options:
// 'target' => '_blank' | '_self' | '_parent' | '_top'
// 'class' => 'css-classes-here'
// 'data-fetch' => 'post' | 'get' - Enable AJAX loading
    

Files - file() method

The file() method converts array fields containing file data into download links. Automatically applied when model has type=array and form-type=file.

$table = \Builders\TableBuilder::create($model, 'documents_table')
    // Basic usage
    ->field('attachments')
        ->file()

    // With custom options
    ->field('files')
        ->file([
            'class' => 'btn btn-link text-primary',
            'target' => '_blank'
        ])

    ->getTable();

// Expected data format:
// $row->attachments = [
//     ['url' => 'media/file1.pdf', 'name' => 'Document.pdf'],
//     ['url' => 'media/file2.docx', 'name' => 'Report.docx']
// ];

// Available options:
// 'class' => 'custom-css-class'  // Default: 'js-file-download'
// 'target' => '_blank' | '_self'  // Default: '_blank'
    

Images - image() method

The image() method displays image thumbnails. Automatically applied when model has type=array and form-type=image.

$table = \Builders\TableBuilder::create($model, 'products_table')
    // Basic usage
    ->field('photos')
        ->image()

    // With custom options
    ->field('gallery')
        ->image([
            'size' => 80,              // Thumbnail size in pixels
            'class' => 'rounded',      // CSS classes
            'lightbox' => true,        // Clickable links
            'max_images' => 3          // Limit displayed images
        ])

    ->getTable();

// Expected data format:
// $row->photos = [
//     ['url' => 'media/photo1.jpg', 'name' => 'Product Image 1'],
//     ['url' => 'media/photo2.jpg', 'name' => 'Product Image 2']
// ];

// Available options:
// 'size' => 50                    // Width/height in pixels
// 'class' => ''                   // CSS classes
// 'lightbox' => false             // Enable clickable links
// 'max_images' => null            // Show "+N" for remaining
    

Text Truncation - truncate() method

The truncate() method limits text length, adding a suffix when exceeded.

$table = \Builders\TableBuilder::create($model, 'posts_table')
    // Basic truncation
    ->field('description')
        ->truncate(100)

    // Custom suffix
    ->field('title')
        ->truncate(50, '…')

    ->field('content')
        ->truncate(200, ' [read more]')

    ->getTable();

// Features:
// - UTF-8 safe (uses mb_substr and mb_strlen)
// - Applied after all formatting
// - Only truncates strings exceeding the specified length
// - Can be combined with other methods
    

Complete Example

$table = \Builders\TableBuilder::create($model, 'posts_table')
    // Query configuration
    ->where('status != ?', ['deleted'])
    ->orderBy('created_at', 'DESC')
    ->limit(20)

    // Field configurations with field-first pattern
    ->field('id')
        ->label('ID')
        ->hide()

    ->field('title')
        ->label('Article Title')
        ->link('?page=posts&action=edit&id=%id%')
        ->truncate(80)

    ->field('author_name')
        ->label('Author')
        ->fn(function($row) {
            return $row['first_name'] . ' ' . $row['last_name'];
        })

    ->field('category')
        ->label('Category')
        ->type('select')
        ->options([
            'tech' => 'Technology',
            'news' => 'News',
            'blog' => 'Blog'
        ])

    ->field('status')
        ->label('Status')

    ->field('photos')
        ->label('Images')
        ->image(['size' => 60, 'max_images' => 2])

    ->field('created_at')
        ->label('Publication Date')
        ->type('datetime')

    // Actions
    ->setPage('posts')
    ->setDefaultActions()

    ->getTable();

Row Actions

Row actions are buttons that appear for each table row, allowing users to perform operations like Edit, Delete, View, or custom actions.

Quick Example

$table = \Builders\TableBuilder::create($model, 'posts_table')
    ->setActions([
        'edit' => [
            'label' => 'Edit',
            'link' => '?page=posts&action=edit&id=%id%'
        ],
        'delete' => [
            'label' => 'Delete',
            'action' => [$this, 'actionDelete'],
            'confirm' => 'Are you sure?',
            'class' => 'btn-danger'
        ]
    ]);

Default Actions Helper

Use setDefaultActions() to automatically generate Edit and Delete actions:

$table = \Builders\TableBuilder::create($model, 'posts_table')
    ->setDefaultActions();  // Auto-generates Edit and Delete actions

See: Row Actions Documentation

Bulk Actions

For operations on multiple rows at once, use bulk actions.

See: Bulk Actions Documentation

Table Styling

TableBuilder provides comprehensive styling options including field-specific styling with conditional classes.

See: Table Styling Documentation

Output Methods

Understanding render() vs getResponse()

When to use each method

render() or getTable() - Use when you have NO row actions or bulk actions with callbacks:

// Simple table - no actions or only link actions
$html = TableBuilder::create($model, 'table_id')
    ->field('title')->link('?page=posts&action=edit&id=%id%')
    ->render();

Response::render($view, ['html' => $html]);

getResponse() - REQUIRED when you have row actions or bulk actions with callback functions:

// Table with action callbacks - MUST use getResponse()
$response = TableBuilder::create($model, 'table_id')
    ->setActions([
        'delete' => [
            'label' => 'Delete',
            'action' => [$this, 'actionDelete'], // Callback function
            'confirm' => 'Are you sure?'
        ]
    ])
    ->getResponse(); // Returns ['html' => '...', ...callback results]

// Pass entire $response array to Response::render()
Response::render($view, $response);
AJAX Handling

Response::render() automatically handles AJAX requests. When the table makes AJAX calls for sorting, pagination, or filtering, Response::render() detects this and returns JSON automatically. No manual checks needed!

Common Mistake
// ❌ WRONG - Don't concatenate with render() when you have callbacks
$response['html'] = $titleHtml . $tableBuilder->render();
Response::render($view, $response);

// ✅ CORRECT - Use getResponse() and add other data separately
$response = $tableBuilder->getResponse();
$response['title_html'] = $titleHtml;
Response::render($view, $response);

Why? getResponse() returns ['html' => '...', ...action_results]. The action results are needed for AJAX/JSON handling and table reloading after callbacks execute.

All Available Output Methods


$table = \Builders\TableBuilder::create($model, 'posts_table')
    ->limit(20)
    ->orderBy('created_at', 'desc');

// 1. Get complete data array (advanced usage)
$data = $table->getData();
// Returns: ['rows' => [...], 'info' => ListStructure, 'page_info' => PageInfo]

// 2. Get HTML table only (no actions with callbacks)
$html = $table->render();
echo $html;

// 3. Alternative syntax for render()
$html = $table->getTable();
echo $html;

// 4. Get response array (required for action callbacks)
$response = $table->getResponse();
// Returns: ['html' => '...', ...additional data from actions]

// 5. Get only function results from actions
$results = $table->getFunctionsResults();
    

Key Features Summary

  • Field-First Pattern: Configure each field with clear, grouped method chains
  • Fluent Interface: Method chaining for readable code
  • Action Functions: Execute custom logic on selected rows with return values for theme integration
  • Advanced Queries: Support for JOINs, WHERE conditions, and custom callbacks
  • Flexible Display: Links, images, files, truncation, custom formatters
  • Styling Options: Comprehensive table and field-specific styling with conditional classes
Loading...