Milk Admin

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-2 for sidebar and col-md-10 for 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 modules
  • milkadmin_local/Modules/Didattica/ - Container module for educational modules
  • milkadmin_local/Modules/Aule/ - Example child module with sidebar integration
Loading...