ArtiGrid Documentation

This page introduces the main ArtiGrid features with practical examples. Each section explains what the method does, when to use it, and how to apply it in a real project.

Get Started

ArtiGrid can be configured quickly using a simple configuration file. In this version, baseUrl is no longer required because the library now detects paths automatically.

What this means You only need to configure your database, form behavior, general CRUD options, and optional mail settings.

Example configuration

<?php
return [
    'db' => [
        'driver' => 'mysql',
        'host' => 'localhost',
        'port' => 3306,
        'dbname' => 'artigrid',
        'user' => 'root',
        'password' => '',
        'charset' => 'utf8'
    ],
    'forms' => [
        'required_all_fields' => true // form field required option
    ],
    'filter' => true,
    'search' => true,
    'add' => true,
    'refresh' => true,
    'actionsPosition' => 'right',
    'edit' => true,
    'view' => true,
    'checkbox' => true,
    'dropdownpage' => true,
    'pagination' => true,
    'delete' => true,
    'delete_multiple' => true,
    'mail' => [
        'host' => 'smtp.gmail.com',
        'username' => 'daniel.telematico@gmail.com',
        'password' => 'your-password',
        'port' => 587,
        'secure' => 'tls',
        'from' => 'daniel.telematico@gmail.com',
        'from_name' => 'ArtiGrid'
    ]
];
?>

Core

The core of ArtiGrid is based on defining a data source and rendering it using different modes. By default, it generates a full CRUD interface, but you can also render specific forms depending on your needs.

How it works You define a table or query, configure the grid, and then call render() to display it.

Basic Example

include 'D:\dev\laragon\www\artigrid\ArtiGrid.php';

$grid = new ArtiGrid();
$grid->table('users');

echo $grid->render();

Example result

ArtiGrid basic example

Render Modes

ArtiGrid supports multiple rendering modes that allow you to display only specific parts of the CRUD system.

echo $grid->render();           // Full CRUD
echo $grid->render("insert");   // Insert form only
echo $grid->render("edit", 1);  // Edit record with ID 1
echo $grid->render("view", 2);  // View record with ID 2
echo $grid->render("select");   // Custom select / login form
Tip Use full CRUD for admin panels, and specific modes for custom pages like forms or login screens.

Insert Form Mode

ArtiGrid allows you to generate a standalone insert form using render('insert').

This mode displays only the form for creating new records, without showing the grid. It is ideal when you want a dedicated page for data entry.

When to use it
  • Create separate pages for forms
  • Build custom workflows
  • Hide the grid and focus on input

Example

$grid = new ArtiGrid();

$grid->table('payments')
    ->template('bootstrap5')
    ->required(false)
    ->validation_required('customerNumber')
    ->validation_required('checkNumber');

echo $grid->render('insert');
Pro tip Combine this with formFields() to control exactly which fields are displayed in the form.

Fields

ArtiGrid lets you organize and customize fields so your forms become easier to read and more user-friendly. You can group fields, change field types, and even rename column labels without modifying your database.

Arrange Fields

$grid->fields_arrange('name,date,office', 'Group 1 - Names', true, false);
What it does Groups fields visually inside forms to improve readability and structure.

Field Types (Select, Input, etc.)

$grid->setFieldType('status', 'select');
$grid->setSelect('status', ['Active', 'Inactive']);
Use case Replace manual input with dropdowns to reduce user errors.

Column Rename

The colRename() method allows you to change how column headers are displayed in the grid without modifying the database structure.

This is useful when your database uses technical names but you want more user-friendly labels in the interface.

$grid = new ArtiGrid();

$grid->table('products')
    ->template('bootstrap5')
    ->colRename('productName', 'Product Name') // rename column
    ->modal();

echo $grid->render();
Tip You can rename multiple columns to make your grid more readable for users.

Relational Combobox

The combobox() method allows you to transform a field into a dropdown (select) using either dynamic or static data sources.

It supports two types of sources:

  • Database table: load options from another table by defining the value and label columns.
  • Array: define a fixed list of options directly in your code.

In this example, the officeCode field is linked to the offices table, using officeCode as the value and city as the label. The lastName field uses a static array as a simple dropdown source.

Why use it Comboboxes make forms easier to use by replacing manual input with predefined selections, which helps reduce errors and improves usability.

Example

$grid = new ArtiGrid();
$grid->table('employees')
    ->template('bootstrap5')
    ->unset('filter', false)
    ->modal();

$grid->combobox('lastName', [
    'Activate' => 'Activate',
    'Desactive' => 'Desactive'
]);

$grid->combobox('officeCode', 'offices', 'officeCode', 'city');

echo $grid->render();

Inline Edit

Inline editing allows users to modify table data directly without opening a separate form or modal window. This is especially helpful for fast edits and repeated updates.

ArtiGrid supports inline editing with different field types such as text, textarea, date, and select. In the example below, lastName is edited as a textarea and officeCode is handled as a dropdown list.

Example

$grid = new ArtiGrid();
$grid->table('employees')
    ->template('bootstrap5')
    ->unset('filter', false)
    ->setFieldType('lastName', 'textarea')
    ->setFieldType('officeCode', 'select')
    ->setSelect('officeCode', [
        "2" => 'Boston',
        "7" => 'London',
        "3" => 'NYC',
        "4" => 'Paris',
        "1" => 'San Francisco',
        "6" => 'Sydney',
        "5" => 'Tokyo'
    ])
    ->inlineEdit()
    ->combobox('officeCode', 'offices', 'officeCode', 'city')
    ->perPage(10)
    ->modal();

echo $grid->render();
Benefit Inline editing improves speed, reduces clicks, and helps users update information more naturally.

Field Conditions

The fieldCondition() method allows you to create dynamic show/hide rules for form fields without writing any JavaScript. Rules are defined in PHP and automatically injected into the frontend via data-field-conditions.

When a condition is defined such as: $grid->fieldCondition('email', 'officeCode', '==', 4, 'hide'), ArtiGrid stores an internal rule indicating that the email field depends on the value of officeCode. When the form is rendered, these rules are converted into JSON and injected into the frontend. JavaScript reads them, identifies the trigger field, and evaluates the rule in real time.

How it works
  • ArtiGrid stores the rule internally and serializes it as JSON on the form element
  • On load, JavaScript reads the conditions and evaluates them immediately
  • Every time the user changes the trigger field, the rule is re-evaluated
  • In edit mode, the condition is applied at form open time based on existing data — no user interaction required

Signature

$grid->fieldCondition(
    string $field,      // Field to show or hide
    string $dependsOn,  // Field that triggers the rule
    string $operator,   // ==, !=, >, <, >=, <=, in, not_in
    mixed  $value,      // Value or array to compare against
    string $action      // 'show' (default) or 'hide'
);

Example — Hide email when officeCode equals 4

$grid = new ArtiGrid();
$grid->table('employees')
    ->template('bootstrap5')
    ->fieldCondition('email', 'officeCode', '==', 4, 'hide');

echo $grid->render();
What this does When the user selects officeCode = 4 in the form, the email field is hidden automatically. When any other value is selected, the field becomes visible again.

Supported operators

  • == → equals
  • != → not equals
  • > / < → greater / less than
  • >= / <= → greater or equal / less or equal
  • in → value is in array
  • not_in → value is not in array

Multiple conditions — chaining

$grid->fieldCondition('email',     'officeCode', '==', 4,                   'hide')
     ->fieldCondition('rut_tutor',  'tipo',       '==', 'menor',             'show')
     ->fieldCondition('descuento',  'convenio',   'in', ['isapre','fonasa'], 'show');
Chaining fieldCondition() returns $this, so you can chain as many rules as needed in a single fluent expression.

Insert mode vs Edit mode

In insert mode, the condition evaluates as soon as the form renders and again on every change to the trigger field. In edit mode, ArtiGrid reads the saved record value and applies the rule immediately when the form opens — no user interaction needed.

Use cases
  • Show/hide fields based on a dropdown selection
  • Conditional sections in multi-step forms
  • Role-based field visibility without extra JavaScript
  • Dynamic form layouts that adapt to user input in real time

Send Email

ArtiGrid allows you to send emails automatically or manually using built-in methods. This is useful for notifications, confirmations, and alerts when data is inserted into the system.

Two ways to send emails
  • Automatic → when inserting data (CRUD event)
  • Manual → using sendMail()

1. Automatic Email on Insert

You can automatically send an email when a new record is inserted using sendEmailInsert().

$grid = new ArtiGrid();

$grid->table('payments')
    ->formFields([
        'customerNumber',
        'checkNumber',
        'paymentDate',
        'amount'
    ])

    ->sendEmailInsert(true, [
        'to' => ['daniel.telematico@gmail.com'],
        'subject' => 'New order',
        'body' => "
            New order
            ID: {id}
            Client: {customerNumber}
            State: {checkNumber}
        "
    ]);

echo $grid->render();
How it works
  • The email is triggered automatically after inserting a record
  • You can define recipients, subject, and message body
  • Dynamic placeholders like {id} are replaced at runtime

Dynamic placeholders

You can include any field from your table using curly braces:

  • {id}
  • {customerNumber}
  • {checkNumber}
  • {any_field}
Example
Client: {customerNumber} → replaced with real value

2. Manual Email Sending

You can also send emails manually at any point using sendMail().

$grid->sendMail(
    'cliente@email.com',
    'Purchase made',
    'Thank you for your purchase. I have attached your product.'
);
Use case Useful for custom actions, notifications outside CRUD, or integration with buttons and callbacks.

3. Mail Configuration

Email functionality depends on your SMTP configuration in the config file.

'mail' => [
    'host' => 'smtp.gmail.com',
    'username' => 'your@email.com',
    'password' => 'your-password',
    'port' => 587,
    'secure' => 'tls',
    'from' => 'your@email.com',
    'from_name' => 'ArtiGrid'
]
Important Make sure your SMTP credentials are correct, otherwise emails will not be sent.

Use cases

  • Order confirmation emails
  • Admin notifications
  • User alerts
  • System logs or events
Pro Tip Combine email with:
  • Callbacks
  • Custom Buttons
  • Authentication
to build automated workflows.

UI

You can improve readability by highlighting cells and rows based on field values. This is useful for alerts, statuses, or priority indicators.

Conditional Colors for Cells and Rows

ArtiGrid applies conditional styling to individual cells or entire rows using dynamic rules based on field values — no extra JavaScript needed.

1. Apply Color to a Specific Cell — CellColor()

Highlights a single cell when a condition is met. The fourth argument is a CSS style string applied directly to that cell.

$grid->CellColor('officeCode', '==', 4, 'background:red;color:white');
What this does When officeCode equals 4, the cell background becomes red and the text becomes white.

2. Apply Style to an Entire Row — RowColor()

Styles the full row when a condition is met. The fourth argument can be a CSS string or a structured array with multiple properties.

$grid->RowColor('paymentDate', '==', '2004-11-14', [
    'background'  => '#8DED79',
    'color'       => '#000',
    'font-weight' => 'bold'
]);
Why use an array? Passing an array allows you to define multiple CSS properties cleanly without string concatenation or !important overrides.

3. Full Example

$grid = new ArtiGrid();

$grid->CellColor('checkNumber', '==', 'NG94694',    'background:green;color:white;');
$grid->CellColor('paymentDate', '==', '2004-03-10', 'background:red;color:white;');

$grid->RowColor('paymentDate', '==', '2004-11-14', [
    'background'  => '#8DED79',
    'color'       => '#000',
    'font-weight' => 'bold'
]);

$grid->table('payments');
echo $grid->render();
Summary
  • CellColor() → styles individual cells using a CSS string
  • RowColor() → styles full rows using a CSS string or an array of properties
  • Both methods support operators: ==, !=, >, <, >=, <=
  • Rules are evaluated dynamically on the frontend after each data load

Example

$grid = new ArtiGrid();

$grid->table('payments')
    ->template('bootstrap5')
    ->buttonsArrange();

echo $grid->render();
Result Instead of showing multiple buttons in each row, all actions will be grouped inside a dropdown menu, improving readability and saving space.

Everything Deactivated

The unset() method allows you to disable specific features of the grid. In this example, all main functionalities are turned off. This effectively deactivates filters, search, add button, refresh, pagination, checkboxes, and action buttons, leaving a minimal table display.

    $grid = new ArtiGrid();
    $grid->table('employees')
        ->template('bootstrap5')
        ->perPage(10)
        ->unset('filter', false)
        ->unset('add', false)
        ->unset('search', false)
        ->unset('refresh', false)
        ->unset('pagination', false)
        ->unset('dropdownpage', false)
        ->unset('checkbox', false)
        ->unset('actions', false);
    echo $grid->render();
    

Validation

Validation helps you control the information users submit. You can require specific fields, disable the “all required” behavior, and define which inputs must be completed before submitting the form.

Basic Required Validation

$grid = new ArtiGrid();
$grid->table('employees')
    ->template('bootstrap5')
    ->unset('filter', false)
    ->required(false)
    ->validation_required([
        'lastName',
        'firstName',
        'extension'
    ])
    ->modal();

echo $grid->render();
Why use it This gives you more precise control over which fields are mandatory in each form.

Check for Duplicate Records When Inserting

ArtiGrid also allows you to prevent duplicate records when inserting data. This is useful when certain combinations of fields should be unique.

In the following example, the insert will be blocked if another record already exists with the same title and description.

$grid = new ArtiGrid();

$grid->table('gallery')
    ->required(false)
    ->validation_required('image')
    ->validation_required('title')
    ->validation_required('description')
    ->template('bootstrap5')
    ->fieldType('image', 'image')
    ->formFields([
        'image',
        'title',
        'description'
    ])
    ->checkDuplicateRecord([
        'title',
        'description'
    ]);

echo $grid->render();
Use case This is especially useful for galleries, products, catalogs, or any table where duplicate content should be avoided.

Query

ArtiGrid allows you to control how data is retrieved and displayed using filters, sorting, grouping, and custom SQL queries.

Basic Query (Custom SQL)

$grid = new ArtiGrid();
$grid->query('SELECT * FROM products WHERE productCode = "S32_2206"')
    ->template('bootstrap5')
    ->modal();

echo $grid->render();
Use case Useful when you want filtered datasets, reports, or read-only views.

CRUD with Query

ArtiGrid also allows you to build a full CRUD interface using custom SQL queries through the query() method.

This provides full control over the data source, allowing you to filter, join, or transform data directly at the query level instead of relying on a fixed table.

It is especially useful for advanced scenarios where you need specific datasets or more complex data structures.

When using custom queries, ArtiGrid cannot automatically determine which table should be used for write operations (insert, update, delete). For this reason, you must explicitly define it using the editable() method.

The editable() method receives the table name, enabling ArtiGrid to handle all CRUD operations such as add, view, edit, and delete, even when the data is loaded from a custom query.

$grid = new ArtiGrid();
$table = 'products';

$grid->query("SELECT * FROM $table WHERE productCode = 'S32_2206'")
    ->editable($table)
    ->template('bootstrap5')
    ->formFields([
        'productCode',
        'productName'
    ])
    ->crudCol([
        'id',
        'productCode',
        'productName'
    ])
    ->required(false)
    ->validation_required('productCode')
    ->modal();

echo $grid->render();
Note When using custom queries with joins, make sure the table defined in editable() matches the primary table you intend to update.

Filters & Sorting

You can filter and sort data using built-in methods without modifying your database queries directly.

// Filtering
$grid->where('status','=','active');
$grid->whereLike('name','john');

// Sorting
$grid->orderby('id','desc');
What it does
  • where() → exact filtering
  • whereLike() → partial search
  • orderby() → sort results

Order By Example

The orderby() method allows you to define how data is sorted by default when the grid loads.

You can choose ascending (asc) or descending (desc) order for any column.

$grid = new ArtiGrid();

