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">

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.