Folders & Navigation
Folders appear in the sidebar and filter the list view. Every model gets default folders ("All", "Trash", "Checked Out"), but you can add custom folders with any filter logic.
The simplest way to add folders is in meta():
| use System\CurrentUser;
use System\Data\Database;
public static function meta(): ?ModelMeta
{
return (new ModelMeta('Tickets', 'ticket'))
->withSingular('Ticket')
->enable()
// Static folder with a record finder closure
->folder(
'tickets.own', // unique key
'Own', // display label
'user', // Phosphor icon
'default', // CSS class
'', // parent folder key (empty = root)
fn(Database $db) => $db->find(Ticket::class, [
'owner_id' => CurrentUser::id()
])
)
->folder(
'tickets.others',
'Others',
'users',
'default',
'',
fn(Database $db) => $db->find(Ticket::class, [
'owner_id' => ':NOT(' . CurrentUser::id() . ')'
])
);
}
|
Folder Parameters
| ->folder(
string $key, // unique identifier (used in ?folder= URL)
string $label, // display label (auto-wrapped in Lang::t())
string $icon, // Phosphor icon name
string $cssClass, // CSS class ('default', 'danger', etc.)
string $parent, // parent folder key for nesting ('' = root)
callable $finder // closure that returns records for this folder
)
|
Dynamic Folders with withFolderBuilder()
For folders that need runtime logic (e.g. one folder per record type), use withFolderBuilder():
| public static function meta(): ?ModelMeta
{
return (new ModelMeta('Records', 'folder'))
->withSingular('Record')
->enable()
->withFolderBuilder(function (\System\AppPage $page, string $className) {
// Folder: Modified today
$page->folder('today', 'Modified today', 'calendar', 'default', 'all',
function (Database $db, string $class) {
$today = (new \DateTime('midnight'))->format('Y-m-d');
$tomorrow = (new \DateTime('tomorrow'))->format('Y-m-d');
return $db->findRaw($class,
'modified_on BETWEEN ? AND ?',
[$today, $tomorrow]
);
}
);
// Folder: Modified this month
$page->folder('month', 'Modified this month', 'calendar', 'default', 'all',
function (Database $db, string $class) {
$start = (new \DateTime('first day of this month'))->format('Y-m-d');
$end = (new \DateTime('first day of next month'))->format('Y-m-d');
return $db->findRaw($class,
'modified_on BETWEEN ? AND ?',
[$start, $end]
);
}
);
});
}
|
Dynamic Folders Per Record
Create one folder per lookup record (e.g. one folder per record type):
| ->withFolderBuilder(function (\System\AppPage $page, string $className) {
// Create a parent folder
$page->folder('by_type', 'By Type', 'folder', 'default', 'all',
fn() => null // parent folder doesn't show records itself
);
// One child folder per RecordType
$types = Database::instance()->find(RecordType::class, []);
foreach ($types as $type) {
$typeId = $type->getId();
$page->folder(
"type_{$typeId}",
$type->name,
'tag',
'default',
'by_type', // nested under parent
fn(Database $db, string $class) => $db->find($class, [
'type_id' => $typeId
])
);
}
})
|
Folder Labels with i18n
Folder labels passed to ->folder() are automatically wrapped in Lang::t(). No additional wrapping needed.
For dynamic labels, use Lang::t() explicitly:
| $page->folder(
"type_{$id}",
Lang::t("Record.recordtype.{$type->name}"),
'tag'
);
|