$grid->table('payments')
    ->template('bootstrap5')
    ->orderby('paymentId', 'desc')
    ->formFields(['customerNumber','checkNumber','amount'])
    ->modal();

echo $grid->render();
Tip Use desc for latest records first (common in dashboards) and asc for alphabetical or chronological order.

Group By

The groupby() method allows you to group records based on one or more fields.

This is useful when working with repeated values like customers, categories, or related records, making the data easier to understand.

$grid = new ArtiGrid();

$grid->table('payments')
    ->template('bootstrap5')
    ->groupby(['customerNumber'])
    ->formFields(['customerNumber','checkNumber','amount'])
    ->modal();

echo $grid->render();
Tip You can group by multiple fields:
$grid->groupby(['customerNumber','checkNumber']);

Charts

ArtiGrid includes built-in chart support, allowing you to visualize data directly from the database without requiring extra libraries in your code.

With the chart_labels() method, you can define labels, multiple datasets, and even execute SQL queries dynamically by prefixing the query with #.

By combining chart_labels() with chart_view(true), you can render visual charts together with your grid, giving users both tabular and graphical views of the same information.

What this feature provides
  • Direct chart rendering from your grid configuration
  • Support for multiple datasets
  • Dynamic SQL-based datasets using #query
  • Custom chart options such as scales, legends, and plugins

Example

$chart = new ArtiGrid();

$chart->table('orderdetails')
    ->template('bootstrap5');

$chart->chart_labels(
    ['S24_2841', 'S24_3420', 'S24_3949', 'S24_4278', 'S32_4289', 'S50_1341'],
    [
        [
            'label' => '# of Quantity Ordered',
            'data' => '#select quantityOrdered from orderdetails where id IN (60,61,62,63,64,65)',
            'backgroundColor' => 'rgba(255, 99, 132, 0.2)',
            'borderColor' => 'rgba(255, 99, 132, 1)',
            'borderWidth' => 1
        ],
        [
            'label' => '# Price of Order',
            'data' => '#select priceEach from orderdetails where id IN (60,61,62,63,64,65)',
            'backgroundColor' => 'rgba(30, 23, 132, 0.2)',
            'borderColor' => 'rgba(30, 23, 132, 0.2)',
            'borderWidth' => 1
        ]
    ],
    'pie',
    [
        'scales' => [
            'y' => ['beginAtZero' => true]
        ],
        'plugins' => [
            'legend' => ['display' => true]
        ]
    ]
);

$chart->chart_view(true);

echo $chart->render();
How the example works
  • The labels define the visible chart categories
  • Each dataset can be static or loaded dynamically using SQL
  • The chart type in this example is pie
  • Options allow you to configure scales, legends, and other chart settings

Supported Chart Types

ArtiGrid supports several common chart types, which can be selected through the third parameter of chart_labels().

  • bar — useful for comparing values across categories
  • line — useful for trends over time or sequences
  • pie — useful for proportions and percentage-like distributions
  • doughnut — similar to pie, but with a centered empty space
  • radar — useful for comparing multiple variables around a central axis
  • polarArea — useful for comparing values by radial area

Examples of Chart Type Usage

// Bar chart
$grid->chart_labels($labels, $datasets, 'bar');

// Line chart
$grid->chart_labels($labels, $datasets, 'line');

// Pie chart
$grid->chart_labels($labels, $datasets, 'pie');

// Doughnut chart
$grid->chart_labels($labels, $datasets, 'doughnut');

// Radar chart
$grid->chart_labels($labels, $datasets, 'radar');

// Polar area chart
$grid->chart_labels($labels, $datasets, 'polarArea');
Tip Use bar and line when comparing values, and use pie or doughnut when showing proportions of a whole.

Dynamic SQL Datasets

One of the most powerful features is the ability to load chart data directly from SQL queries by prefixing the query string with #.

'data' => '#select quantityOrdered from orderdetails where id IN (60,61,62,63,64,65)'

This makes it possible to build real-time visual reports directly from your database without manually preparing arrays.

Use cases
  • Sales dashboards
  • Order summaries
  • Inventory reports
  • Product performance charts
  • Admin panels with analytics

JSON API

ArtiGrid can build CRUD interfaces directly from JSON data, which makes it useful when working with APIs or external data sources instead of a direct database table.

Example


            $ch = curl_init();
            curl_setopt($ch, CURLOPT_URL, "https://jsonplaceholder.typicode.com/todos");
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            $response = curl_exec($ch);
            curl_close($ch);

            // 2. Convert to array
            $data = json_decode($response, true);

            // 3. Take only the first 20 records
            $data = array_slice($data, 0, 20);

            // 4. Create rows for the grid with the fields you need
            $rows = [];
            foreach ($data as $item) {
                $id = $item['id'];
                $rows[] = [
                    'id' => $id,
                    'name' => "Usuario $id",
                    'email' => "usuario$id@mail.com",
                    'created_at' => date('Y-m-d', strtotime("-$id days")),
                    'title' => $item['title'],
                    'completed' => $item['completed'] ? 'Sí' : 'No'
                ];
            }

            // 5. Define grid columns
            $columns = [
                ['name'=>'id','label'=>'ID','type'=>'number'],
                ['name'=>'name','label'=>'Nombre','type'=>'text'],
                ['name'=>'email','label'=>'Correo','type'=>'text'],
                ['name'=>'created_at','label'=>'Fecha','type'=>'date'],
                ['name'=>'title','label'=>'Título','type'=>'text'],
                ['name'=>'completed','label'=>'Completado','type'=>'text'],
            ];

            // 6. Create and configure the grid
            $grid = new ArtiGrid();
            $grid->perPage(10);
            $grid->addCustomBtn(
                'btn btn-sm btn-info', // class
                'ver',                 // action JS
                '', // icon
                []                     // conditions
            );

            // 7. Prepare JSON for the grid
            $jsonData = [
                'columns' => $columns,
                'rows' => $rows
            ];

            // 8. Render the grid
            echo $grid->crudJson($jsonData)->render();
        
Use case This is ideal when your project consumes remote services or when data is already available as JSON.

Advanced

Advanced features let you build more dynamic and flexible CRUD interfaces. One common example is joining related tables.

Example


            $grid = new ArtiGrid();
            $grid->table('employees');
            $grid->join('officeCode','offices','officeCode');
            echo $grid->render();
        
Next step You can combine joins with comboboxes, custom buttons, pagination, hidden columns, and more.

Callbacks

ArtiGrid callbacks allow you to hook into the CRUD lifecycle and execute custom logic before or after database operations. They are especially powerful for implementing advanced validation, enforcing business rules, transforming data, or conditionally stopping an operation.

Unlike basic validations, callbacks give you full control over the request flow. You can validate multiple fields at once, apply complex conditions, and return structured errors that are automatically handled and displayed in the frontend.

How it works
You define callbacks per table and event (e.g. beforeInsert, beforeUpdate), and ArtiGrid automatically executes them at runtime. Each callback receives the form data and can either:
  • Return modified data → continues the process
  • Return success: false with errors → stops the operation

Validation with callbacks

Callbacks can act as a centralized validation layer. Inside beforeInsert and beforeUpdate, you can validate fields, enforce rules, and return structured error messages.

Errors can be defined at two levels:

  • fields → specific field errors (shown inline in inputs)
  • global → general errors (shown as alerts/messages)
Important
If a callback returns: { success: false, errors: {...} }, the operation is automatically canceled and all errors are sent to the frontend for display.

Example callback configuration

<?php
// callbacks/name_table.php  // example: productlines.php

return [
    'beforeInsert' => [
        ['callback' => 'insert_paymets', 'file' => 'functions.php'],
    ],

    'beforeUpdate' => [
        ['callback' => 'update_paymets', 'file' => 'functions.php'],
    ],
];
?>

Example callback function (with validation)

<?php
function insert_paymets($data) {

    $errors = [
        'fields' => [],
        'global' => []
    ];

    if (empty($data['customerNumber'])) {
        $errors['fields']['customerNumber'] = 'Customer is required';
    }

    if (!isset($data['amount']) || $data['amount'] === '') {
        $errors['fields']['amount'] = 'Amount is required';
    } elseif (!is_numeric($data['amount'])) {
        $errors['fields']['amount'] = 'Amount must be a number';
    }

    if (!empty($data['paymentDate'])) {
        if ($data['paymentDate'] > date('Y-m-d')) {
            $errors['global'][] = 'Payment date cannot be in the future';
        }
    }

    if (!empty($errors['fields']) || !empty($errors['global'])) {
        return [
            'success' => false,
            'errors' => $errors,
            'data' => $data
        ];
    }

    return $data;
}
?>
Best practice
Always return the $data when validation passes. This allows ArtiGrid to continue the process normally. Use structured errors instead of die() to keep a clean and user-friendly flow.

Available callback events

  • beforeInsert → runs before inserting a new record
  • afterInsert → runs after inserting a new record
  • beforeUpdate → runs before updating an existing record
  • beforeDelete → runs before deleting a record
Use cases
  • Advanced validation beyond simple required fields
  • Preventing invalid or inconsistent data
  • Business rules enforcement
  • Data transformation before saving
  • Logging or auditing operations
  • Blocking operations under certain conditions

Authentication, Login & Permissions

ArtiGrid allows you to build a complete login system using custom forms, callbacks, sessions, and role-based permissions (RBAC).

What you can do Create login forms, validate users with callbacks, manage sessions, and control access using roles and permissions.

1. Custom Login Form

You can design your own login interface using templates. The placeholders {user}, {password} and {action} are automatically replaced by ArtiGrid.


$html = '
<div class="container d-flex justify-content-center align-items-center">
    <div class="col-md-8 col-lg-4">
        <div class="card shadow-lg border-0">
            <div class="card-body p-4">
                <h4 class="text-center mb-4">
                    Login Example
                </h4>
                <div class="mb-3">
                    <label class="form-label">User *</label>
                    {user}
                </div>
                <div class="mb-3">
                    <label class="form-label">Password *</label>
                    {password}
                </div>
                <div class="d-grid">
                    {action}
                </div>
            </div>
        </div>
    </div>
</div>';
$grid->setSelectFormTemplate($html);

2. Login Configuration

$grid->table('users')
    ->template('bootstrap5')
    ->required(false)
    ->validation_required('user')
    ->validation_required('password')
    ->formFields(['user','password']);

echo $grid->render('select');
Important The select mode is used as a login form.

3. Callback Validation

The login is validated using a callback before processing the form.

// callbacks/users.php
return [
    'beforeSelect' => [
        ['callback' => 'login', 'file' => 'functions.php'],
    ]
];
// functions.php

function login($data){

    if (session_status() === PHP_SESSION_NONE) {
        session_start();
    }

    $db = DB::connect();

    $user = trim($data['user'] ?? '');
    $pass = trim($data['password'] ?? '');

    if ($user === '' || $pass === '') {
        return [
            'success' => false,
            'message' => 'Empty username or password'
        ];
    }

    $q = new Queryfy($db);
    $row = $q->table('users')
            ->where('user', $user)
            ->limit(1)
            ->get();

    $row = $row[0] ?? null;

    if ($row && password_verify($pass, $row['password'])) {

        $permissions = getPermissionsByRole($row['rol']);

        $_SESSION['artigrid_auth'] = [
            'id' => $row['id'],
            'rol' => $row['rol'],
            'usuario' => $row['user'],
            'permissions' => $permissions
        ];

        return [
            'success' => true,
            'message' => 'Successful login',
            'redirect' => 'management.php'
        ];
    }

    return [
        'success' => false,
        'message' => 'Incorrect username or password'
    ];
}

4. Frontend Handling

document.addEventListener('artigrid_select_form_response', function(e){

    const { response } = e.detail;

    if (response.success && response.data?.success) {
        if (response.data?.redirect) {
            window.location.href = response.data.redirect;
        }
    } else {
        const msg = response.data?.message || response.message;

        Swal.fire({
            icon: 'error',
            title: 'Error',
            text: msg
        });
    }

});

5. Access Logged User

$auth = $grid->auth();

echo $auth['usuario'];
echo $auth['rol'];

6. Permissions (RBAC)

You can control what each user can do using permissions.

if (!$grid->can('add')) {
    $grid->unset('add', false);
}

if (!$grid->can('delete')) {
    $grid->unset('delete', false);
}

if ($grid->canAny(['add','edit'])) {
    echo "User has access";
}

if ($grid->isRole('admin')) {
    $grid->unset('refresh', false);
}

7. Role Permissions Example

function getPermissionsByRole($role){
    return [
        'admin'  => ['add','view','edit','delete'],
        'editor' => ['view','edit'],
        'viewer' => ['view']
    ][$role] ?? [];
}
Summary With ArtiGrid you can build:
  • Login system
  • Session management
  • Role-based access control
  • Frontend feedback

Dependent Dropdowns

ArtiGrid allows you to create dependent dropdowns, where one field dynamically changes its options based on the value selected in another field.

This improves data accuracy and user experience by preventing invalid selections and guiding users through a logical flow.

How it works A child field listens to a parent field and filters its data automatically based on the selected value.

Basic Example (Office → Manager)

When the user selects an office, the manager dropdown will only show employees from that office.

$grid->combobox(
    'manager',
    'employees',
    'employeeNumber',
    ['firstName','lastName'], // label
    'office',                 // parent field
    'officeCode'              // DB column to match
);
Explanation
  • office → field in the form (parent)
  • officeCode → column in database
  • The child dropdown updates automatically

Full Example (Multiple Dependencies)

$grid = new ArtiGrid();

$grid->table('consultation')
    ->template('bootstrap5')
    ->required(false)
    ->validation_required([
        'office',
        'manager',
        'country',
        'region',
        'city'
    ])

    ->fields_arrange('office,manager,name', 'Group 1 - Names', true, true)
    ->fields_arrange('country,region,city', 'Group 2 - Regions', true, true)

    // Parent dropdown
    ->combobox('office','offices','officeCode','city')

    // Static dropdown
    ->combobox('name', [
        'Pedro' => 'Pedro',
        'Juan' => 'Juan',
        'JHON' => 'JHON',
        'JACKSON' => 'JACKSON'
    ])

    // Dependent dropdown
    ->combobox(
        'manager',
        'employees',
        'employeeNumber',
        ['firstName','lastName'],
        'office',
        'officeCode'
    )

    // Multi-level dependency (Country → Region → City)
    ->combobox('country','meta_location','id','local_name', null, null, [
        'type' => ['=', 'CO']
    ])

    ->combobox('region','meta_location','id','local_name',
        'country',
        'in_location',
        ['type'=> ['=', 'RE']]
    )

    ->combobox('city','meta_location','id','local_name',
        'region',
        'in_location',
        ['type'=> ['=', 'CI']]
    );

echo $grid->render('insert');
Use cases
  • Country → Region → City
  • Category → Subcategory
  • Office → Employee
  • Brand → Model
Pro tip You can combine dependencies with filters (where) to create even more precise dropdowns.

HTML Templates for Form Fields

ArtiGrid allows you to fully customize the layout of your forms and views using custom HTML templates. This gives you complete control over the design while the library continues to handle all CRUD logic automatically.

What you can customize
  • Insert form
  • Edit form
  • View mode (record details)

1. Insert Form Template

This template is used when creating new records. Placeholders like {customerNumber} are automatically replaced by ArtiGrid.

$insert = '<div class="order-form">
    <h2>Customer Order Form</h2>

    <div class="form-group">
        <label>Customer Number: *</label>
        {customerNumber}
    </div>

    <div class="form-group">
        <label>Order Date: *</label>
        {orderDate}
    </div>

    <div class="form-actions">
        {action}
    </div>

</div>';

$grid->setInsertFormTemplate($insert);
---

2. Edit Form Template

This template is used when editing existing records. You can change structure or layout without affecting CRUD functionality.

$edit = '<div class="order-form">
    <h2>Edit Order</h2>

    <div class="form-group">
        <label>Customer Number</label>
        {customerNumber}
    </div>

    <div class="form-group">
        <label>Order Date</label>
        {orderDate}
    </div>

    {action}

</div>';

$grid->setEditFormTemplate($edit);
---

3. View Template

This template is used to display record details in a read-only format.

$view = "<div class='table-responsive'>
    <table class='table table-bordered table-sm'>
        <tr>
            <th>Customer</th>
            <td>{customerNumber}</td>
        </tr>
        <tr>
            <th>Date</th>
            <td>{orderDate}</td>
        </tr>
    </table>
</div>";

$grid->setViewFormTemplate($view);
---
Important All placeholders like {field} are automatically replaced with real data or form inputs by ArtiGrid.
---

Full Example

$grid = new ArtiGrid();

$insert = '<div class="order-form">
    <h2>Customer Order Form</h2>

    <div class="form-group">
        <label>Customer Number: *</label>
        {customerNumber}
    </div>

    <div class="form-group">
        <label>Order Date: *</label>
        {orderDate}
    </div>

    <div class="form-group">
        <label>Status:</label>
        {status}
    </div>

    <div class="form-actions">{action}</div>
</div>';

$edit = '<div class="order-form">
    <h2>Edit Order</h2>

    <div class="form-group">
        <label>Order Number</label>
        {orderNumber}
    </div>

    <div class="form-group">
        <label>Order Date</label>
        {orderDate}
    </div>

    {action}
</div>';

$view = "<div class='table-responsive'>
    <table class='table table-bordered table-sm'>
        <tr>
            <th>Customer</th>
            <td>{customerNumber}</td>
        </tr>
        <tr>
            <th>Date</th>
            <td>{orderDate}</td>
        </tr>
    </table>
</div>";

$grid->setInsertFormTemplate($insert);
$grid->setEditFormTemplate($edit);
$grid->setViewFormTemplate($view);

$grid->table('orders')
    ->template('bootstrap5')
    ->perPage(10)
    ->required(false)
    ->validation_required('customerNumber')
    ->validation_required('orderDate')
    ->modal();

echo $grid->render();
---
Why this is powerful
  • You can design forms exactly like your UI needs
  • You separate logic from design
  • You can create layouts like dashboards or custom panels
  • Works perfectly with Bootstrap or any CSS framework

PDF

ArtiGrid includes integrated PDF support, allowing you to generate custom documents such as invoices, reports, or printable summaries directly from PHP.

What it does You can use getPDFObject() to access the internal PDF engine, build dynamic HTML content, and render it as a PDF document.

1. Get PDF Object

This retrieves the PDF object from ArtiGrid so you can generate a document using HTML content.

$pdf = $grid->getPDFObject();

2. Prepare Invoice Data

Define the main invoice information and the list of items.

$invoiceData = [
    "client_name" => "John Smith",
    "invoice_number" => "INV20260316",
    "invoice_date" => "2026-03-16",
    "due_date" => "2026-03-30",
    "notes" => "This is a sample invoice generated with ArtiGrid."
];

$items = [
    ["description" => "Product A", "quantity" => 2, "unit_price" => 1500],
    ["description" => "Product B", "quantity" => 1, "unit_price" => 3000],
    ["description" => "Service C", "quantity" => 3, "unit_price" => 1200]
];

3. Generate Table Rows and Calculate Totals

Loop through the items, generate the invoice rows, and calculate the final total.

$totalAmount = 0;
$rowsHtml = "";

foreach ($items as $index => $item) {
    $amount = $item['quantity'] * $item['unit_price'];
    $totalAmount += $amount;

    $rowsHtml .= "<tr>
        <td>" . ($index + 1) . "</td>
        <td>" . $item['description'] . "</td>
        <td>" . $item['quantity'] . "</td>
        <td>" . $item['unit_price'] . "</td>
        <td>" . $amount . "</td>
    </tr>";
}

4. Create Full HTML for the Invoice

Build the HTML layout that will be rendered inside the PDF.

$html = <<<EOD
<h1 style="text-align:center;">Invoice</h1>
<table>...</table>
<table border="1">
    ...
    $rowsHtml
    ...
</table>
<p><strong>Notes:</strong> {$invoiceData['notes']}</p>
EOD;
Tip You can fully customize the HTML structure, including tables, spacing, fonts, colors, borders, and headers.

5. Generate and Output the PDF

$pdf->SetFont('dejavusans', '', 12, '', true);
$pdf->AddPage();
$pdf->WriteHTML($html);
$pdf->Output("invoice_demo.pdf", "I");
exit;
Output modes
  • "I" → display the PDF in the browser
  • "D" → force download
  • "F" → save to file

Full Example

$grid = new ArtiGrid();

// Get PDF object
$pdf = $grid->getPDFObject();

// Sample invoice data
$invoiceData = [
    "client_name" => "John Smith",
    "invoice_number" => "INV20260316",
    "invoice_date" => "2026-03-16",
    "due_date" => "2026-03-30",
    "notes" => "This is a sample invoice generated with ArtiGrid."
];

$items = [
    ["description" => "Product A", "quantity" => 2, "unit_price" => 1500],
    ["description" => "Product B", "quantity" => 1, "unit_price" => 3000],
    ["description" => "Service C", "quantity" => 3, "unit_price" => 1200]
];

// Calculate totals
$totalAmount = 0;
$rowsHtml = "";

foreach ($items as $index => $item) {
    $amount = $item['quantity'] * $item['unit_price'];
    $totalAmount += $amount;

    $rowsHtml .= "<tr style='background-color:#f2f2f2;'>
        <td style='width:5%;text-align:center;'>" . ($index + 1) . "</td>
        <td style='width:50%;'>" . $item['description'] . "</td>
        <td style='width:15%;text-align:center;'>" . $item['quantity'] . "</td>
        <td style='width:15%;text-align:right;'>$ " . number_format($item['unit_price'],0,",",".") . "</td>
        <td style='width:15%;text-align:right;'>$ " . number_format($amount,0,",",".") . "</td>
    </tr>";
}

$totalFormatted = number_format($totalAmount, 0, ".", ",");

// Full HTML invoice
$html = <<<EOD
<h1 style="text-align:center;">Invoice</h1>

<table style="width:100%;font-size:12px;margin-bottom:20px;">
<tr>
    <td style="width:50%;">Client:<br><strong>{$invoiceData['client_name']}</strong></td>
    <td style="width:50%;">Invoice No.:<br><strong>{$invoiceData['invoice_number']}</strong></td>
</tr>
<tr>
    <td>Invoice Date:<br>{$invoiceData['invoice_date']}</td>
    <td>Due Date:<br>{$invoiceData['due_date']}</td>
</tr>
</table>

<table style="width:100%;border-collapse: collapse;font-size:11px;" border="1">
<tr style="background-color:#d9d9d9;">
    <th style="width:5%;">#</th>
    <th style="width:50%;">Description</th>
    <th style="width:15%;">Quantity</th>
    <th style="width:15%;">Unit Price</th>
    <th style="width:15%;">Amount</th>
</tr>
$rowsHtml
<tr style="background-color:#e1e1e1;">
    <td colspan="4" style="text-align:right;"><strong>Total</strong></td>
    <td style="text-align:right;"><strong>$ $totalFormatted</strong></td>
</tr>
</table>

<br>
<p><strong>Notes:</strong><br>{$invoiceData['notes']}</p>
EOD;

// Generate PDF
$pdf->SetFont('dejavusans', '', 12, '', true);
$pdf->AddPage();
$pdf->WriteHTML($html);
$pdf->Output("invoice_demo.pdf", "I");
exit;
Summary
  • Use getPDFObject() to access PDF features
  • Generate dynamic HTML for invoices, reports, or summaries
  • Write the HTML into the PDF document
  • Display it in the browser or save it as a file

Action Buttons Conditions

ArtiGrid allows you to control the visibility of action buttons such as view, edit, and delete based on dynamic conditions.

