Typo3 tx_form: Empfänger im Frontend auswählbar machen. Eigenes Formular-Element und eigener Finisher

Im Formular-Editor des FormFrameworks von Typo3 soll man verschiedene Empfänger angeben können. Im Frontend erscheint eine Dropdown-Liste der Empfänger. Der User kann eine Zieladresse aus dem Dropdown auswählen und so bestimmen, wer die Mail erhält.


Anleitung im Backend FormEditor

  • Neues Formular-Element in Formular einfügen
  • Im Formular-Element eine Liste der möglichen Empfänger angeben. Pro Zeile ein Empfänger in dieser Schreibweise: email@adresse.de;Empfängername
  • Neuen Finisher für das Formular auswählen

1. Wähle einen Namen für Deine Extension

Wenn Du die beiden Felder hier ausfüllst, wird im Quelltext alles automatisch für Dich ersetzt:



2. Eine Extension anlegen

(oder eine vorhandene nutzen)

Wir ignorieren mal alle Normen und halten es simpel. Folgende Dateien und Struktur brauchst Du in Deiner Extension.

Ersetze nng durch den Ordner-Namen Deiner Extension, in der Du arbeitest.
Ersetze das Wort RecipientSelector im Dateinamen durch Deinen Bezeichner.

nng
│
├── Classes
│   ├── Finishers
│   │   └── RecipientSelectorFinisher.php
│   ├── ViewHelpers
│   │   └── RecipientSelectorViewHelper.php
├── Configuration
│   ├── TypoScript
│   │   └── setup.txt
│   └── Yaml
│       ├── BaseSetup.yaml
│       ├── FormEditorSetup.yaml
│       └── FormEngineSetup.yaml
├── Resources
│   ├── Private
│   │   └── FormFramework
│   │       └── Frontent
│   │           └── Partials
│   │               └── RecipientSelector.html
│   └── Public
│       ├── Icons
│       │   └── RecipientSelector-icon.svg
│       └── JavaScript
│           └── Backend
│               └── FormEditor
│                   └── RecipientSelectorViewModel.js
├── ext_emconf.php
├── ext_localconf.php
└── ext_typoscript_setup.txt

3. Inhalte der Dateien

Folgender Code muss in die einzelnen Dateien:


Configuration/TypoScript/setup.txt


# Einstellungen für das Frontend
plugin.tx_form.settings.yamlConfigurations {
   153450370601 = EXT:nng/Configuration/Yaml/FormEditorSetup.yaml
}
# Einstellungen für das Backend
module.tx_form.settings.yamlConfigurations {
   153450370601 = EXT:nng/Configuration/Yaml/FormEditorSetup.yaml
   153450370602 = EXT:nng/Configuration/Yaml/FormEngineSetup.yaml
}

Configuration/Yaml/FormEditorSetup.yaml


# – -------------------------------------------------------------------------
# Diese YAML wird im Backend UND Frontend geladen.
# 

TYPO3:
  CMS:
    Form:
      prototypes:
        standard:
          formElementsDefinition:

            # – -------------------------------------------------------
            # Registrierung zusätzlicher Fluid-Pfade

            Form:
              renderingOptions:
                templateRootPaths:
                  125: 'EXT:nng/Resources/Private/FormFramework/Frontend/Templates/'
                partialRootPaths:
                  125: 'EXT:nng/Resources/Private/FormFramework/Frontend/Partials/'
                layoutRootPaths:
                  125: 'EXT:nng/Resources/Private/FormFramework/Frontend/Layouts/'

            # – -------------------------------------------------------
            # Registrierung des neuen Formular-Elementes

            RecipientSelector:
              __inheritances:
                10: 'TYPO3.CMS.Form.mixins.formElementMixins.FormElementMixin'
              formEditor:
                label: 'Empfänger-Auswahl'
                group: custom
                groupSorting: 1000
                iconIdentifier: 'nng-selectrecipient'
                predefinedDefaults:
                  properties:
                    nng-recipients: ''
                editors:
                  300:
                    identifier: 'nng-recipients'
                    templateName: 'Inspector-TextareaEditor'
                    label: 'Empfänger-Liste'
                    propertyPath: 'properties.nng-recipients'

          # – -------------------------------------------------------
          # Registrierung des Finishers
          
          finishersDefinition:
            RecipientSelector:
              __inheritances:
                10: 'TYPO3.CMS.Form.mixins.finishersEmailMixin'
              implementationClassName: 'Nng\RecipientSelector\Finishers\RecipientSelectorFinisher'
              FormEngine:
                label: "C E-Mail an Empfänger aus Auswahl (nng)"
              formEditor:
                iconIdentifier: 'nng-selectrecipient'
                label: 'D E-Mail an Empfänger aus Auswahl (nng)'
                predefinedDefaults:
                  options:
                    pageUid: ''
                    additionalParameters: ''



Configuration/Yaml/FormEngineSetup.yaml



# – -------------------------------------------------------------------------
# Diese YAML wird NUR im Backend geladen.
# 

TYPO3:
  CMS:
    Form:
      prototypes:
        standard:

          # – -----------------------------------------------------
          # JS für Formular-Element im Backend bereitstellen
          
          formEditor:

            dynamicRequireJsModules:
              additionalViewModelModules:
                125: 'TYPO3/CMS/Nnsite/Backend/FormEditor/RecipientSelectorViewModel'

            formEditorPartials:
              FormElement-RecipientSelector: 'Stage/SimpleTemplate'

          formElementsDefinition:
            Form:
              # – -----------------------------------------------------
              # Backend: Definition des Finishers

              formEditor:
                editors:
                  # 900 ist fix. Hier werden die Finisher definiert.
                  900:
                    selectOptions:
                      125:
                        value: 'RecipientSelector'
                        label: 'E-Mail an Empfänger aus Auswahl (nnsite)'

                propertyCollections:
                  finishers:
                    125:
                      identifier: 'RecipientSelector'
                      editors:
                        __inheritances:
                          10: 'TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin'
                        100:
                          label: "E-Mail an Empfänger aus Auswahl (nnsite)"
                        120:
                          identifier: 'fieldSubject'
                          templateName: 'Inspector-TextEditor'
                          label: 'Betreff'
                          propertyPath: 'options.fieldSubject'
                          enableFormelementSelectionButton: true
                          propertyValidators:
                            10: 'NotEmpty'
                        500:
                          identifier: 'fieldFromEmail'
                          templateName: 'Inspector-TextEditor'
                          label: 'formEditor.elements.Form.finisher.EmailToSender.editor.senderAddress.label'
                          propertyPath: 'options.fieldFromEmail'
                          enableFormelementSelectionButton: true
                          propertyValidatorsMode: 'OR'
                          propertyValidators:
                            10: 'NaiveEmail'
                            20: 'FormElementIdentifierWithinCurlyBracesExclusive'
                          fieldExplanationText: 'formEditor.elements.Form.finisher.EmailToSender.editor.senderAddress.fieldExplanationText'
                        600:
                          identifier: 'fieldFromName'
                          templateName: 'Inspector-TextEditor'
                          label: 'formEditor.elements.Form.finisher.EmailToSender.editor.senderName.label'
                          propertyPath: 'options.fieldFromName'
                          enableFormelementSelectionButton: true
                          propertyValidators:
                            10: 'FormElementIdentifierWithinCurlyBracesInclusive'
                          fieldExplanationText: 'formEditor.elements.Form.finisher.EmailToSender.editor.senderName.fieldExplanationText'

Classes/ViewHelpers/RecipientSelectorViewHelper.html

Das eigentliche Fluid-Template für die Ausgabe im Frontend.


<?php

namespace Extname\RecipientSelector\ViewHelpers;

use \TYPO3\CMS\Extbase\Utility\DebuggerUtility;
use \TYPO3\CMS\Core\Utility\GeneralUtility;


class RecipientSelectorViewHelper extends \TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper {


    /**
     * @var boolean
     */
    protected $escapeChildren = false;


    /**
     * @var boolean
     */
    protected $escapeOutput = false;
    
    
    /**
	 * Initialize arguments.
	 *
	 * @return void
	 */
	public function initializeArguments() {
		parent::initializeArguments();
    	$this->registerArgument('str', 'string', 'Text', false);
	}
	
	
	/**
	 * 
	 *
	 * @throws \TYPO3\CMS\Fluid\Core\ViewHelper\Exception
	 * @return string Rendered tag
	 */
	public function render() {
		$str = $this->arguments['str'] ?: $this->renderChildren();
		$rows = explode("\n", $str);
		foreach ($rows as $k => $row) {
			$parts = explode(";", $row);
			$rows[$k] = ['key' => $k, 'email' => $parts[0], 'label'=>$parts[1]];
		}
		return $rows;
    }


}

