Add ViewHelpers for BackendModules to the Core

Add BackendActionController to Extbase

Currently all backend modules generate quite a lot of duplicated code for:

  • Initializing view (based on BackendTemplateView)
  • Initializing module template/pageRenderer…
  • Initializing uri-builder with current request
  • Building top level sub menu for actions / adding buttons to docheader…

As all of these are related to the view helpers, Helmut proposed to add ViewHelpers for this functionality to the core, so we can get rid of all the controller code. I’d additionally propose a step-by-step tutorial on how to write backend modules to show the functionality and make it widely known.

Impact

  • New ModuleTemplate ViewHelpers in the core

Pro

  • More convenience for developers
  • Consistent look and fool throughout the backend is much easier to achieve
  • Less code duplication
  • Simple. Since it is Fluid, even non PHP programmers could customize the docheader of modules
    Clean separation of controller and view. Enabling different output without touching controller code (needed e.g. in log module)
  • Compact. You can see at one glance which buttons are used, how the menu is and what requirejs modules are loaded (try that with plain module template api, no matter what additional abstraction is added)
    Flexible. Buttons, Menu, even the complete docheader can be easily different for different views (actions). Without that, controller code would need conditions on actions.

Con

  • Main module layout view helper needs to be added only once (this is now checked and an exception emitted otherwise) and all other VHs need to be children of that one (which is a strict requirement, which can’t easily be changed).

  • It is (easily) possible to add HTML outside the main VH (this would result in invalid HTML so the “benefit” in doing so is pretty low).

  • The view helpers added won’t be view helpers in the sense that they won’t directly render things in the place they are used (which is also true for other core VHs like the <f:layout> VH).

  • Usage of ModuleTemplate isn’t enforced (which is also a pro, see “Flexible” from above).

Remarks and notes

This topic was updated to reflect the discussion results and is therefore not longer about adding a new Controller, but about BackendViewHelpers.

Helmut already did an example implementation in https://review.typo3.org/#/c/51088/

Organizational

Topic Initiator: Susanne Moog
Topic Mentor: Susanne Moog

The amount of code added to the core would be comparatively small, the maintenance burden should be rather small as these parts of the code don’t change that often.
We should provide strong defaults and the pros definitely outweigh the cons.

An additional pro is that, we can change the defaults for backend modules and 99% of the modules would simple change with it. But we need to make sure to document on how to add owns own tweaks, such that the core can change the BackendActionController with minimal impact on classes extending it.

Also came across this while rewriting TemplaVoilà, but I’m not an extbase extension. And I didn’t had the time to make a “super” class. :wink:

Everything mentioned here, is semantically part of the view.
To get things done quickly for 7.6 release, these were added to the controller.

Now creating a abstract controller class nails down this state as API.

What I suggested back then as clean solution was:

  • creating a set of view helpers that allows constructing / configuring the module template / button bars (not having HTML there but really using the module template objects)
  • the module template stuff / page renderer, can then be added to fluid templates or layouts
  • to avoid duplication, some generic partials could be added that can be used across modules

Benefits of this proposal would be:

  • view related code is in the view
  • controller is freed from most ui related things
  • no inheritance based API (which has a lot of issues by its own)
  • all pros mentioned in this proposal also apply

Just my 2 cents

Ok, I’m trying to make the parts clearer (taken from the backend modules currently in the core and their usages), so we have a better overview:

DocHeader Components:

  • Adding submenu items (filling the dropdown box) - Functionality of MenuRegistry::class
  • Adding buttons - Functionality of ButtonBar::class
  • Adding Metainformation - Functionality of MetaInformation::class

PageRenderer parts:

  • Registering requirejs modules
  • Loading CSS

URIBuilder parts:

  • Initializing the URIBuilder with the current request.

While I agree that a lot of these things (everything but the URI Builder) are view related, I don’t really see them as view helpers, as the view helpers take their input data and render stuff (that’s why the interface has a render method with return type string) - from my POV it would be wrong to use them for api calls that do not in itself render things.

Maybe these helper functions should instead belong to the BackendTemplateView? That would be semantically correct - as we configure components that every backend view should have (like a docheader…).

What do we do with the URIBuilder?

Can you give a code example for what exactly you mean with “UriBuilder initialisation”?

Taken for example from beuser module.

    $uriBuilder = $this->objectManager->get(UriBuilder::class);
    $uriBuilder->setRequest($this->request);
    return $uriBuilder->reset()->uriFor($action, $parameters, $controller);

This code can perfectly be replaced with a <f:link.action> view helper usage and one argument more, why the complete ModuleTemplate stuff belongs to a Fluid template context :slight_smile:

I guess it’s done that way currently because it’s used for building menus and docheaders :wink:

But what do you think about the BackendTemplateView thing?

Exactly! And if docheaders and menus are built in the fluid template, then this weird initialization code is obsolete :slight_smile:

I introduced that as a mitigation to not having to do everything in controller code. however, this concept turned out to be too limited. That is why I’m no suggesting to finally do it right.