Using the setActionCondition() method, you can define rules that determine when a button should be shown or hidden for each row.

How it works Each condition is defined as:
  • Field name
  • Operator (==, !=, >, <, etc.)
  • Value to compare

Example

$grid = new ArtiGrid();

$grid->table('employees')
    ->template('bootstrap5')
    ->unset('filter', false)

    // Hide delete button for employee 1370
    ->setActionCondition('delete', ['employeeNumber', '!=', 1370])

    // Hide edit button for lastName = Hernandez
    ->setActionCondition('edit', ['lastName', '!=', 'Hernandez'])

    // Hide view button for employee 1501
    ->setActionCondition('view', ['employeeNumber', '!=', 1501])

    ->modal();

echo $grid->render();
What this does
  • Delete is disabled for employee 1370
  • Edit is disabled for users named "Hernandez"
  • View is disabled for employee 1501

Why use this

  • Restrict actions for specific records
  • Apply business rules directly in the UI
  • Prevent accidental edits or deletions
  • Simulate permission systems without backend logic
Important Conditions are evaluated on the client side, so they improve UX, but should still be combined with backend validation for security.

Custom Buttons

ArtiGrid allows you to create fully customizable action buttons for each row using the addCustomBtn() method.

These buttons can execute JavaScript functions, navigate to URLs, or include dynamic attributes based on row data.

What you can do
  • Create custom actions per row
  • Use dynamic values like {id} or {name}
  • Open external or internal links
  • Add custom attributes like data-* or onclick
  • Control visibility using conditions

Example

$grid = new ArtiGrid();

$grid->table('products')
    ->template('bootstrap5')
    ->addCustomBtn(
        'btn btn-sm btn-light',      // CSS classes
        '',                          // JS action (optional)
        '<i class="fa fa-globe"></i>', // icon HTML
        'Go',                        // button text

        // condition: show only if id != 31
        ['id', '!=', 31],

        // URL (dynamic)
        'https://google.cl/search?q={name}',

        // target
        '_blank',

        // extra attributes
        [
            'data-name' => '{name}',
            'onclick' => 'console.log("{id}")'
        ]
    )
    ->modal();

echo $grid->render();
Explanation
  • Class: defines button style (Bootstrap classes)
  • Icon: HTML icon (FontAwesome, SVG, etc.)
  • Text: label displayed in button
  • Condition: controls when the button appears
  • URL: supports dynamic placeholders like {name}
  • Target: "_blank" opens in new tab
  • Attributes: custom HTML attributes

Dynamic Placeholders

You can use placeholders that are automatically replaced with row values:

  • {id}
  • {name}
  • {any_field}
Example URL: https://google.cl/search?q={name}
Result: https://google.cl/search?q=ProductName

Use Cases

  • Redirect to detail pages
  • Open external services (Google, APIs, etc.)
  • Trigger JS actions (modals, alerts, logs)
  • Attach metadata with data-*
Pro Tip Combine custom buttons with:
  • setActionCondition()
  • buttonsArrange()
  • Permissions (RBAC)
to create advanced and professional UI behaviors.

Actions Position

ArtiGrid allows you to control where the action buttons (view, edit, delete) are displayed within the table using the actionsPosition() method.

By default, action buttons are displayed on the right side of the grid, but you can easily move them to the left depending on your layout needs.

Available positions
  • right → default position (end of the table)
  • left → moves actions to the beginning

Example

$grid = new ArtiGrid();

$grid->table('products')
    ->template('bootstrap5')
    ->required(false)
    ->actionsPosition('left') // move actions to the left
    ->validation_required('productCode')
    ->perPage(10)
    ->modal();

echo $grid->render();
What this does The action column (view, edit, delete) will be displayed at the beginning of each row instead of the end.

When to use it

  • When you want quick access to actions
  • When following UI patterns where actions come first
  • When combining with checkboxes or bulk actions
  • When using compact or admin-style layouts
Pro Tip Combine actionsPosition() with:
  • buttonsArrange() → dropdown actions
  • addCustomBtn() → custom buttons
  • setActionCondition() → conditional visibility
to create a fully customized action system.

HTML Template for CRUD

ArtiGrid allows you to fully customize the layout of your CRUD interface using HTML templates. This gives you complete control over where each element is rendered.

Instead of using a fixed layout, you can reposition components like: search inputs, buttons, pagination, and the table itself.

Key idea You design the UI, ArtiGrid injects the functionality automatically.

Available placeholders

  • {table} → main data table
  • {search_input} → search input
  • {search_column} → column selector
  • {add_button} → add record button
  • {bulk_delete} → delete multiple button
  • {pagination} → pagination controls
  • {perpage} → records per page selector
  • {refresh} → refresh button
  • {search} → legacy combined search
Important Unlike other tools, ArtiGrid separates search_input and search_column, allowing you to place them independently.

Example

$grid = new ArtiGrid();

