Instantly share code, notes, and snippets.

Embed
What would you like to do?
How to add a custom element for the formEditor of ext:form

EXT:form How to add a custom form element

If you like to add a custom form element, please follow these steps. For detailed information and documentation of ext:form please visit the official documentation: https://docs.typo3.org/typo3cms/extensions/form/Index.html

Basics

First of all create a new extension, which will hold all the configuration, templates, etc.

Let's call it your_custom_extension whithin this how to.

This is an example structure of the extension:

├── Classes
├── Configuration
│   ├── TypoScript
│   │   ├── constants.txt
│   │   └── setup.txt
│   └── Yaml
│       ├── BaseSetup.yaml
│       └── FormEditorSetup.yaml
├── Documentation
├── Resources
│   ├── Private
│   │   ├── Frontend
│   │   │   └── Partials
│   │   │       └── YourCustomElement.html
│   │   └── Language
│   │       └── Backend.xlf
│   └── Public
│       ├── Icons
│       │   ├── Extension.svg
│       │   └── your_custom_element_icon.svg
│       └── JavaScript
│           └── Backend
│               └── FormEditor
│                   └── YourCustomElementViewModel.js
├── Tests
├── composer.json
├── ext_emconf.php
├── ext_localconf.php
└── ext_typoscript_setup.txt

Ok, let's get our hands dirty.

YAML

As the configuration of the ext:form is written in YAML, you have to define your new shiny element here.

# Configuration/Yaml/BaseSetup.yaml

TYPO3:
  CMS:
    Form:
      prototypes:
        standard:
          formEditor:
            formEditorPartials:
              FormElement-YourCustomElement: 'Stage/SimpleTemplate'

          formElementsDefinition:
            Form:
              renderingOptions:
                templateRootPaths:
                  100: 'EXT:your_custom_extension/Resources/Private/Frontend/Templates/'
                partialRootPaths:
                  100: 'EXT: your_custom_extension/Resources/Private/Frontend/Partials/'
                layoutRootPaths:
                  100: 'EXT: your_custom_extension/Resources/Private/Frontend/Layouts/'

            YourCustomElement:
              __inheritances:
                10: 'TYPO3.CMS.Form.mixins.formElementMixins.YourCustomElementMixin'
      mixins:
        formElementMixins:
          YourCustomElementMixin:
            __inheritances:
              10: 'TYPO3.CMS.Form.mixins.formElementMixins.FormElementMixin'

At this point you will give your elment a template that should be used in the Stage area of the form editor FormElement-YourCustomElement. For more information please read: https://docs.typo3.org/typo3cms/extensions/form/ApiReference/Index.html#stage-simpletemplate

In most cases the Stage/SimpleTemplate will be enough.

At renderingOptions we define our custom path to templates, layouts and / or partials, that should be used to render within the website (Frontend).

Let's register our custom new element underneath formElementsDefinition with the name of the element. This one should be unique, as it could be replaced / used by another extension otherwise. In this example we create a custom mixin for that element, which inherites from the TYPO3.CMS.Form.mixins.formElementMixins.FormElementMixin which I discovered is the most basic mixin you would like to use in combination with the Stage/SimpleTemplate. Other mixins could be used as well. If you base on an input field for example, you could use the TYPO3.CMS.Form.mixins.formElementMixins.TextMixin provided by ext:form.

Now that we have defined the basics for the frontend, let's define the configuration for the form editor:

# Configuration/Yaml/FormEditorSetup.yaml

TYPO3:
  CMS:
    Form:
      prototypes:
        standard:
          formEditor:
            dynamicRequireJsModules:
              additionalViewModelModules:
                - 'TYPO3/CMS/YourCustomExtension/Backend/FormEditor/YourCustomElementViewModel'

            formEditorPartials:
              FormElement-YourCustomElement: 'Stage/SimpleTemplate'

          formElementsDefinition:
            YourCustomElement:
              formEditor:
                label: 'formEditor.elements.YourCustomElement.label'
                group: custom
                groupSorting: 1000
                iconIdentifier: 't3-form-icon-your-custom-element'
      mixins:
        formElementMixins:
          YourCustomElementMixin:
            __inheritances:
              10: 'TYPO3.CMS.Form.mixins.formElementMixins.FormElementMixin'

As the configuration could be different to the basic configuration for the form editor, I configured some settings here again.

Let's keep an eye on the new and elementary parts.

Register your element for the "create new element" modal with this snippet:

formElementsDefinition:
  YourCustomElement:
    formEditor:
      label: 'formEditor.elements.YourCustomElement.label'
      group: custom
      groupSorting: 1000
      iconIdentifier: 't3-form-icon-your-custom-element'
  • The key used at label is the key used in the language file
  • group and groupSorting should be self explaning
  • The icon used at iconIdentifier as to be registered with the IconFactory and should be a SVG

The most trickiest part for me to figure out was to define a model for the stage area as the other stuff works with this little configuration. Thus, here the snippet to get the full experience of the element used in the stage (label of the element, used validator information, etc.):

formEditor:
  dynamicRequireJsModules:
    additionalViewModelModules:
      - 'TYPO3/CMS/YourCustomExtension/Backend/FormEditor/YourCustomElementViewModel'

This one will point to a JavaScript model (defined under JavaScript) within your extension, that will hold all information and will trigger the rendering within the stage.

TypoScript

This was a lot of YAML until now!

Let's register it thus the ext:form will notice it.

My suggestions is to use the ext_typoscript_setup.txt at the root level of your extension.

# ext_typoscript_setup.txt

module.tx_form {
    settings {
        yamlConfigurations {
            40 = EXT:your_custom_extension/Configuration/Yaml/BaseSetup.yaml
            50 = EXT:your_custom_extension/Configuration/Yaml/FormEditorSetup.yaml
        }
    }
    view {
        partialRootPaths {
            40 = EXT:your_custom_extension/Resources/Private/Backend/Partials/
        }
    }
}

This will help for the backend, but the frontend needs some definition as well, thus we have to use the common Configuration/TypoScript/setup.txt for that:

# Configuration/TypoScript/setup.txt

plugin.tx_form {
    settings {
        yamlConfigurations.40 = EXT:your_custom_extension/Configuration/Yaml/BaseSetup.yaml
    }

    view {
        partialRootPaths.100 = EXT:your_custom_extension/Resources/Private/Frontend/Partials/
    }
}

That's it for TypoScript

Language file

To override the language file for the backend, we have to override that one within ext_localconf:

# ext_localconf.php

// register language file for backend
    $GLOBALS['TYPO3_CONF_VARS']['SYS']['locallangXMLOverride']['EXT:form/Resources/Private/Language/Database.xlf'][] = 'EXT:your_custom_extension/Resources/Private/Language/Backend.xlf'

Here should the label etc. be placed as mentioned in the first section.

Layout / Template / Partial

As we configured everything in the first step, we could use fluid layouts, templates and partials for rendering in the Frontend. Have a look at ext:form how to use them.

JavaScript

This snippet is mandatory to display your custom element correctly with all features in the stage area if you use the Stage/SimpleElement template for your element defined in the first step:

// Resources/Public/JavaScript/Backend/FormEditor/YourCustomElementViewModel.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') === 'YourCustomElement') {
                    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);
});

You have only one thing to change:

if (args[0].get('type') === 'YourCustomElement') { // use your registered unique element name here
    getFormEditorApp().getViewModel().getStage().renderSimpleTemplateWithValidators(args[0], args[1]);
}
@David-Bascom

This comment has been minimized.

Show comment
Hide comment
@David-Bascom

David-Bascom Aug 17, 2018

Thanks for this great tutorial and starting point. We used it as a base for this article, which might be helpful. It also has a simple "formelement generator":
http://labor.99grad.de/2018/08/17/typo3-tx_form-eigenes-formularelement-erstellen-custom-form-element/

David-Bascom commented Aug 17, 2018

Thanks for this great tutorial and starting point. We used it as a base for this article, which might be helpful. It also has a simple "formelement generator":
http://labor.99grad.de/2018/08/17/typo3-tx_form-eigenes-formularelement-erstellen-custom-form-element/

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