Ok, but then there is something missing, because for me those things are simply not viewhelpers, because they don’t render anything nor should they themselves be needed for rendering. For me we have a backend view with certain features (and things we define as style how a backend module should look like) like the docheader thing - this can be configured in your own modules…

A view helper would only make sense in my eyes if you could define positioning and content with it, something we explicitly do not want for the backend modules. So the concept of view helpers is IMHO totally wrong for this use case.

Here is a rough sketch what I mean: https://review.typo3.org/51088

This cleans up the log module code by a lot.

This is not only ModuleTemplate related, but is a sneak peak of what is possible by putting view code into the view. Suddenly all hacky initialization code is gone and nicely moved to a Fluid layout.

And if you think about it for a moment, the log module is a perfect example.
The (same) log list is rendered with two different layouts, one with doc header and one without.
This can now be represented with exactly these layouts, one only rendering the content, the other is rendering the module template layout around the content.

Please look at my example so that we make sure we are talking about the same thing.

The module template view helper I introduced is imho a perfectly vaild view helper and in fact returning a string.

I’m not sure if I understand this correctly. If you look at my example, the backend view is perfectly represented by the module layout view helper. I find it perfectly legitimate to add additional vhs that only work as children of that vh that configure the module template in more detail, like adding menus, buttons and so forth.

But it can be that I either not understand your arguments correctly or miss the point of them somehow.

Thanks for that. To me it is actually nicely illustrating what I want to avoid: Having to configure the module template layout for each and every backend module - which will be forgotten or simply left out by extension authors as it is manual work… I want to enforce the outer part of how a backend module should look like, and expecting everyone just to do it the right way does not work as the past shows. I mean, what is the difference (from the documentation / info in the class) between the be.container view helper and your module template?

To stay with your code example and the validity of having one viewhelper wrapping everything: From my understanding, that is not really a viewhelper, because it can’t be placed whereever I want (as it can’t be nested) and it does not render only parts but the whole page with everything in it. It would probably allow me to add it twice on the same page after each other - what wouldn’t make sense.

For the moduleTemplate view helper to get all the functionality would additionally mean quite a lot of arguments (docheader…) or subviewhelpers for subcomponents which the user then has to nest properly.

To make my goals clearer:

  • Convenience for the dev means to me he/she doesn’t have to think but just says “gimme a backend module” - ideally with some sort of autocomplete and without having to fiddle much.
  • Strict enforcing of backend templating - reducing ways of building backend modules to get a more similar and consistent look and feel we as coreteam can control.

I don’t see the benefit of the module template viewhelper in the light of my goals currently, that’s just as much work as it is now - only slightly more semantically correct.

With “configuring” you mean adding a single view helper to the template?[quote=“susanne.moog, post:15, topic:48”]
To stay with your code example and the validity of having one viewhelper wrapping everything: From my understanding, that is not really a viewhelper, because it can’t be placed whereever I want (as it can’t be nested)
[/quote]

That is true for many view helpers[quote=“susanne.moog, post:15, topic:48”]
It would probably allow me to add it twice on the same page after each other - what wouldn’t make sense.
[/quote]

Which is also true for many view helpers

I would not model this as arguments, but as additional view helpers that must be children of the top level view helper

OK. To get a simple backend module running with my approach a dev would need to:

  • add a single view helper as wrapper of the own template

How can a concrete workflow for devs then look like, that is easier than that?

From my understanding it boils down to:

  • add one line of code in the controller or
  • add one lone of code in the view (Fluid template)

Both can be easily explained, both can be forgotten.

But yes, they are slightly different and thus have different benefits and downsides.

For the log module I think I demonstrated, that having this code in the controller, makes the controller unnecessarily more complicated and add view logic where it would otherwise not be needed.

For modules that consistently need a module template for every view, it might be ok, to add that to the controller as it then binds the controller to one exact layout, which can be seen as benefit but also as downside.

I’ll try to explain how I see both approaches with code, maybe that makes it clearer.

With the viewhelper approach you’d have:

Controller:

$this->view->assign('menu', $menuConfigurationData)
$this->view->assign('docheaderButtons', $docheaderButtonData)
$this->view->assign('metaInformation', $metaInformationData)

Template (Dummy-Code):

   
      
      
      
      
   

For the template it doesn’t really matter in which order the viewhelpers are called, but they have to be nested inside module template.

With extending the view to have a backend template view we’d have:

Controller:

$this->view->configureMenu($menuConfigurationData)
$this->view->configureDocheaderButtons($docheaderButtonData)
$this->view->configureMetaInformation($metaInformationData)

I hope this makes it clearer why for me the first approach is more complicated and error prone than the second.

Ah, thanks, this helps!

Uhm, no. I did not make myself clear enough.

With the “the module template stuff” I mean the complete thing: frame/layout, docheader, button bar, menu, (… what else is in there)

With my approach the controller code looks like this:


