Menu
Qore offers a simple API to build your application menu.
Menu
Your GlobalsController
is responsible to provide application wide data and will be retrieved on page load.
This data contains resources, tenants, modules, plugins, the menu and more depending on your application.
You can build your menu via the Menu::make()
method:
use Qore\System\Menu\Menu;
class GlobalsController extends Controller
{
public function index(): JsonResponse
{
return response()->json([
// Here
'menu' => $this->menu(),
'tenants' => TenantResource::collection(auth()->user()->tenants),
'resources' => resources(),
'modules' => array_values(app(Modules::class)->all()),
'community' => array_values(app(Plugins::class)->all()),
]);
}
private function menu(): Menu
{
return Menu::make() // ->{...}
}
}
Resources in menu
You can add a menu item for a resource:
$group->addResourceMenuItem(resource(TicketResource::class));
You can also pass a Closure to modify the menu item:
$group->addResourceMenuItem(resource(TicketResource::class), function (MenuItem $item) {
$item->setLabel('All tickets');
});
API
MenuTab
Every MenuItem
, MenuGroup
and MenuSection
is contained within a MenuTab
:
->tab('home'/*icon*/, function (MenuTab $tab) {
// Optional description
$tab->setDescription(__('My description'));
// Optional permissions
$tab->setPermissions(['settings management']);
});
MenuItem
MenuItem can exist within a MenuTab
or MenuGroup
:
// With optional Closure
$tab->addMenuItem(__('Home'), '/home'/*url*/, []/*permissions*/, function (MenuItem $item) {
// Optional icon
$item->setIcon('home');
});
// Without Closure
$tab->addMenuItem(__('Settings'), '/settings/mailing', ['mail management']);
// Resource menu items
$tab->addResourceMenuItem(resource('users'));
MenuGroup
MenuGroup can be opened/closed and can contain multiple MenuItem
$tab->addMenuGroup(__('Intern'), function (MenuGroup $group) {
// Optional icon
$group->setIcon('group');
$group->addMenuItem(__('Settings'), '/settings/mailing', ['mail management']);
// ..
$group->addResourceMenuItem(resource('users'));
});
MenuSection
MenuSection is a simple divider item and can exist within a MenuTab
:
$tab->addMenuSection(__('Menu section'), ['user management']/*icon*/)
// With optional class overrides
$tab->addMenuSection('Menu section', [], 'text-bold text-primary bg-grey-4 q-pa-sm')
Active / inactive state
Whether a menu item should be active (selected) in the menu is handled by default based on the url
of the menu item.
However, this could sometimes lead into problems when you have multiple menu items that have or start with the same url
.
For this reason, you have 2 methods available on a menu item:
$group->addResourceMenuItem(resource(TicketResource::class), function (MenuItem $item) {
// When this is in the url, the menu item should get the selected state
$item->setActiveWhenIncludes(['resources/tickets'])
// When this is in the url, the menu item should not get the selected state
$item->setInactiveWhenIncludes(['bug']);
});
Multiple menu items for a resource
In some cases, you might need multiple menu items that will all go to the same Resource index page, but with applied filters.
For example, you might have a TicketResource
, and you want 3 menu items: All tickets
, Support tickets
and Bug tickets
.
You can do the following for this use case:
$group->addResourceMenuItem(resource(TicketResource::class), function (MenuItem $item) {
$item->setLabel('All tickets');
// When the following keywords are in the url, don't make this item active.
$item->setInactiveWhenIncludes(['support', 'bug']);
});
$group->addResourceFilterMenuItem(
(new ScopedMenuResource(resource(TicketResource::class), 'support'))
->setLabel('Support tickets')
->setSearchColumns([
'category' => ['support']
])
// Optionally sort by a column
->setSortColumn('name')
->setSortDirection('descending')
);
$group->addResourceFilterMenuItem(
(new ScopedMenuResource(resource(TicketResource::class), 'bug'))
->setLabel('Bug tickets')
->setSearchColumns([
'category' => ['bug']
])
);
The important part here is the tableScope
, which should be a unique key.
This tableScope will be added in the URL, and is used to determine which menu item is active, how to deal with IndexTabs
and more.
Combining with IndexTabs
When using the TicketResource
example again, we also might have 3 IndexTabs
for the table: All
, Internal
, External
.
For example:
public function indexTabs(): IndexTabCollection
{
return (new IndexTabCollection(
new AllTicketsIndexTab($this->indexQuery()),
new InternalTickets($this->indexQuery()),
new ExternalTickets($this->indexQuery())
));
}
class AllTicketsIndexTab extends IndexTab
{
protected Builder $baseQuery;
public function __construct(Builder $baseQuery)
{
$this->baseQuery = $baseQuery;
if ($this->tableScope()) {
$this->baseQuery->where('category', $this->tableScope());
}
}
public function query(): Builder
{
return $this->baseQuery;
}
}
class ExternalTickets extends AllTicketsIndexTab
{
// ..
public function query(): Builder
{
return $this->baseQuery->where('is_internal', false);
}
}
class InternalTickets extends AllTicketsIndexTab
{
// ..
public function query(): Builder
{
return $this->baseQuery->where('is_internal', true);
}
}
As you can see in the AllTicketsIndexTab
, the tableScope
will be utilized in order to take the menu item into account.
Every other tab extends this class, so this scope will be appended to all executed queries.
Custom indexQuery
In the examples above, the data that will be shown in the tables is either filtered using ->setSearchColumns
or using IndexTabs
.
If necessary, there is also a third option to filter data, which is to use the table_scope
from the request:
public function indexQuery()
{
return parent::indexQuery()
->when(request()->get('table_scope') === TicketType::BUG->value, function($query) {
$query->where('category', TicketType::BUG->value);
});
}
Full example
The following menu ships with skeleton
by default:
private function menu(): Menu
{
return Menu::make()
->tab('home', function (MenuTab $tab) {
// HOME
$tab->addMenuItem(__('Home'), '/home', [], function (MenuItem $item) {
$item->setIcon('home');
});
// INTERNAL
$tab->addMenuGroup(__('Intern'), function (MenuGroup $group) {
$group->setIcon('group');
$group->addResourceMenuItem(resource('users'));
$group->addMenuItem(__('Permissions'), '/permissions', [
'manage permissions',
'apply permissions'
]);
});
// CRM
if (module_is_active('qore/crm')) {
$tab->addMenuGroup(__('CRM'), function (MenuGroup $group) {
$group->setIcon('card_travel');
$group->addResourceMenuItem(resource(Organization::class));
$group->addResourceMenuItem(resource(Contact::class));
});
}
})
->tab('settings', function (MenuTab $tab) {
$tab->setDescription(__('Settings'));
$tab->setPermissions(['settings management', 'module management']);
// APPLICATION SETTINGS
$tab->addMenuGroup(__('Application'), function (MenuGroup $group) {
$group->setIcon('settings_applications');
$group->addResourceMenuItem(resource(Tenant::class));
$group->addMenuItem(__('Company preferences'), '/company-preferences', ['settings management']);
$group->addMenuItem(__('Authentication'), '/settings/authentication', ['settings management']);
$group->addMenuItem(__('Variables'), '/company-variables', ['settings management']);
$group->addMenuItem(__('Serial Numbers'), '/resources/serial_numbers', ['settings management']);
});
// MODULES
$tab->addMenuGroup(__('Modules'), function (MenuGroup $group) {
$group->setIcon('view_module');
$group->addMenuItem(__('List'), '/modules', config('qore.permissions.module.index'));
});
// PLUGINS
$tab->addMenuGroup(__('Plugins'), function (MenuGroup $group) {
$group->setIcon('extension');
$group->addMenuItem(__('List'), '/community', config('qore.permissions.plugin.index'));
});
// MAILING
$tab->addMenuGroup(__('Mailing'), function (MenuGroup $group) {
$group->setIcon('forward_to_inbox');
$group->addResourceMenuItem(resource(MailTemplate::class));
$group->addResourceMenuItem(resource(MailMessage::class));
$group->addResourceMenuItem(resource(MailFooter::class));
$group->addResourceMenuItem(resource(TwigTemplate::class));
$group->addMenuItem(__('Settings'), '/settings/mailing', ['mail management']);
});
})
->tab('o_account_tree', function (MenuTab $tab) {
$tab->setDescription(__('Additional'));
$tab->addMenuGroup('Demo', function (MenuGroup $group) {
$group->setIcon('travel_explore');
$group->addMenuItem(__('Query builder'), '/demo');
});
});
}