Classes/Finishers/RecipientSelectorFinisher.php


<?php
namespace Extname\RecipientSelector'\Finishers;
 
use TYPO3\CMS\Core\Utility\GeneralUtility;	 
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Utility\PathUtility;

class RecipientSelectorFinisher extends \TYPO3\CMS\Form\Domain\Finishers\EmailFinisher {
    
    /**
	 * Finisher
     * 
	 */
	protected function executeInternal()
	{
		$formRuntime = $this->finisherContext->getFormRuntime();
		$formValues = $this->finisherContext->getFormValues();

		$fromEmail = $this->parseOption('fieldFromEmail');
		$fromName = $this->parseOption('fieldFromName');
		$subjectEmail = $this->parseOption('fieldSubject');

		$recipientElement = false;

		// Finde das ERSTE Formular-Element, in dem die Empfänger definiert wurden
		foreach ($formValues as $k => $val) {
			$element = $formRuntime->getFormDefinition()->getElementByIdentifier($k);
			if ($element->getType() == 'RecipientSelector') {
				$recipientElement = [
					'key' 		=> $k,
					'element' 	=> $element,
					'value'		=> $val,
				];
			}
		}

		if (!$recipientElement) {
			die('Finisher ist im Formular angegeben, aber Formular-Element für Auswahl des Empfängers fehlt.');
		}

		$recipients = explode("\n", $recipientElement['element']->getProperties()['nnsite-recipients'] ?? '');

		foreach ($recipients as $n=>$recipient) {
			$parts = explode(";", $recipient);
			$recipients[$n] = [
				'key' => $n,
				'label' => $parts[1],
				'email' => $parts[0],
			];
		}

		if (!$recipients) {
			die('Keine Zieladressen im Formular angegeben.');
		}

		$recipientName = $recipients[$recipientElement['value']]['label'] ?? '';
		$recipientEmail = $recipients[$recipientElement['value']]['email'] ?? '';

		if (!$recipients) {
			die('Zieladresse nicht bekannt.');
		}

		$this->setOption('templateName', 'html');
		$this->setOption('subject', $subjectEmail);
		$this->setOption('recipientName', $recipientName);
		$this->setOption('recipientAddress', $recipientEmail);
		$this->setOption('senderName', $fromName);
		$this->setOption('senderAddress', $fromEmail);

		parent::executeInternal();
	}

}

Resources/Private/FormFramework/Frontend/Partials/RecipientSelector.html

Das eigentliche Fluid-Template für die Ausgabe im Frontend.

<div>
	<h2>{element.properties.elementDescription}</h2>
	<f:form.textfield
		property="{element.identifier}"
		id="{element.uniqueIdentifier}"
		class="{element.properties.elementClassAttribute} form-control form-field"
		errorClass="{element.properties.elementErrorClassAttribute}"
		additionalAttributes="{formvh:translateElementProperty(element:element, property: 'fluidAdditionalAttributes')}"
	/>	
</div>

Resources/Public/Icons/RecipientSelector-icon.svg

Ein simples, quadratisches SVG-Icon für die Darstellung im Backend.


Resources/Public/JavaScript/Backend/FormEditor/RecipientSelectorViewmodel.js


// Resources/Public/JavaScript/Backend/FormEditor/RecipientSelectorViewmodel.js

define([
    'jquery',
    'TYPO3/CMS/Form/Backend/FormEditor/Helper'
], function ($, Helper) {
    'use strict';

    return (function ($, Helper) {

        /**
         * @private
         *
         * @var object
         */
        var _formEditorApp = null;

        /**
         * @private
         *
         * @return object
         */
        function getFormEditorApp() {
            return _formEditorApp;
        };

        /**
         * @private
         *
         * @return object
         */
        function getPublisherSubscriber() {
            return getFormEditorApp().getPublisherSubscriber();
        };

        /**
         * @private
         *
         * @return object
         */
        function getUtility() {
            return getFormEditorApp().getUtility();
        };

        /**
         * @private
         *
         * @param object
         * @return object
         */
        function getHelper() {
            return Helper;
        };

        /**
         * @private
         *
         * @return object
         */
        function getCurrentlySelectedFormElement() {
            return getFormEditorApp().getCurrentlySelectedFormElement();
        };

        /**
         * @private
         *
         * @param mixed test
         * @param string message
         * @param int messageCode
         * @return void
         */
        function assert(test, message, messageCode) {
            return getFormEditorApp().assert(test, message, messageCode);
        };

        /**
         * @private
         *
         * @return void
         * @throws 1491643380
         */
        function _helperSetup() {
            assert('function' === $.type(Helper.bootstrap),
                'The view model helper does not implement the method "bootstrap"',
                1491643380
            );
            Helper.bootstrap(getFormEditorApp());
        };

        /**
         * @private
         *
         * @return void
         */
        function _subscribeEvents() {
            /**
             * @private
             *
             * @param string
             * @param array
             *              args[0] = formElement
             *              args[1] = template
             * @return void
             */
            getPublisherSubscriber().subscribe('view/stage/abstract/render/template/perform', function (topic, args) {
                if (args[0].get('type') === 'RecipientSelector') {
                    getFormEditorApp().getViewModel().getStage().renderSimpleTemplateWithValidators(args[0], args[1]);
                }
            });
        };

        /**
         * @public
         *
         * @param object formEditorApp
         * @return void
         */
        function bootstrap(formEditorApp) {
            _formEditorApp = formEditorApp;
            _helperSetup();
            _subscribeEvents();
        };

        /**
         * Publish the public methods.
         * Implements the "Revealing Module Pattern".
         */
        return {
            bootstrap: bootstrap
        };
    })($, Helper);
});

ext_emconf.php

Der übliche Code, um eine Extension zu registrieren – falls nicht schon vorhanden.

<?php
$EM_CONF[$_EXTKEY] = [
    'title' => 'Meine Extension',
    'description' => '',
    'category' => 'plugin',
    'author' => 'David Bascom',
    'author_email' => 'deine@email.de',
    'state' => 'stable',
    'internal' => '',
    'uploadfolder' => '1',
    'createDirs' => '',
    'clearCacheOnLoad' => 0,
    'version' => '0.1',
    'constraints' => [
        'depends' => [
            'typo3' => '8.7.0',
        ],
        'conflicts' => [],
        'suggests' => [],
    ],
];

ext_localconf.php

Registriert das Icon

<?php 
if (TYPO3_MODE === 'BE') {
    /** @var \TYPO3\CMS\Core\Imaging\IconRegistry $iconRegistry */
    $iconRegistry = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Imaging\IconRegistry::class);
    $iconRegistry->registerIcon(
        'RecipientSelector-icon',
        \TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider::class,
        ['source' => 'EXT:nng/Resources/Public/Icons/RecipientSelector-icon.svg']
    );
}

ext_typoscript_setup.txt

Lädt das TypoScript, ohne das im Root-Template die „Enthält Erweiterungen“ gewählt werden müssen.

<INCLUDE_TYPOSCRIPT: source="FILE:EXT:nng/Configuration/Typoscript/setup.txt">

2 Gedanken zu „Typo3 tx_form: Empfänger im Frontend auswählbar machen. Eigenes Formular-Element und eigener Finisher“

  1. Hi. Vielen Dank für die ausführliche Darstellung.
    In folgender Datei habe ich ein Problem: Configuration/Yaml/FormEngineSetup.yaml

    Die RecipientselectorViewModel.js kann nicht geladen werden.

    Das dürfte mit folgender Zeile zusammenhängen:
    additionalViewModelModules:
    1632292624872: ‚TYPO3/CMS/Nnsite/Backend/FormEditor/RecipientselectorViewModel‘
    Wie ist der Pfad anzupassen, bzw. was genau passiert da eigentlich?
    Bin dankbar für jeden Schubs in die richtige Richtung!

  2. Oben beschriebenes Problem habe ich lösen/umgehen können in dem ich den individuellen Namen meines Site-Packages anstelle von ‚Nnsite‘ eingesetzt habe und die RecipientselectorViewModel.js an entsprechenden Ort in meinem Package gelegt habe.
    Das Backend funktioniert damit, im Frontend werden aber nicht gefundene Klassen angemeckert (‚Site-Packege-Name’\Recipientselector\Finishers\RecipientselectorFinisher). Vermutlich eine Namespace-Geschichte, vielleicht zu lösen wenn ich obiges komplett in ‚mein‘ Site-Package integriere.

    Ist dann aber wenig flexibel, daher: Oder vereinfacht gefragt: Gibt es die beschriebene Funktionalität als fertige Extension die ich in ein 11er TYPO3 laden kann und dann meine Formulare entsprechend erweitert? Gerne spende ich dafür auch einen Betrag an die Kaffekasse.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.