Část I · Začínáme
1. Instalace
Šest kroků. Tabulky v databázi si knihovna vytvoří sama.
-
Do
composer.jsonpřidat:"repositories": [ {"type": "vcs", "url": "git@gitlab.xart.cz:xart/devel/app-packages/utils.git"}, {"type": "vcs", "url": "git@gitlab.xart.cz:xart/devel/app-packages/console.git"}, {"type": "vcs", "url": "git@gitlab.xart.cz:xart/devel/app-packages/grid.git"} ] -
Spustit:
composer require xart/grid npm install xart-grid -
Do konfigurace přidat:
extensions: grid: Xart\Grid\Extension -
Do frontendu přidat:
import 'xart-grid'; import 'xart-grid/css'; -
Zaregistrovat potomka
Xart\Grid\User\UserProviders metodouloadUser(string $id): ?UserDTO. Bez něj neprojde kompilace kontejneru; nemáte-li uživatele, vracejtenull. -
Presentery s gridem dědit z
BasePresenter(xart/utils).
Poznámky
- Vyžaduje PHP 8.1+, Nette 3.2, MySQL, bootstrap 5.3, naja 3.3, ikony Tabler.
- Naja musí být tatáž instance jako ve zbytku aplikace (ve Vite přes
dedupe). UserDTO: povinnéida jméno; volitelnéimageUrl,color,online,profileUrl.- Nepřihlášenému drží nastavení session, profily nemá.
Ukázka
Jediný grid v dokumentaci, kde je vidět všechno naráz. Data se nikam nezapisují.
Objednávky
Co je na ukázce vidět
| Sloupce s presety | Číslo, částka, datum, ano/ne – část III |
| Enum sloupce | Stav a Platba z PHP enumu i s ikonou a barvou – kapitola 8 |
| Avatar | V datech je jen id; jméno a fotku dodá UserProvider – kapitola 10 |
| Systémový sloupec | Stáří se dopočítá v rows() – kapitola 11 |
| Obarvené řádky | Nezaplacené objednávky – kapitola 16 |
| Hromadná akce | Zatržené řádky sečte tlačítko v liště – kapitola 37 |
| Řádkový modál | Detail otevře běžnou Nette komponentu – kapitola 36.2 |
| Nabídka Zobrazení | Skrývání sloupců, délka stránky – kapitoly 12, 26 a 43 |
Zdrojový kód
php
app/Modules/AdminModule/Components/Docs/Start/TourGridControl/TourGridControl.php
<?php
declare(strict_types=1);
namespace App\Components\Docs\Start\TourGridControl;
use App\Components\Docs\Start\OrderDetailControl\OrderDetailControl;
use App\Enum\OrderPayment;
use App\Enum\OrderStatus;
use DateTimeImmutable;
use DateTimeInterface;
use Override;
use Xart\Grid\Button\ButtonManager;
use Xart\Grid\Color\ColorScheme;
use Xart\Grid\Column\ColumnManager;
use Xart\Grid\Column\Enum\GridEnum;
use Xart\Grid\Control\GridControl;
use Xart\Grid\DataOutput\Row;
use Xart\Grid\DataSource\DataSource;
use Xart\Grid\DataSource\SQL\SQLDataSource;
use Xart\Grid\Settings\SettingsManager;
use Xart\Utils\Presenter\BasePresenter;
/**
* Dokumentace, kapitola 1 – Instalace.
*
* Jediný grid v celé dokumentaci, který schválně ukazuje všechno najednou: sloupce s presety, enum
* s pilulkou, avatar, systémový sloupec, obarvené řádky, hromadnou akci i řádkový modál. Ostatní
* kapitoly mají vždy jen to jedno, o čem jsou.
*
* Nic tady nic nepřepisuje – hromadná akce jen spočítá, co má vybráno. Dokumentaci si prochází
* kdekdo a demo data mají zůstat, jak jsou.
*/
class TourGridControl extends GridControl
{
public function createDataSource(): DataSource
{
return new SQLDataSource(__DIR__ . '/TourGridControl.sql');
}
public function columns(ColumnManager $cm): void
{
$cm->add('number', 'Číslo');
// Avatar: v buňce je id zákazníka, jméno i fotku si grid vyzvedne u UserProvideru.
$cm->addAvatar('id_customer', 'Zákazník');
$cm->addEnum('status', 'Stav', OrderStatus::class)
->showInCell(GridEnum::Badge, GridEnum::Icon, GridEnum::Label)
->showInFilter(GridEnum::Label, GridEnum::Color);
$cm->addEnum('payment', 'Platba', OrderPayment::class)
->showInCell(GridEnum::Icon, GridEnum::Label);
$cm->addNumber('total_price', 'Celkem', 2, 'Kč');
$cm->addCheck('paid', 'Zaplaceno');
$cm->addDatetime('created_at', 'Vytvořeno', true);
// Systémový sloupec – hodnota se nebere z databáze, dopočítá se v rows().
$cm->addSystem('age', 'Stáří');
}
public function buttons(ButtonManager $bm): void
{
$bm->addBulkButton('Sečíst vybrané', $this->sumSelected(...))
->setIconClasses('ti ti-sum')
->setType('secondary');
}
public function rows(Row $row): void
{
// Systémový sloupec se plní tady – v datech pro něj nic není.
$created = $row['created_at']->defaultValue;
$row['age'] = $created instanceof DateTimeInterface
? (new DateTimeImmutable)->diff($created)->days . ' dní'
: '';
// Nezaplacená objednávka se podbarví, ať je v seznamu vidět.
if (!$row['paid']->defaultValue) {
$row->setColor(ColorScheme::orange()->subtle());
}
$row['total_price']->addClass('fw-bold');
$detail = $row->addModalButton('Detail', fn(Row $row): OrderDetailControl => $this->detail($row));
$detail->setPreset('detail');
$detail->modal
->setTitle('Detail objednávky')
->setSize('lg');
}
/**
* Obsluha hromadného tlačítka: dostane zatržené řádky a odpoví hláškou. Grid se po ní
* nepřenačítá – nic se nezměnilo, takže by to bylo jen zbytečné natažení dat.
*/
private function sumSelected(): void
{
$rows = $this->getSelectedRows();
$sum = 0.0;
foreach ($rows as $row) {
$sum += (float) $row['total_price']->defaultValue;
}
$presenter = $this->getPresenter();
assert($presenter instanceof BasePresenter);
$presenter->payloadSuccess(sprintf(
'Vybrané objednávky: %d, celkem %s Kč.',
count($rows),
number_format($sum, 2, ',', ' '),
));
$presenter->sendPayload();
}
/** Obsah modálu si bereme z presenteru – jen jako jeho komponenta umí odkazy i vlastní signály. */
private function detail(Row $row): OrderDetailControl
{
$detail = $this->getPresenter()->getComponent('orderDetail');
assert($detail instanceof OrderDetailControl);
return $detail->setOrderId((int) $row['id']->defaultValue);
}
#[Override]
public function settings(): SettingsManager
{
$sm = new SettingsManager;
$sm->setTitle('Objednávky');
return $sm;
}
public function render(): void
{
$this->template->render(__DIR__ . '/TourGridControl.latte');
}
}
sql
app/Modules/AdminModule/Components/Docs/Start/TourGridControl/TourGridControl.sql
SELECT
id,
number,
id_customer,
status,
payment,
total_price / 100 AS total_price,
(paid_at IS NOT NULL) AS paid,
created_at
FROM `order`
php
app/Helpers/DemoUserProvider.php
<?php
declare(strict_types=1);
namespace App\Helpers;
use Nette\Application\LinkGenerator;
use Nette\Database\Explorer;
use Xart\Grid\User\UserDTO;
use Xart\Grid\User\UserProvider;
final class DemoUserProvider extends UserProvider
{
public function __construct(
private Explorer $db,
private LinkGenerator $linkGenerator,
) {
}
protected function loadUser(string $id): ?UserDTO
{
$row = $this->db->query('SELECT * FROM customer WHERE id = ?', $id)->fetch();
if ($row === null) {
return null;
}
$numericId = (int) $id;
return new UserDTO(
$id,
(string) $row->first_name,
(string) $row->last_name,
// Demo: fotku má jen půlka lidí, ať je vidět i vykreslení iniciál.
$numericId % 2 === 0 ? 'https://i.pravatar.cc/256?u=' . urlencode($id) : null,
null,
$numericId % 4 === 0 ? $numericId % 3 === 0 : null,
$numericId % 3 === 0 ? $this->linkGenerator->link('Customer:Form', [$id]) : null,
);
}
}
sql
vendor/xart/grid/backend/State/grid_state.sql
CREATE TABLE IF NOT EXISTS `grid_state` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`id_user` INT NOT NULL,
`grid_id` VARCHAR(255) NOT NULL,
`name` VARCHAR(100) NOT NULL DEFAULT '',
`state` MEDIUMTEXT NOT NULL,
`share_hash` CHAR(32) NULL DEFAULT NULL,
`id_profile` INT UNSIGNED NULL DEFAULT NULL,
`created_at` DATETIME NOT NULL,
`updated_at` DATETIME NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `grid_state_user_grid_name` (`id_user`, `grid_id`, `name`),
UNIQUE KEY `grid_state_share_hash` (`share_hash`),
KEY `grid_state_profile_idx` (`id_profile`),
CONSTRAINT `grid_state_profile_fk` FOREIGN KEY (`id_profile`) REFERENCES `grid_state` (`id`) ON DELETE SET NULL
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci;