Skip to content

Instantly share code, notes, and snippets.

@bwaidelich
Last active April 13, 2017 12:28
Show Gist options
  • Save bwaidelich/f3a3d6373acb558a7aa6 to your computer and use it in GitHub Desktop.
Save bwaidelich/f3a3d6373acb558a7aa6 to your computer and use it in GitHub Desktop.
A DTO that can be used to create simple menus in a TYPO3 Flow application
<ul class="nav">
<f:for each="{menu.menuItems}" as="menuItem">
<f:render section="menuItem" arguments="{menuItem: menuItem}" />
</f:for>
</ul>
<f:section name="menuItem">
<f:if condition="{menuItem.header}">
<f:then>
<li class="nav-header">{menuItem.label}</li>
</f:then>
<f:else>
<f:if condition="{menuItem.separator}">
<f:then>
<li class="divider"></li>
</f:then>
<f:else>
<li class="{f:if(condition: menuItem.active, then: 'active')}">
<a href="{f:uri.action(action: menuItem.targetActionName, controller: menuItem.targetControllerName, package: menuItem.targetPackageKey)}">
<f:if condition="{menuItem.icon}">
<i class="fa fa-fw fa-{menuItem.icon}"></i>
</f:if>
{menuItem.label}
<f:if condition="{menuItem.badge}">
<span class="pull-right badge badge-info badge-hidden">{menuItem.badge}</span>
</f:if>
</a>
<f:if condition="{menuItem.subMenuItems}">
<ul>
<f:for each="{menuItem.subMenuItems}" as="subMenuItem">
<f:render section="menuItem" arguments="{menuItem: subMenuItem}" />
</f:for>
</ul>
</f:if>
</li>
</f:else>
</f:if>
</f:else>
</f:if>
</f:section>
<?php
namespace Acme\SomePackage\Domain\Dto;
use TYPO3\Flow\Annotations as Flow;
use TYPO3\Flow\Mvc\ActionRequest;
/**
* Simple Menu that can be rendered with Fluid
*/
class Menu {
/**
* @var ActionRequest
*/
protected $actionRequest;
/**
* @var MenuItem[]
*/
protected $menuItems = [];
/**
* @param ActionRequest $actionRequest
* @param array $configuration in the format ['label' => 'A', 'targetPackageKey' => 'Some.Package', ..., 'subMenuItems' => ['label' => A.1', ...], ...]
*/
public function __construct(ActionRequest $actionRequest, array $configuration = NULL) {
$this->actionRequest = $actionRequest;
if ($configuration !== NULL && isset($configuration['menuItems'])) {
$this->menuItems = $this->createMenuItems($configuration['menuItems']);
$this->setActiveMenuItems();
}
}
/**
* Recursively creates menu and sub menu items corresponding to the given $configuration
*
* @param array $menuItemsConfiguration
* @return MenuItem[]
*/
protected function createMenuItems(array $menuItemsConfiguration) {
$menuItems = [];
foreach ($menuItemsConfiguration as $menuItemConfiguration) {
$label = isset($menuItemConfiguration['label']) ? $menuItemConfiguration['label'] : NULL;
$targetPackageKey = isset($menuItemConfiguration['package']) ? $menuItemConfiguration['package'] : NULL;
$targetControllerName = isset($menuItemConfiguration['controller']) ? $menuItemConfiguration['controller'] : NULL;
$targetActionName = isset($menuItemConfiguration['action']) ? $menuItemConfiguration['action'] : 'index';
$targetActionArguments = isset($menuItemConfiguration['arguments']) ? $menuItemConfiguration['arguments'] : [];
$icon = isset($menuItemConfiguration['icon']) ? $menuItemConfiguration['icon'] : NULL;
$badge = isset($menuItemConfiguration['badge']) ? $menuItemConfiguration['badge'] : NULL;
$menuItem = new MenuItem($label, $targetPackageKey, $targetControllerName, $targetActionName, $targetActionArguments, $icon, $badge);
if (isset($menuItemConfiguration['menuItems'])) {
$menuItem->setSubMenuItems($this->createMenuItems($menuItemConfiguration['menuItems']));
}
$menuItems[] = $menuItem;
}
return $menuItems;
}
/**
* @return MenuItem[]
*/
public function getMenuItems() {
return $this->menuItems;
}
/**
* Adds a menu item to the end of this menu
*
* @param MenuItem $menuItem
* @return Menu the current instance to enable method chaining
*/
public function addMenuItem(MenuItem $menuItem) {
$this->menuItems[] = $menuItem;
return $this;
}
/**
* Recursively activates all menu items that match the given actionRequest
*
* @return void
*/
public function setActiveMenuItems() {
foreach ($this->menuItems as $menuItem) {
$menuItem->activateForRequest($this->actionRequest);
}
}
}
<?php
namespace Acme\SomePackage\Domain\Dto;
use TYPO3\Flow\Annotations as Flow;
use TYPO3\Flow\Persistence\PersistenceManagerInterface;
use TYPO3\Flow\Security\Account;
use TYPO3\Flow\Security\Context;
use TYPO3\Flow\Utility\Arrays;
use TYPO3\Flow\Mvc\ActionRequest;
/**
* Simple Menu that can be rendered with Fluid
*/
class MenuItem {
/**
* @Flow\Inject
* @var PersistenceManagerInterface
*/
protected $persistenceManager;
/**
* @var string
*/
protected $label = '';
/**
* @var boolean
*/
protected $active = FALSE;
/**
* @var string
*/
protected $targetPackageKey;
/**
* @var string
*/
protected $targetControllerName;
/**
* @var string
*/
protected $targetActionName;
/**
* @var array
*/
protected $targetActionArguments = [];
/**
* @var string
*/
protected $icon;
/**
* @var string
*/
protected $badge;
/**
* @var MenuItem[]
*/
protected $subMenuItems = [];
/**
* @param string $label label of the menu item - if omitted, the resulting menu item is considered a separator
* @param string $targetPackageKey key of the target package - if omitted, the resulting menu item is considered a menu header
* @param string $targetControllerName name of the target controller
* @param string $targetActionName the target action name
* @param array $targetActionArguments the target action arguments
* @param string $icon an optional icon identifier (to be used from the fluid template)
* @param string $badge an optional badge text (to be used from the fluid template)
*/
public function __construct($label, $targetPackageKey = NULL, $targetControllerName = NULL, $targetActionName = NULL, array $targetActionArguments = [], $icon = NULL, $badge = NULL) {
$this->label = $label;
$this->targetPackageKey = $targetPackageKey;
$this->targetControllerName = $targetControllerName;
$this->targetActionName = $targetActionName;
$this->targetActionArguments = $targetActionArguments;
$this->icon = $icon;
$this->badge = $badge;
}
/**
* Sets the menu item (and it's sub items) active if it matches the given $actionRequest:
*
* If the menu item has sub menu items, matching package and controller is enough to mark the item active.
* Otherwise the action name has to match as well (unless that is not defined).
*
* @param ActionRequest $actionRequest
* @return void
*/
public function activateForRequest(ActionRequest $actionRequest) {
if ($this->isHeader() || $this->isSeparator()) {
return;
}
if (!$this->matchesRequest($actionRequest)) {
return;
}
$this->active = TRUE;
foreach ($this->subMenuItems as $subMenuItem) {
$subMenuItem->activateForRequest($actionRequest);
}
}
/**
* returns TRUE if the given $request points to the controller/action specified in this menu item
*
* @param ActionRequest $actionRequest
* @return boolean TRUE if this menuItem is active for the given $request, otherwise FALSE
*/
protected function matchesRequest(ActionRequest $actionRequest) {
if ($this->hasSubMenuItems() || $this->targetActionName === NULL) {
return $this->targetPackageKey === $actionRequest->getControllerPackageKey() && $this->targetControllerName === $actionRequest->getControllerName();
}
if ($this->targetPackageKey !== $actionRequest->getControllerPackageKey()) {
return FALSE;
}
if ($this->targetControllerName !== $actionRequest->getControllerName()) {
return FALSE;
}
// for items with sub items a matching controller is enough to turn it active
if ($this->hasSubMenuItems() || $this->targetActionName === NULL) {
return TRUE;
}
if ($this->targetActionName !== $actionRequest->getControllerActionName()) {
return FALSE;
}
$targetActionArguments = $this->targetActionArguments;
$targetActionArguments = $this->persistenceManager->convertObjectsToIdentityArrays($targetActionArguments);
Arrays::sortKeysRecursively($targetActionArguments);
$requestArguments = $actionRequest->getArguments();
Arrays::sortKeysRecursively($requestArguments);
return ($targetActionArguments === $requestArguments);
}
/**
* @return boolean TRUE if this menu item is just a header (i.e. no target package set)
*/
public function isHeader() {
return $this->targetPackageKey === NULL && $this->label !== '';
}
/**
* @return boolean TRUE if this menu item is just a separator (i.e. no label set)
*/
public function isSeparator() {
return $this->label === '';
}
/**
* @return MenuItem[]
*/
public function getSubMenuItems() {
return $this->subMenuItems;
}
/**
* @param MenuItem[] $subMenuItems
* @return void
*/
public function setSubMenuItems(array $subMenuItems) {
$this->subMenuItems = $subMenuItems;
}
/**
* @param MenuItem $subMenuItem
* @return MenuItem the current instance to enable method chaining
*/
public function addSubMenuItem(MenuItem $subMenuItem) {
$this->subMenuItems[] = $subMenuItem;
return $this;
}
/**
* @return boolean
*/
public function hasSubMenuItems() {
return $this->subMenuItems !== [];
}
/**
* @return string
*/
public function getTargetPackageKey() {
return $this->targetPackageKey;
}
/**
* @return string
*/
public function getTargetControllerName() {
return $this->targetControllerName;
}
/**
* @return string
*/
public function getTargetActionName() {
return $this->targetActionName;
}
/**
* @return array
*/
public function getTargetActionArguments() {
return $this->targetActionArguments;
}
/**
* @return boolean
*/
public function isActive() {
return $this->active;
}
/**
* @return string
*/
public function getLabel() {
return $this->label;
}
/**
* @return string
*/
public function getBadge() {
return $this->badge;
}
/**
* @return string
*/
public function getIcon() {
return $this->icon;
}
}
@bwaidelich
Copy link
Author

Usage:
example settings:

Acme:
  SomePackage:
    mainMenu:
      menuItems:
        -
          label: 'HOME'
          package: 'Acme.SomePackage'
        -
          label: 'Billing'
          package: 'Acme.SomePackage'
          controller: 'Invoice'
          menuItems:
            -
              label: 'Invoices'
              package: 'Acme.SomePackage'
              controller: 'Invoice'
            -
              label: 'Dunnings'
              package: 'Acme.SomePackage'
              controller: 'Dunning'
              action: 'latest'
        -
          label: 'Customers'
          package: 'Acme.SomePackage'
          controller: 'Customer'

... in your controller:

class SomeController extends ActionController {

    /**
     * @param ViewInterface $view
     * @return void
     */
    protected function initializeView(ViewInterface $view) {
        $view->assign('mainMenu', new Menu($this->request, $this->settings['mainMenu']));
    }

    // ...
}

..and finally in your fluid template/layout:

<f:render partial="Menu" arguments="{menu: mainMenu}" />

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment