typo3extbasetypo3-11.x

How to add custom Node for fieldControl in selectMultipleSideBySide TCA to add custom control for TYPO3 11 LTS


I have below TCA having lots of record in MM table for side by side multiple selection.

'select_multiplesidebyside_6' => [
  'label' => 'select_multiplesidebyside_6 fieldControl',
  'config' => [
    'type' => 'select',
    'renderType' => 'selectMultipleSideBySide',
    'foreign_table' => 'tx_styleguide_staticdata',
    'size' => 5,
    'autoSizeMax' => 20,
    'fieldControl' => [
      'editPopup' => [
        'disabled' => false,
        'options' => [
          'windowOpenParameters' => 'height=300,width=500,status=0,' 
            . 'menubar=0,scrollbars=1',
        ],
      ],
      'addRecord' => [
        'disabled' => false,
      ],                    
      'allSelection' => [
        'renderType' => 'AllSelection'
      ],
    ],
  ],
],

Now, I want to enable custom option "allSelection" which create new button, On click it will select all available option from right side and add to selection.

I have added sub-class to override NodeFactory as below with classes.php

<?php

return [
    \TYPO3\CMS\Backend\Form\NodeFactory::class => [                
        'subclasses' => [
            'NodeFactory' => \NK\FeatureTour\Form\NodeFactory::class
        ],
    ],
];

and with Xclass as below:

$GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'][\TYPO3\CMS\Backend\Form\NodeFactory::class] = [
   'className' => \NK\FeatureTour\Form\NodeFactory::class,
];

I have wrote custom class called "AllSelection"

<?php

declare(strict_types=1);

namespace NK\FeatureTour\Form\FieldControl;

use TYPO3\CMS\Backend\Form\AbstractNode;
use TYPO3\CMS\Backend\Routing\UriBuilder;
use TYPO3\CMS\Core\Page\JavaScriptModuleInstruction;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\StringUtility;

class AllSelection extends AbstractNode
{
   /**
     * Add button control
     *
     * @return array As defined by FieldControl class
     */
    public function render()
    {
          // Logic goes here for rendering button
    }
}

and my NodeFactory class is:

<?php

/*
 * This file is part of the TYPO3 CMS project.
 *
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
 *
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with this source code.
 *
 * The TYPO3 project - inspiring people to share!
 */

namespace NK\FeatureTour\Form;

use TYPO3\CMS\Backend\Form\Container;
use TYPO3\CMS\Backend\Form\Element;
use TYPO3\CMS\Backend\Form\NodeExpansion;
use TYPO3\CMS\Backend\Form\FieldWizard;
use TYPO3\CMS\Backend\Form\FieldInformation;
use NK\FeatureTour\Form\FieldControl;
use TYPO3\CMS\Backend\Form\FieldControl as CoreFieldControl;

/**
 * Create an element object depending on renderType.
 *
 * This is the main factory to instantiate any node within the render
 * chain of FormEngine. All nodes must implement NodeInterface.
 *
 * Nodes are "container" classes of the render chain, "element" classes that
 * render single elements, as well as "fieldWizard", "fieldInformation" and
 * "fieldControl" classes which are called by single elements to enrich them.
 *
 * This factory gets a string "renderType" and then looks up in a list which
 * specific class should handle this renderType. This list can be extended with
 * own renderTypes by extensions, existing renderTypes can be overridden, and
 * - for complex cases - it is possible to register own resolver classes for single
 * renderTypes that can return a node class name to override the default lookup list.
 */
class NodeFactory extends \TYPO3\CMS\Backend\Form\NodeFactory
{
    /**
     * Default registry of node name to handling class
     *
     * @var array
     */
    protected $nodeTypes = [
        /* Default node type goes here
         * Core class: \TYPO3\CMS\Backend\Form\NodeFactory
         */
        ...        
        'allSelection' => FieldControl\AllSelection::class,
        ...
    ];

    /**
     * Set up factory. Initialize additionally registered nodes.
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Create a node depending on type
     *
     * @param array $data All information to decide which class should be instantiated and given down to sub nodes
     * @return NodeInterface
     * @throws Exception
     */
    public function create(array $data)
    {
        return parent::create($data);
    }

    /**
     * Add node types from nodeRegistry to $this->nodeTypes.
     * This can be used to add new render types or to overwrite existing node types. The registered class must
     * implement the NodeInterface and will be called if a node with this renderType is rendered.
     *
     * @throws Exception if configuration is incomplete or two nodes with identical priorities are registered
     */
    protected function registerAdditionalNodeTypesFromConfiguration()
    {
        parent::registerAdditionalNodeTypesFromConfiguration();
    }

    /**
     * Add resolver and add them sorted to a local property.
     * This can be used to manipulate the nodeName to class resolution with own code.
     *
     * @throws Exception if configuration is incomplete or two resolver with identical priorities are registered
     */
    protected function initializeNodeResolver()
    {
        parent::initializeNodeResolver();
    }

    /**
     * Instantiate given class name
     *
     * @param string $className Given class name
     * @param array $data Main data array
     * @return object
     */
    protected function instantiate($className, array $data)
    {
        return parent::instantiate($className, $data);
    }
}
?>

But, this not giving any output within TCA, Can anyone has any idea how I can add new custom Node Type for selectMultipleSideBySide.


Solution

  • You can try this approach,

    Add below into ext_localconf.php

    $GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['nodeRegistry'][1730889775]
      = [
        'nodeName' => 'selectAllControl',
        'priority' => 40,
        'class' => \Vendor\YourExt\Form\Element\SelectAllControlElement::class,
      ];
    

    Add below into TCA

    'state' => [
      'exclude' => true,
      'label' => 'state',
      'config' => [
        'type' => 'select',
        'renderType' => 'selectMultipleSideBySide',
        'foreign_table' => 'your_table',
        'MM' => 'your_mm_table',
        'size' => 5,
        'autoSizeMax' => 20,
        'fieldControl' => [
          'selectAllControl' => [
            'disabled' => false,
            'renderType' => 'selectAllControl'
          ],
        ],
      ],
    ],
    

    Add node class named "SelectAllControlElement"

    <?php
        
    namespace Vendor\YourExt\Form\Element;
        
    use TYPO3\CMS\Backend\Form\AbstractNode;
        
    class SelectAllControlElement extends AbstractNode
    {
      public function render()
      {
        $result = [
          'iconIdentifier' => 'select-all',
          'title' => 'Select All',
          'linkAttributes' => [
            'class' => 'importData ',
            'data-id' => 'allStateSelect'
          ],
          'requireJsModules' => [
            'TYPO3/CMS/YourExt/SelectAllModule',
          ],
        ];
        return $result;
      }
    }
    

    Add below Require Js at path "Ext:your_ext/Resources/Public/JavaScript/SelectAllModule.js"

    define([], function() {
      document.querySelectorAll('a[data-id="allStateSelect"]').forEach(element => {
        element.addEventListener('click', function() {
          const selectElements = document.querySelectorAll('.form-select.t3js-formengine-select-itemstoselect');
          selectElements.forEach(selectElement => {
            var visibleOptions = Array.from(selectElement.options).filter(option => !option.classList.contains('hidden'));
        
            visibleOptions.forEach(option => {
              option.selected = true; // Set it as selected
              selectElement.dispatchEvent(new Event('click'));
            });
          });
        });
      })
    });