====== Back Office Menu Entries ====== {{ :writing_modules:back_office_new_tab.png?direct&300|The new Tab in back office.}} Best practice for modules adding services in back office is to not offer these services on their configuration page, but to add an entry to one of the back office menus. This strategy may well become mandatory one day. Going this route isn't particularly difficult. One big advantage: one doesn't have to go through ''getContent()'' in the module main class and build up everything from there. One can use 'real' controllers, just like all the controllers and classes coming with thirty bees core. Code snippets here were written while starting with the [[https://github.com/thirtybees/gitupdater|Git Updater module]]. You can find complete files with similar code (names adjusted for that module) in the early commits there. ===== Install the Tab / Menu Item ===== Note: while entries in a menu are usually called //menu item// or similar, thirty bees source code calls them //Tabs//. Base class ''Tabs'', controller ''AdminTabsController'', and so on. ==== Define a Controller ==== As written above, Tabs run a controller. First step to define the name of this controller. Using a class constant is a good idea. class MyModule extends Module { const MAIN_CONTROLLER = 'AdminMy'; // ... } Note that the controller name is defined to just //AdminMy//, not //AdminMyController//. The //Controller// part gets added by thirty bees core automatically. Prepending admin controller names with //Admin// is the usual convention. ==== Installing the Tab ==== Installation code obviously goes into ''install()'' of the module main class. Here is how it can be done: public function install() { $success = parent::install(); if ($success) { try { $tab = new Tab(); $tab->module = $this->name; $tab->class_name = static::MAIN_CONTROLLER; $tab->id_parent = Tab::getIdFromClassName('AdminPriceRule'); # As of thirty bees v1.0.8, having a name is no longer mandatory. $langs = Language::getLanguages(); foreach ($langs as $lang) { $tab->name[$lang['id_lang']] = $this->l('Updater'); } $success = $tab->save(); } catch (Exception $e) { $success = false; } } return $success; } == $tab->module == That's the module which created the Tab. Always ''$this->name''. == $tab->class_name == This defines the controller class for the Tab. Without the trailing //Controller//, see previous section. == $tab->id_parent == Tab ID of the parent Tab. If ''NULL'', the new Tab gets appended to the menu top level. Else it gets appended to the submenu of this parent. There are multiple ways to find the parent ID. One is to look up the HTML ID of the existing menu entry and stripping //maintab-// from it. Hardcoding this ID isn't a good idea, it might change in the future. For the purpose of this HowTo, //AdminPriceRule// was choosen, because it has the shortest submenu, giving the most compact screenshots. == $tab->name == This is the user visible name of the Tab. As thirty bees supports multiple languages, one has to set each language. == $tab->save() == This saves the Tab into the database. Should be repeated after each change of the Tab. And yes, Tabs defined by modules are visible everywhere, not just from code inside the defining module. ==== Uninstalling the Tab ==== Being nice code writers, we uninstall Tabs on module uninstallation, of course. Nobody wants to be responsible for useless clutter in the database. Nice enough, that's pretty simple: public function uninstall() { $success = true; $tabs = Tab::getCollectionFromModule($this->name); foreach ($tabs as $tab) { $success = $success && $tab->delete(); } return $success && parent::uninstall(); } This uninstalls all module related Tabs, in case the module installed more than one. ==== Enabling and Disabling ==== One might wonder what happens with module defined Tabs when the module gets disabled, but not uninstalled. Nice feature of such Tabs is, they automatically get disabled together with disabling the module. No need for the module developer to take care of this. A disabled Tab is still recorded in the database, nevertheless it disappears from the back office menu. ==== Results ==== Lo and behold, with the above code additions and installing (or resetting) the module, a submenu item to menu item //Price Rules// appears in back office: {{ :writing_modules:back_office_new_tab.png?direct |New Tab in back office.}} One can disable or uninstall the module, the menu item disappears. Re-enabling the module gets the menu item back. This is how it should work. Using a database browser, one can also see see a record appearing in table ''tb_tabs'', and one record for each language in table ''tb_tabs_lang''. These records stay with the module disabled and disappear on module uninstallation. However, when clicking this menu item, one sees this: {{ :writing_modules:back_office_controller_not_found.png?direct }} No heading for the page, no controller found. This will get corrected in the next section. ===== Creating a Controller ===== This section shows how to get a bare minimum controller into work. Just to show how the mechanism of accepting controllers works. ==== A Bare Minimum Controller ==== As usual in this wiki, with all the documentation comments stripped: bootstrap = true; parent::__construct(); } public function initContent() { $this->page_header_toolbar_title = $this->l('My Controller'); parent::initContent(); } } == Class Name == Base name of the class should match ''class_name'' of the Tab. Which got defined on Tab creation, in ''install()'' in the module main class. Additionally, a //Controller// gets appended. == Parent Class == Subclassing ''ModuleAdminController'' instead of ''AdminController''. Former is a thin wrapper around the latter, loading the module as needed and bending template search paths into the module directory. == $this->bootstrap == This is a flag to declare compatibility with Bootstrap, the CSS layout system. Should always be set to ''true''. == $this->page_header_toolbar_title == This is the title of the controller page. Translated as needed, of course. See also [[Back Office Display Decoration#Adding a Title|Adding a Title]] (page Back Office Display Decoration). ==== Controller Code File Placement ==== thirty bees' dispatcher prefers certain locations where code files should be placed. Following these wishes, controllers get loaded without additional efforts. Search strategy for back office controllers can be found in ''Dispatcher->dispatch()'', case ''static::FC_ADMIN''. For controllers inside modules it looks like this: * All back office controllers go into ''controllers/admin/'' inside the module directory. * Name of the PHP file should match the name of the class inside. Plus the //.php// suffix, of course. * File names without //Controller// appended get accepted as well. * Only for retrocompatibility, code files in the module root get found as well. Don't use this. Accordingly, the file shown above goes into ''controllers/admin/AdminMyController.php''. After placing the file there, delete ''cache/class_index.php''. It'll get rebuilt on the next page request. Then including the new controller. ==== Results ==== {{ :writing_modules:back_office_controller_found.png?direct |Back office controller found!}} Looks promising, doesn't it? ===== Actual Content Display ===== Strategies to display actual content with the controller went into dedicated pages. For a list, see section [[Writing Controllers#Back Office Display|Back Office Display]] (page Writing Controllers).