Page Tabs
Description
A Tabs Page is a controller that assembles Components into a Bootstrap tab interface. Each tab has an id, a visible label, and its own grid of components. Everything else — authorization, CSRF, GET/POST dispatch, <head> / <foot> collection — works identically to a Grid Page.
Create a Tabs Page by extending BaseTabsComponent and declaring a $tabs array.
Minimal example
use Fabiom\UDDemo\Components\BaseTabsComponent;
class ArticlesPage extends BaseTabsComponent {
const CONTROLLER_NAME = 'articles-page';
protected array $tabs = [
[
'id' => 'tab-list',
'label' => 'All Articles',
'panels' => [
['cssclass' => 'col-12', 'component' => ArticlesList::class],
],
],
[
'id' => 'tab-new',
'label' => 'New Article',
'panels' => [
['cssclass' => 'col-md-8 offset-md-2', 'component' => ArticleNew::class],
],
],
];
}The CONTROLLER_NAME constant registers the page with the router. A request to /articles-page.html renders the tab interface, with the first tab active by default.
The $tabs array
Each entry in $tabs is a tab descriptor with three required keys.
| Key | Type | Description |
|---|---|---|
id |
string | Unique HTML id for the tab panel. Must be valid as an HTML id attribute. |
label |
string | Text shown on the tab button. |
panels |
array | A list of layout nodes, identical to the $panels array of a Grid Page. |
Panel nodes inside a tab
The panels array inside each tab supports the same three node types as a Grid Page.
Component node
['cssclass' => 'col-12', 'component' => ArticlesList::class]Container node
['cssclass' => 'row', 'panels' => [
['cssclass' => 'col-md-6', 'component' => ArticlesList::class],
['cssclass' => 'col-md-6', 'component' => ArticleSummary::class],
]]Nested tabs node
Tabs can be nested — a tab panel can itself contain a tabs node.
['cssclass' => 'col-12', 'tabs' => [
['id' => 'tab-published', 'label' => 'Published', 'panels' => [
['cssclass' => 'col-12', 'component' => PublishedArticlesList::class],
]],
['id' => 'tab-drafts', 'label' => 'Drafts', 'panels' => [
['cssclass' => 'col-12', 'component' => DraftArticlesList::class],
]],
]]POST dispatch
POST handling works the same as in a Grid Page. Each component form must include a hidden _component field so the page can route the submission to the correct component:
<form method="post">
<input type="hidden" name="_component" value="<?= self::class ?>">
<!-- form fields -->
<button type="submit" class="btn btn-primary">Save</button>
</form>After a successful POST the page redirects back to the referring page by default. Override onPostSuccess() to change this:
protected function onPostSuccess(): void {
$this->redirectToPage(url_for('articles-page'));
}Authorization
Override check_authorization_get_request() and check_authorization_post_request() to guard the whole page.
protected function check_authorization_get_request(): bool {
return isset($_SESSION['group']) && in_array($_SESSION['group'], ['editor', 'admin']);
}
protected function check_authorization_post_request(): bool {
return isset($_SESSION['group']) && in_array($_SESSION['group'], ['editor', 'admin']);
}Individual components inside any tab can still declare their own check_authorization_resource_request() to hide themselves for certain users.
Complete example
use Fabiom\UDDemo\Components\BaseTabsComponent;
use Fabiom\UDDemo\Chapters\Articles\Components\ArticlesList;
use Fabiom\UDDemo\Chapters\Articles\Components\ArticleNew;
use Fabiom\UDDemo\Chapters\Articles\Components\ArticleSearch;
use Fabiom\UDDemo\Chapters\Articles\Components\ArticleStats;
class ArticlesPage extends BaseTabsComponent {
const CONTROLLER_NAME = 'articles-page';
protected array $tabs = [
[
'id' => 'tab-list',
'label' => 'Articles',
'panels' => [
['cssclass' => 'col-12 mb-3', 'component' => ArticleSearch::class],
['cssclass' => 'col-12', 'component' => ArticlesList::class],
],
],
[
'id' => 'tab-new',
'label' => 'New Article',
'panels' => [
['cssclass' => 'row', 'panels' => [
['cssclass' => 'col-md-8 offset-md-2', 'component' => ArticleNew::class],
]],
],
],
[
'id' => 'tab-stats',
'label' => 'Statistics',
'panels' => [
['cssclass' => 'col-12', 'component' => ArticleStats::class],
],
],
];
protected function check_authorization_get_request(): bool {
return isset($_SESSION['group']);
}
protected function check_authorization_post_request(): bool {
return isset($_SESSION['group']);
}
}This page renders three tabs. The first tab shows a search bar above the articles list. The second tab shows a centred new-article form. The third tab shows statistics. POST submissions from ArticleNew are routed automatically via the _component field.