No, this isn’t an error. controller code is indeed none. This is important especially for the use case log module, which is rendered once with and once without module menu. Doing that in the controller, would require a condition in the controller to switch how the log is shown. If you go for inheritance and let the controller inherit from a generic backend action controller, then you would be in trouble. As you need the same controller logic once with and once without the inherited module menu functionality, you would need to duplicate code or add another abstraction or hack (trait) to make it work.

In any case, you’ll need a second controller class to get a different view. And that is exactly what I would like to avoid.

If you look at my code again, you’ll notice that there is only one controller and two different Fluid layouts. The only thing that needs to be done in the controller (currently) is to select the layout, but this is only done for convenience and could be added to the configuration layer (settings) instead. Then controller code is identical for both cases, only a different layout is selected.

To illustrate what I mean with an additional example, I pushed a (not completed) change of the be user module: https://review.typo3.org/#/c/51091/

To make it visual here. The concept I propose makes it possible to replace controller code like this:

if ($this->actionMethodName == 'indexAction'
    || $this->actionMethodName == 'onlineAction'
    || $this->actionMethodName == 'compareAction') {
    $this->generateMenu();
    $this->view->getModuleTemplate()->setFlashMessageQueue($this->controllerContext->getFlashMessageQueue());
}
if ($view instanceof BackendTemplateView) {
    $view->getModuleTemplate()->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/Modal');
}

/**
 * Generates the action menu
 *
 * @return void
 */
protected function generateMenu()
{
    $menuItems = [
        'index' => [
            'controller' => 'BackendUser',
            'action' => 'index',
            'label' => $this->getLanguageService()->sL('LLL:EXT:beuser/Resources/Private/Language/locallang.xml:backendUsers')
        ],
        'pages' => [
            'controller' => 'BackendUserGroup',
            'action' => 'index',
            'label' => $this->getLanguageService()->sL('LLL:EXT:beuser/Resources/Private/Language/locallang.xml:backendUserGroupsMenu')
        ],
        'online' => [
            'controller' => 'BackendUser',
            'action' => 'online',
            'label' => $this->getLanguageService()->sL('LLL:EXT:beuser/Resources/Private/Language/locallang.xml:onlineUsers')
        ]
    ];
    $uriBuilder = $this->objectManager->get(UriBuilder::class);
    $uriBuilder->setRequest($this->request);

    $menu = $this->view->getModuleTemplate()->getDocHeaderComponent()->getMenuRegistry()->makeMenu();
    $menu->setIdentifier('BackendUserModuleMenu');

    foreach ($menuItems as  $menuItemConfig) {
        if ($this->request->getControllerName() === $menuItemConfig['controller']) {
            $isActive = $this->request->getControllerActionName() === $menuItemConfig['action'] ? true : false;
        } else {
            $isActive = false;
        }
        $menuItem = $menu->makeMenuItem()
            ->setTitle($menuItemConfig['label'])
            ->setHref($this->getHref($menuItemConfig['controller'], $menuItemConfig['action']))
            ->setActive($isActive);
        $menu->addMenuItem($menuItem);
    }

    $this->view->getModuleTemplate()->getDocHeaderComponent()->getMenuRegistry()->addMenu($menu);
}

With fluid code that looks like this:

<f:be.moduleLayout>
	<f:be.pageRenderer
		loadJQuery="true"
		jQueryNamespace="none"
		includeRequireJsModules="{
			0:'TYPO3/CMS/Backend/ClickMenu',
			1:'TYPO3/CMS/Backend/Modal'
		}"
	/>
	<f:be.moduleLayout.menu identifier="BackendUserModuleMenu">
		<f:be.moduleLayout.menuItem label="{f:translate(id: 'backendUsers')}" uri="{f:uri.action(controller: 'BackendUser', action: 'index')}"/>
		<f:be.moduleLayout.menuItem label="{f:translate(id: 'backendUserGroupsMenu')}" uri="{f:uri.action(controller: 'BackendUserGroup', action: 'index')}"/>
		<f:be.moduleLayout.menuItem label="{f:translate(id: 'onlineUsers')}" uri="{f:uri.action(controller: 'BackendUser', action: 'online')}"/>
	</f:be.moduleLayout.menu>
</f:be.moduleLayout>

I find it: compact, easy to read, write and understand, clean, semantically correct, covers all use cases in the core

I’m happy to be convinced that what you envision is cleaner and easier for devs. Maybe you can also draft some code how beuser module and log module could look like with that, so we can compare the approaches.

I can understand that putting this into view helpers might feel to much and that this might be a style not wanted for the core. I’m fine with that as well, just let me know. In that case I will publish a view helper collection as composer package that enables writing backend modules like I drafted here. Hm, maybe I publish such a package anyway to enable writing modules like that in TYPO3 7.6 as well

… Looking at the page renderer view helper, I might make it possible to configure it with nested view helpers as well instead of arguments …

What I’m missing most with that approach is a way to somehow enforce the usage of moduleTemplate for backend modules. I’m just afraid that people won’t use it :frowning: