Sidebar Modules
Create container modules with shared sidebar navigation to group related modules together.
Overview
Sidebar modules allow you to create a parent container module that provides a shared navigation menu for multiple child modules. This pattern is useful when you have several related modules that should be grouped together under a common interface.
Use Cases:
- Settings modules (Aule, Causali, Strumenti, etc.)
- Educational modules (Corsi, Lezioni, Frequenze, Eventi)
- Any group of related administrative modules
Implementation Steps
1. Create the Container Module
First, create a container module that will serve as the main menu entry point:
<?php
namespace Local\Modules\Settings;
use App\Abstracts\AbstractModule;
use App\Attributes\{RequestAction, AccessLevel};
use App\{Response, Theme};
class SettingsModule extends AbstractModule
{
protected function configure($rule): void {
$rule->page('settings')
->title('Settings')
->menu('Settings', '', 'bi bi-gear', 10)
->access('registered')
->version(251227);
}
#[RequestAction('home')]
public function settingsHome() {
// Render the main settings page with sidebar
$contentHtml = '<h1>Settings</h1><p>Select a section from the sidebar.</p>';
$this->renderWithSidebar($contentHtml);
}
private function renderWithSidebar($contentHtml) {
ob_start();
require (\App\Get::uriPath(__DIR__."/Views/menu.php"));
$sidebar = ob_get_clean();
$combinedContent = '
<div class="container-fluid px-0">
<div class="row">
<div class="col-md-2">
' . $sidebar . '
</div>
<div class="col-md-10">
<div class="bg-white p-4">
' . $contentHtml . '
</div>
</div>
</div>
</div>';
Theme::set('content', $combinedContent);
Response::themePage('default');
}
}
2. Create the Sidebar Menu
Create a hard-coded menu file at Views/menu.php within your container module:
<?php
namespace Local\Modules\Settings\Views;
use Builders\LinksBuilder;
!defined('MILK_DIR') && die();
$currentPage = $_REQUEST['page'] ?? '';
$menus = [
['label' => 'Aule', 'icon' => 'bi bi-door-open', 'url' => '?page=aule', 'active' => ($currentPage === 'aule')],
['label' => 'Causali', 'icon' => 'bi bi-list-check', 'url' => '?page=causali', 'active' => ($currentPage === 'causali')],
['label' => 'Strumenti', 'icon' => 'bi bi-music-note-list', 'url' => '?page=strumenti', 'active' => ($currentPage === 'strumenti')]
];
$linksBuilder = LinksBuilder::create()->addMany($menus);
echo $linksBuilder->render('sidebar');
3. Create a Helper Class
Create a helper class to make the sidebar accessible to child modules:
<?php
namespace Local\Modules\Settings;
class SettingsHelper
{
public static function getSidebar(): string
{
ob_start();
require(\App\Get::uriPath(__DIR__ . "/Views/menu.php"));
return ob_get_clean();
}
public static function wrapWithSidebar(string $content): string
{
$sidebar = self::getSidebar();
return '
<div class="container-fluid px-0">
<div class="row">
<div class="col-md-2">' . $sidebar . '</div>
<div class="col-md-10">' . $content . '</div>
</div>
</div>';
}
}
4. Update Child Modules
Update each child module to use the selectMenu() method:
<?php
namespace Local\Modules\Aule;
use App\Abstracts\AbstractModule;
use App\Attributes\{RequestAction, AccessLevel};
use Builders\{TableBuilder, FormBuilder};
use App\Response;
class AuleModule extends AbstractModule
{
protected function configure($rule): void {
$rule->page('aule')
->title('Aule')
// Comment out the menu entry - it will be in Settings sidebar
//->menu('Aule', '', 'bi bi-door-open', 10)
->access('registered')
->selectMenu('Settings') // Link to Settings sidebar
->version(251222);
}
#[RequestAction('home')]
public function auleList() {
$tableBuilder = TableBuilder::create($this->model, 'idTableAule')
->activeFetch()
->field('AULA')->link('?page='.$this->page.'&action=edit&id=%ID%')
->setDefaultActions();
$response = array_merge($this->getCommonData(), $tableBuilder->getResponse());
$response['title_btns'] = [
['label' => 'Aggiungi Nuova', 'link' => '?page='.$this->page.'&action=edit', 'color' => 'primary', 'fetch' => 'get']
];
// Use custom view with sidebar
Response::render(__DIR__ . '/Views/list_page.php', $response);
}
}
5. Create Custom View for Child Modules
Create a custom Views/list_page.php for each child module that integrates the sidebar:
<?php
namespace Local\Modules\Aule\Views;
use Builders\TitleBuilder;
use Local\Modules\Settings\SettingsHelper;
!defined('MILK_DIR') && die(); // Avoid direct access
/**
* Variables:
* $title - string
* $title_btns - array [label, link, color][]
* $description - string
* $html - string
*/
// Get Settings sidebar
$sidebar = SettingsHelper::getSidebar();
?>
<div class="container-fluid px-0">
<div class="row">
<div class="col-md-2">
<?= $sidebar ?>
</div>
<div class="col-md-10">
<div class="card">
<?php if (isset($title)) : ?>
<div class="card-header">
<?php
$title_builder = TitleBuilder::create($title);
if (isset($title_btns) && is_array($title_btns)) {
foreach ($title_btns as $btn) {
$title_builder->addButton($btn['label'], $btn['link'], $btn['color'] ?? 'primary', $btn['class'] ?? '', $btn['fetch'] ?? null);
}
}
echo (isset($search_html)) ? $title_builder->addRightContent($search_html) : $title_builder->addSearch($table_id, 'Search...', 'Search');
?>
</div>
<?php endif; ?>
<div class="card-body">
<?php if (isset($description)) { ?>
<p class="text-body-secondary mb-3"><?php _pt($description); ?></p>
<?php } ?>
<?php _ph($html); ?>
</div>
</div>
</div>
</div>
</div>
How selectMenu() Works
The selectMenu() method is a chainable method in ModuleRuleBuilder that automatically sets the active sidebar when the module is loaded.
Implementation in AbstractModule:
// In AbstractModule constructor
if ((isset($_REQUEST['page']) && $_REQUEST['page'] == $this->page)) {
$this->loadLang();
Hooks::set('after_modules_loaded', [$this, 'init'], 10);
Hooks::set('after_modules_loaded', [$this, 'setStylesAndScripts'], 15);
Hooks::set('after_modules_loaded', [$this, 'afterInit'], 11);
// Set selected menu if configured
$selected_menu = $this->rule_builder->getSelectedMenu();
if ($selected_menu !== null) {
Theme::set('sidebar.selected', $selected_menu);
}
}
This ensures that Theme::set('sidebar.selected', 'Settings') is only applied when you're actually viewing that module's page.
Best Practices
- Hard-coded menus: Use static menu arrays instead of dynamic page-based routing for better control.
- Narrow sidebar: Use
col-md-2for sidebar andcol-md-10for content to maintain a clean layout. - Consistent structure: Keep the same file structure across all child modules for maintainability.
- Comment out menu entries: When moving modules under a sidebar container, comment out their original
->menu()entries instead of deleting them. - Helper classes: Use helper classes to centralize sidebar generation logic.
Complete Example
A complete working example can be found in:
milkadmin_local/Modules/Settings/- Container module for configuration modulesmilkadmin_local/Modules/Didattica/- Container module for educational modulesmilkadmin_local/Modules/Aule/- Example child module with sidebar integration