$grid->setCrudTemplate('
    <div class="d-flex justify-content-between align-items-center mb-2 p-2">
        <div class="d-flex gap-2">
            {bulk_delete}
            {pagination}
        </div>

        <div class="d-flex gap-2">
            {add_button}
            {refresh}
        </div>
    </div>

    <div>{table}</div>

    <div class="d-flex align-items-center mt-2 p-2 w-100">
        <div>
            {perpage}
        </div>

        <div class="ms-auto d-flex gap-2 align-items-center">
            {search_input}
            {search_column}
        </div>
    </div>
');

$grid->table('payments')
    ->template('bootstrap5')
    ->fieldCss('customerNumber', ['customerNumber', 'demo'])
    ->formFields(['customerNumber','checkNumber','paymentDate', 'amount'])
    ->modal();

echo $grid->render();

What this layout does

  • Top section → buttons and pagination
  • Middle → table
  • Bottom → per-page + search controls
Use cases
  • Custom admin dashboards
  • Modern UI layouts
  • Minimalist interfaces
  • Integration with design systems
Pro Tip Combine setCrudTemplate() with:
  • buttonsArrange()
  • addCustomBtn()
  • setActionCondition()
to create fully customized and professional CRUD interfaces.

Overview Nested Table

Each nested grid is fully independent and can have its own actions, pagination, form fields, validation rules, column visibility, and conditional button behavior. ArtiGrid automatically handles all AJAX loading, parent–child filtering, and foreign key synchronization.

What ArtiGrid handles automatically
  • Parent–child relationship filtering
  • Dynamic AJAX loading per level
  • Independent CRUD operations per level
  • Multi-level nested hierarchy rendering
  • Pagination and searching inside child grids
  • Bulk edit and bulk delete operations
  • Automatic foreign key synchronization on insert

Hierarchy Example

This is the structure used in the full example on this page:

Level 1 orders
└─ Level 2 orderdetails  ← child of orders via orderNumber
└─ Level 3 products  ← child of orderdetails via productCode
└─ Level 2 customers  ← independent child of orders via orderNumber

Basic Nested Table

The nestedTable() method is the only thing you need to define a child grid. You provide a label, the parent key, the child table, and the child key.

Signature

$grid->nestedTable(
    string $label,       // Tab/section label shown in the UI
    string $parentKey,   // Column in the parent table (e.g. "orderNumber")
    string $childTable,  // Child table name (e.g. "orderdetails")
    string $childKey,    // Column in the child table that references the parent
    array  $config = []  // Optional configuration (see next section)
);

Minimal Example

$grid = new ArtiGrid();
$grid->table('orders');

$grid->nestedTable("Order Details", "orderNumber", "orderdetails", "orderNumber");

echo $grid->render();
What happens When the user clicks Edit or View on an order row, a child grid for orderdetails is loaded automatically, filtered to show only the records that belong to that order.

Configuration Options

The fourth argument of nestedTable() accepts an array with options that apply exclusively to the child grid.

Available keys

$grid->nestedTable("Label", "parentKey", "childTable", "childKey", [

    // ── Actions ──────────────────────────────────────────────────
    "actions" => [
        "add"             => true,
        "edit"            => true,
        "delete"          => true,
        "view"            => true,
        "search"          => true,
        "refresh"         => true,
        "delete_multiple" => true,
        "edit_multiple"   => true,
    ],

    // ── Layout ───────────────────────────────────────────────────
    "buttonsArrange" => true,   // group action buttons into a dropdown
    "useModal"       => false,  // open edit/view forms inline (not in a modal)
    "perPage"        => 10,     // rows per page
    "template"       => "bootstrap5",

    // ── Columns & fields ─────────────────────────────────────────
    "columns"    => ['field1', 'field2'],   // visible table columns
    "formFields" => ['field1', 'field2'],   // fields shown in insert/edit form

    // ── Validation ───────────────────────────────────────────────
    "requiredFields"    => ['field1'],
    "allFieldsRequired" => false,

    // ── Conditional action buttons ────────────────────────────────
    "actionConditions" => [
        "edit"   => [["field" => "status", "operator" => "!=", "value" => "closed"]],
        "delete" => [["field" => "active", "operator" => "==", "value" => "1"]],
    ],

    // ── Sub-nesting (level 3+) ────────────────────────────────────
    "nestedGrids" => [
        [
            "label"      => "Sub-label",
            "parentKey"  => "someKey",
            "childTable" => "some_table",
            "childKey"   => "some_fk",
            "config"     => [ /* same options recursively */ ]
        ]
    ]
]);
Tip Keys you omit fall back to ArtiGrid defaults. You only need to specify what differs from the default behavior.

Multi-level Nesting

To nest a grid inside a nested grid (level 3, 4, …), use the nestedGrids key inside the parent's config array. This works recursively to any depth.

Example — Level 2 + Level 3

$grid = new ArtiGrid();
$grid->table('orders');

$grid->nestedTable("Order Details", "orderNumber", "orderdetails", "orderNumber", [

    "actions"     => ["add" => true, "edit" => true, "delete" => true, "view" => true],
    "columns"     => ['productCode', 'quantityOrdered', 'priceEach'],
    "formFields"  => ['productCode', 'quantityOrdered', 'priceEach'],
    "perPage"     => 5,

    // ── Level 3 nested inside orderdetails ──────────────────────
    "nestedGrids" => [
        [
            "label"      => "Products",
            "parentKey"  => "productCode",
            "childTable" => "products",
            "childKey"   => "productCode",
            "config"     => [
                "actions"    => ["add" => true, "edit" => true, "delete" => false, "view" => true],
                "columns"    => ['productCode', 'productName', 'productLine', 'quantityInStock'],
                "formFields" => ['productCode', 'productName', 'productLine', 'quantityInStock'],
                "perPage"    => 5,
            ]
        ]
    ]
]);

echo $grid->render();
How it works Each level is loaded lazily via AJAX only when the parent row is opened. ArtiGrid automatically applies the parent–child filter at every level.

Multiple Independent Children

You can attach more than one independent child grid to the same parent by calling nestedTable() multiple times. Each child is completely independent — it has its own actions, columns, and config.

Example — Two children on the same parent

$grid = new ArtiGrid();
$grid->table('orders');

// ── Child 1: orderdetails ─────────────────────────────────────────
$grid->nestedTable("Order Details", "orderNumber", "orderdetails", "orderNumber", [
    "actions"    => ["add" => true, "edit" => true, "delete" => true, "view" => true],
    "columns"    => ['productCode', 'quantityOrdered', 'priceEach'],
    "formFields" => ['productCode', 'quantityOrdered', 'priceEach'],
    "perPage"    => 5,
]);

// ── Child 2: customers (independent, same parent key) ─────────────
$grid->nestedTable("Customers", "orderNumber", "customers", "orderNumber", [
    "actions"    => ["add" => true, "edit" => true, "delete" => false, "view" => true],
    "columns"    => ['customerName', 'city', 'country'],
    "formFields" => ['customerName', 'city', 'country', 'orderNumber'],
    "perPage"    => 5,
]);

echo $grid->render();
Result Both grids appear when the user opens a parent row. Each one has its own CRUD operations, pagination, and search. They do not interact with each other.

Action Conditions in Nested Grids

You can control which action buttons (edit, delete, view) appear on each child row using actionConditions inside the config. The condition is evaluated against the row's own field values.

Example — Hide Edit for a specific productCode

$grid->nestedTable("Order Details", "orderNumber", "orderdetails", "orderNumber", [

    "actions" => ["add" => true, "edit" => true, "delete" => true, "view" => true],

    // Hide "Edit" when productCode == 'S18_2325'
    "actionConditions" => [
        "edit" => [
            [
                "field"    => "productCode",
                "operator" => "!=",
                "value"    => "S18_2325"
            ]
        ]
    ],

    "columns"    => ['productCode', 'quantityOrdered', 'priceEach'],
    "formFields" => ['productCode', 'quantityOrdered', 'priceEach'],
]);
Supported operators ==, !=, >, <, >=, <=, in, not in

Multiple conditions on different buttons

"actionConditions" => [
    "edit"   => [["field" => "status",  "operator" => "!=", "value" => "closed"]],
    "delete" => [["field" => "locked",  "operator" => "==", "value" => "0"]],
    "view"   => [["field" => "visible", "operator" => "==", "value" => "1"]],
],

Using render("edit") and render("view") with Nested Tables

When you call render("edit", $id) or render("view", $id), ArtiGrid renders the edit or view form for a specific record directly in the page — without showing the main CRUD table. Any nested tables configured on that grid are pre-rendered inline alongside the form.

$grid = new ArtiGrid();
$grid->table('orders');
$grid->validation_required("customerNumber");

$grid->nestedTable("Order Details", "orderNumber", "orderdetails", "orderNumber", [
    "actions"    => ["add" => true, "edit" => true, "delete" => true, "view" => true],
    "columns"    => ['productCode', 'quantityOrdered', 'priceEach'],
    "formFields" => ['productCode', 'quantityOrdered', 'priceEach'],
    "buttonsArrange" => true,
]);

// Render the edit form for order #10101, with the nested grid pre-loaded
echo $grid->render("edit", 10101);
What happens ArtiGrid renders the parent form fields and, immediately below them, each nested grid pre-filtered to show only the child records that belong to the record being edited.

Known limitation — dropdown buttons with multiple nested grids

When using buttonsArrange: true (dropdown action buttons) and you have two or more nested grids on the same page via render("edit"), the dropdown toggle may not work correctly. This happens because each nested grid initializes its own dropdown listener, and they cancel each other out.

The fix is a one-line change in artigrid.js: replace the per-instance flag with a global one in clickDropdown().

// artigrid.js — clickDropdown()

// ❌ Before (per-instance flag — breaks with multiple grids on the same page)
// if (this._dropdownBound) return;
// this._dropdownBound = true;

// ✅ After (global flag — only one document listener regardless of instance count)
if (ArtiGrid._globalDropdownBound) return;
ArtiGrid._globalDropdownBound = true;

Full Example

The example below demonstrates a 3-level nested structure: orders → orderdetails → products, plus a second independent child orders → customers.


$grid = new ArtiGrid();
$grid->table('orders');
$grid->template('bootstrap5');
$grid->unset("add", true);
$grid->perPage(5);
$grid->required(false);
$grid->validation_required("customerNumber");

// ── Level 2: orderdetails ─────────────────────────────────────────
$grid->nestedTable("Order Details", "orderNumber", "orderdetails", "orderNumber", [

    // ── Actions ──────────────────────────────────────────────────
    "actions" => [
        "add"             => true,
        "edit"            => true,
        "delete"          => true,
        "view"            => true,
        "search"          => true,
        "refresh"         => true,
        "delete_multiple" => true,
        "edit_multiple"   => true,
    ],

    // ── Layout ───────────────────────────────────────────────────
    "template"       => "bootstrap5",     // bootstrap5 | bootstrap4
    "useModal"       => false,            // open forms inline instead of modal
    "buttonsArrange" => true,             // group action buttons into a dropdown
    "actionsPosition"=> "right",          // left | right

    // ── Pagination ───────────────────────────────────────────────
    "perPage"        => 5,
    "perPageOptions" => [5, 10, 25, 50, "all"],

    // ── Columns & Fields ─────────────────────────────────────────
    "columns"        => ['productCode', 'quantityOrdered', 'priceEach'],
    "formFields"     => ['productCode', 'quantityOrdered', 'priceEach'],
    "hiddenColumns"  => [],               // columns to hide from the grid view
    "colRename"      => [
        'productCode'     => 'Code',
        'quantityOrdered' => 'Qty',
        'priceEach'       => 'Unit Price',
    ],

    // ── Sorting ──────────────────────────────────────────────────
    "sortColumn"     => "priceEach",
    "sortOrder"      => "desc",           // asc | desc

    // ── Field Types ──────────────────────────────────────────────
    "fieldTypes"     => [
        'priceEach' => 'number',          // text, number, date, datetime,
                                          // textarea, checkbox, hidden, etc.
    ],

    // ── Validation ───────────────────────────────────────────────
    "requiredFields"    => ['productCode', 'quantityOrdered', 'priceEach'],
    "allFieldsRequired" => false,

    // ── Comboboxes ───────────────────────────────────────────────
    "comboBoxes" => [
        // From a database table
        'productCode' => [
            'source'      => 'table',
            'table'       => 'products',
            'value'       => 'productCode',
            'label'       => 'productName',
            'dependsOn'   => null,        // parent field name (for dependent dropdowns)
            'dependsField'=> null,        // DB column to match
            'where'       => [],
        ],
        // From a static array
        'status' => [
            'source'  => 'array',
            'options' => ['active' => 'Active', 'inactive' => 'Inactive'],
        ],
    ],

    // ── Radio Buttons ────────────────────────────────────────────
    "radioFields" => [
        'priority' => [
            'low'    => 'Low',
            'medium' => 'Medium',
            'high'   => 'High',
        ],
    ],

    // ── Export ───────────────────────────────────────────────────
    "exportTypes" => ['excel', 'csv', 'pdf'],

    // ── Inline Edit ──────────────────────────────────────────────
    "inlineEditEnabled" => false,
    "inlineEditConfig"  => [
        'mode'       => 'cell',           // cell | row
        'saveOnBlur' => true,
        'highlight'  => true,
    ],

    // ── Form Templates ───────────────────────────────────────────
    "insertFormTemplate" => "",           // custom HTML string or file path
    "editFormTemplate"   => "",
    "viewFormTemplate"   => "",
    "crudTemplate"       => "",           // custom HTML template for the full grid

    // ── Custom Buttons ───────────────────────────────────────────
    "customButtons" => [
        [
            'class'      => 'btn btn-sm btn-info',
            'action'     => 'customAction',
            'label'      => '',
            'title'      => 'Preview',
            'conditions' => ['quantityOrdered', '>', 0],
            'url'        => '/preview/{productCode}',
            'target'     => '_blank',
            'attributes' => ['data-code' => '{productCode}'],
        ],
    ],

    // ── Duplicate Check ──────────────────────────────────────────
    "duplicateFields" => ['productCode'],

    // ── Conditional Cell Colors ──────────────────────────────────
    "columnColors" => [
        [
            'field'    => 'quantityOrdered',
            'operator' => '<',
            'value'    => 10,
            'color'    => 'background:red;color:white;',
        ],
    ],

    // ── Conditional Row Colors ───────────────────────────────────
    "rowColors" => [
        [
            'field'    => 'priceEach',
            'operator' => '>',
            'value'    => 100,
            'color'    => [
                'background'  => '#fff3cd',
                'color'       => '#000',
                'font-weight' => 'bold',
            ],
        ],
    ],

    // ── Action Button Conditions ─────────────────────────────────
    "actionConditions" => [
        "edit"   => [["field" => "productCode", "operator" => "!=", "value" => "S18_2325"]],
        "delete" => [["field" => "quantityOrdered", "operator" => ">", "value" => 0]],
        "view"   => [],
    ],

    // ── Joins ────────────────────────────────────────────────────
    "joins" => [
        [
            'localColumn'   => 'productCode',
            'joinTable'     => 'products',
            'foreignColumn' => 'productCode',
            'type'          => 'LEFT',    // INNER | LEFT | RIGHT
        ],
    ],

    // ── Subselects (dynamic aggregated columns) ──────────────────
    "subselects" => [
        'TotalLine' => 'SELECT SUM(quantityOrdered * priceEach)
                        FROM orderdetails
                        WHERE orderNumber = {orderNumber}',
    ],

    // ── Calculated Fields (arithmetic on existing columns) ───────
    "calculatedFields" => [
        'LineTotal' => '{quantityOrdered} * {priceEach}',
    ],

    // ── Sub-nesting (Level 3) ────────────────────────────────────
    "nestedGrids" => [
        [
            "label"      => "Products",
            "parentKey"  => "productCode",
            "childTable" => "products",
            "childKey"   => "productCode",
            "config"     => [
                "actions" => [
                    "add"             => true,
                    "edit"            => true,
                    "delete"          => false,
                    "view"            => true,
                    "delete_multiple" => true,
                    "edit_multiple"   => true,
                ],
                "columns"    => ['productCode', 'productName', 'productLine', 'quantityInStock'],
                "formFields" => ['productCode', 'productName', 'productLine', 'quantityInStock'],
                "perPage"    => 5,
            ],
        ],
    ],
]);

// ── Level 2: customers (independent second child of orders) ───────
$grid->nestedTable("Customers", "orderNumber", "customers", "orderNumber", [
    "actions" => [
        "add"             => true,
        "edit"            => true,
        "delete"          => false,
        "view"            => true,
        "search"          => true,
        "delete_multiple" => true,
        "edit_multiple"   => true,
    ],
    "columns"    => ['customerName', 'city', 'country'],
    "formFields" => ['customerName', 'city', 'country', 'orderNumber'],
    "perPage"    => 5,
    "template"   => "bootstrap5",
]);

echo $grid->render();
Summary of what this example demonstrates
  • A top-level CRUD grid for orders
  • A child grid for orderdetails with dropdown buttons and a conditional Edit rule
  • A grandchild grid for products nested inside orderdetails
  • A second independent child grid for customers attached to the same parent
  • All CRUD operations, search, pagination, and bulk actions working at every level

Overview SubSelect & Calculations

ArtiGrid allows you to enrich your grids with dynamic data using subselects and calculated fields. This enables you to pull aggregated values from related tables and perform inline computations directly inside the grid without additional backend processing.

What ArtiGrid handles automatically
  • Dynamic value injection using placeholders like {columnName}
  • Execution of subqueries per row
  • Automatic mapping of subselect results into grid columns
  • Support for arithmetic expressions between fields
  • Real-time computed columns without database schema changes
  • Combination of multiple subselect + calculated fields

SubSelect Concept

A subselect allows you to retrieve aggregated or related data from another table based on the current row context. The placeholder {customerNumber} is automatically replaced for each record.


  $grid->subselect('Paid',
      'SELECT SUM(amount) FROM payments WHERE customerNumber = {customerNumber}'
  );
    

In this example, ArtiGrid calculates the total payments made by each customer dynamically.

Calculated Fields

Calculated fields allow you to create derived values using existing columns or subselect results. These expressions are evaluated per row after all data is resolved.


  $grid->calculate('Profit', '{Paid} - {creditLimit}');
    

Here, Profit is computed by subtracting the customer's credit limit from the total paid amount.

Full Implementation Example

This is a complete working example combining both features:


  <?php
      $grid = new ArtiGrid();
      $grid->table('customers');

      $grid->crudCol([
          'customerNumber',
          'customerName',
          'city',
          'creditLimit',
          'Paid',
          'Profit'
      ]);

      $grid->subselect('Paid',
          'SELECT SUM(amount) FROM payments WHERE customerNumber = {customerNumber}'
      );

      $grid->calculate('Profit', '{Paid} - {creditLimit}');

      echo $grid->render();
  ?>