[FEATURE] Allow setting of validation messages in form editor 78/52078/31
authorRalf Zimmermann <ralf.zimmermann@tritum.de>
Fri, 17 Mar 2017 11:35:01 +0000 (12:35 +0100)
committerSusanne Moog <susanne.moog@typo3.org>
Wed, 4 Apr 2018 09:30:00 +0000 (11:30 +0200)
Add the form element property "validationErrorMessages" to allow the
definition of custom validation error messages with the help of the
form editor.

Resolves: #80124
Releases: master
Change-Id: Ic72a5adf0a943a0fae11eb4af89c66f7fa6ca00f
Reviewed-on: https://review.typo3.org/52078
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Tobi Kretschmann <tobi@tobishome.de>
Reviewed-by: Steffen Frese <steffenf14@gmail.com>
Reviewed-by: Alexander Opitz <opitz.alexander@googlemail.com>
Tested-by: Alexander Opitz <opitz.alexander@googlemail.com>
Reviewed-by: Susanne Moog <susanne.moog@typo3.org>
Tested-by: Susanne Moog <susanne.moog@typo3.org>
typo3/sysext/core/Documentation/Changelog/master/Feature-80124-EXTform-AllowSettingOfValidationMessagesInFormEditor.rst [new file with mode: 0644]
typo3/sysext/form/Classes/Domain/Model/Renderable/AbstractRenderable.php
typo3/sysext/form/Configuration/Yaml/FormEditorSetup.yaml
typo3/sysext/form/Resources/Private/Backend/Partials/FormEditor/Inspector/RequiredValidatorEditor.html
typo3/sysext/form/Resources/Private/Backend/Partials/FormEditor/Inspector/ValidationErrorMessageEditor.html [new file with mode: 0644]
typo3/sysext/form/Resources/Private/Language/Database.xlf
typo3/sysext/form/Resources/Public/JavaScript/Backend/FormEditor/InspectorComponent.js
typo3/sysext/form/Resources/Public/JavaScript/Backend/FormEditor/ViewModel.js

diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-80124-EXTform-AllowSettingOfValidationMessagesInFormEditor.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-80124-EXTform-AllowSettingOfValidationMessagesInFormEditor.rst
new file mode 100644 (file)
index 0000000..5437118
--- /dev/null
@@ -0,0 +1,14 @@
+.. include:: ../../Includes.txt
+
+================================================================================
+Feature: #80124 - EXT:form - allow setting of validation messages in form editor
+================================================================================
+
+See :issue:`80124`
+
+Description
+===========
+
+A new form element property "validationErrorMessages" has been introduced. It allows the definition of custom validation error messages. Within the form editor, one can set those error messages for all existing validators.
+
+.. index:: Backend, Frontend, ext:form, NotScanned
index aff8fa9..624268b 100644 (file)
@@ -157,8 +157,14 @@ abstract class AbstractRenderable implements RenderableInterface
         }
 
         if (isset($options['validators'])) {
+            static $configurationHashes = [];
             foreach ($options['validators'] as $validatorConfiguration) {
+                $configurationHash = md5($this->getIdentifier() . json_encode($validatorConfiguration));
+                if (in_array($configurationHash, $configurationHashes)) {
+                    continue;
+                }
                 $this->createValidator($validatorConfiguration['identifier'], $validatorConfiguration['options'] ?? []);
+                $configurationHashes[] = $configurationHash;
             }
         }
 
index e6291ef..e129746 100644 (file)
@@ -98,6 +98,7 @@ TYPO3:
               Inspector-ValidatorsEditor: 'Inspector/ValidatorsEditor'
               Inspector-RequiredValidatorEditor: 'Inspector/RequiredValidatorEditor'
               Inspector-CheckboxEditor: 'Inspector/CheckboxEditor'
+              Inspector-ValidationErrorMessageEditor: 'Inspector/ValidationErrorMessageEditor'
               Inspector-Typo3WinBrowserEditor: 'Inspector/Typo3WinBrowserEditor'
 
             formElementPropertyValidatorsDefinition:
@@ -724,6 +725,14 @@ TYPO3:
                           10: 'TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin'
                         100:
                           label: 'formEditor.elements.DatePicker.validators.DateTime.editor.header.label'
+                        200:
+                          identifier: 'validationErrorMessage'
+                          templateName: 'Inspector-ValidationErrorMessageEditor'
+                          label: 'formEditor.elements.DatePicker.validators.DateTime.editor.validationErrorMessage.label'
+                          fieldExplanationText: 'formEditor.elements.DatePicker.validators.DateTime.editor.validationErrorMessage.fieldExplanationText'
+                          errorCodes:
+                            10: 1238087674
+                          propertyPath: 'properties.validationErrorMessages'
 
             StaticText:
               formEditor:
@@ -1094,6 +1103,16 @@ TYPO3:
                   validatorIdentifier: 'NotEmpty'
                   propertyPath: 'properties.fluidAdditionalAttributes.required'
                   propertyValue: 'required'
+                  configurationOptions:
+                    validationErrorMessage:
+                      label: 'formEditor.elements.FormElement.editor.requiredValidator.validationErrorMessage.label'
+                      propertyPath: 'properties.validationErrorMessages'
+                      fieldExplanationText: 'formEditor.elements.FormElement.editor.requiredValidator.validationErrorMessage.fieldExplanationText'
+                      errorCodes:
+                        10: 1221560910
+                        20: 1221560718
+                        30: 1347992400
+                        40: 1347992453
 
           TextMixin:
             formEditor:
@@ -1153,6 +1172,15 @@ TYPO3:
                         10: 'TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin'
                       100:
                         label: 'formEditor.elements.TextMixin.validators.Alphanumeric.editor.header.label'
+                      200:
+                        identifier: 'validationErrorMessage'
+                        templateName: 'Inspector-ValidationErrorMessageEditor'
+                        label: 'formEditor.elements.TextMixin.validators.Alphanumeric.editor.validationErrorMessage.label'
+                        fieldExplanationText: 'formEditor.elements.TextMixin.validators.Alphanumeric.editor.validationErrorMessage.fieldExplanationText'
+                        errorCodes:
+                          10: 1221551320
+                        propertyPath: 'properties.validationErrorMessages'
+
                   20:
                     identifier: 'Text'
                     editors:
@@ -1160,6 +1188,15 @@ TYPO3:
                         10: 'TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin'
                       100:
                         label: 'formEditor.elements.TextMixin.validators.Text.editor.header.label'
+                      200:
+                        identifier: 'validationErrorMessage'
+                        templateName: 'Inspector-ValidationErrorMessageEditor'
+                        label: 'formEditor.elements.TextMixin.validators.Text.editor.validationErrorMessage.label'
+                        fieldExplanationText: 'formEditor.elements.TextMixin.validators.Text.editor.validationErrorMessage.fieldExplanationText'
+                        errorCodes:
+                          10: 1221565786
+                        propertyPath: 'properties.validationErrorMessages'
+
                   30:
                     identifier: 'StringLength'
                     editors:
@@ -1174,6 +1211,19 @@ TYPO3:
                       300:
                         additionalElementPropertyPaths:
                           10: 'properties.fluidAdditionalAttributes.maxlength'
+                      400:
+                        identifier: 'validationErrorMessage'
+                        templateName: 'Inspector-ValidationErrorMessageEditor'
+                        label: 'formEditor.elements.TextMixin.validators.StringLength.editor.validationErrorMessage.label'
+                        fieldExplanationText: 'formEditor.elements.TextMixin.validators.StringLength.editor.validationErrorMessage.fieldExplanationText'
+                        errorCodes:
+                          10: 1238110957
+                          20: 1269883975
+                          30: 1428504122
+                          40: 1238108068
+                          50: 1238108069
+                        propertyPath: 'properties.validationErrorMessages'
+
                   40:
                     identifier: 'EmailAddress'
                     editors:
@@ -1181,6 +1231,15 @@ TYPO3:
                         10: 'TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin'
                       100:
                         label: 'formEditor.elements.TextMixin.validators.EmailAddress.editor.header.label'
+                      200:
+                        identifier: 'validationErrorMessage'
+                        templateName: 'Inspector-ValidationErrorMessageEditor'
+                        label: 'formEditor.elements.TextMixin.validators.EmailAddress.editor.validationErrorMessage.label'
+                        fieldExplanationText: 'formEditor.elements.TextMixin.validators.EmailAddress.editor.validationErrorMessage.fieldExplanationText'
+                        errorCodes:
+                          10: 1221559976
+                        propertyPath: 'properties.validationErrorMessages'
+
                   50:
                     identifier: 'Integer'
                     editors:
@@ -1188,6 +1247,15 @@ TYPO3:
                         10: 'TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin'
                       100:
                         label: 'formEditor.elements.TextMixin.validators.Integer.editor.header.label'
+                      200:
+                        identifier: 'validationErrorMessage'
+                        templateName: 'Inspector-ValidationErrorMessageEditor'
+                        label: 'formEditor.elements.TextMixin.validators.Integer.editor.validationErrorMessage.label'
+                        fieldExplanationText: 'formEditor.elements.TextMixin.validators.Integer.editor.validationErrorMessage.fieldExplanationText'
+                        errorCodes:
+                          10: 1221560494
+                        propertyPath: 'properties.validationErrorMessages'
+
                   60:
                     identifier: 'Float'
                     editors:
@@ -1195,6 +1263,15 @@ TYPO3:
                         10: 'TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin'
                       100:
                         label: 'formEditor.elements.TextMixin.validators.Float.editor.header.label'
+                      200:
+                        identifier: 'validationErrorMessage'
+                        templateName: 'Inspector-ValidationErrorMessageEditor'
+                        label: 'formEditor.elements.TextMixin.validators.Float.editor.validationErrorMessage.label'
+                        fieldExplanationText: 'formEditor.elements.TextMixin.validators.Float.editor.validationErrorMessage.fieldExplanationText'
+                        errorCodes:
+                          10: 1221560288
+                        propertyPath: 'properties.validationErrorMessages'
+
                   70:
                     identifier: 'NumberRange'
                     editors:
@@ -1209,6 +1286,16 @@ TYPO3:
                       300:
                         additionalElementPropertyPaths:
                           10: 'properties.fluidAdditionalAttributes.max'
+                      400:
+                        identifier: 'validationErrorMessage'
+                        templateName: 'Inspector-ValidationErrorMessageEditor'
+                        label: 'formEditor.elements.TextMixin.validators.NumberRange.editor.validationErrorMessage.label'
+                        fieldExplanationText: 'formEditor.elements.TextMixin.validators.NumberRange.editor.validationErrorMessage.fieldExplanationText'
+                        errorCodes:
+                          10: 1221563685
+                          20: 1221561046
+                        propertyPath: 'properties.validationErrorMessages'
+
                   80:
                     identifier: 'RegularExpression'
                     editors:
@@ -1224,6 +1311,14 @@ TYPO3:
                         propertyPath: 'options.regularExpression'
                         propertyValidators:
                           10: 'NotEmpty'
+                      300:
+                        identifier: 'validationErrorMessage'
+                        templateName: 'Inspector-ValidationErrorMessageEditor'
+                        label: 'formEditor.elements.TextMixin.validators.RegularExpression.editor.validationErrorMessage.label'
+                        fieldExplanationText: 'formEditor.elements.TextMixin.validators.RegularExpression.editor.validationErrorMessage.fieldExplanationText'
+                        errorCodes:
+                          10: 1221565130
+                        propertyPath: 'properties.validationErrorMessages'
 
           SelectionMixin:
             formEditor:
@@ -1277,6 +1372,15 @@ TYPO3:
                         20: 'TYPO3.CMS.Form.mixins.formElementMixins.MinimumMaximumEditorsMixin'
                       100:
                         label: 'formEditor.elements.MultiSelectionMixin.validators.Count.editor.header.label'
+                      400:
+                        identifier: 'validationErrorMessage'
+                        templateName: 'Inspector-ValidationErrorMessageEditor'
+                        label: 'formEditor.elements.TextMixin.validators.Count.editor.validationErrorMessage.label'
+                        fieldExplanationText: 'formEditor.elements.TextMixin.validators.Count.editor.validationErrorMessage.fieldExplanationText'
+                        errorCodes:
+                          10: 1475002976
+                          20: 1475002994
+                        propertyPath: 'properties.validationErrorMessages'
 
           FileUploadMixin:
             formEditor:
index 314ab3a..96ddf44 100644 (file)
@@ -1,6 +1,6 @@
 <html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" xmlns:formvh="http://typo3.org/ns/TYPO3/CMS/Form/ViewHelpers" data-namespace-typo3-fluid="true">
 <div class="form-editor">
-       <div class="t3-form-control-group form-group">
+       <div class="t3-form-control-group form-group" data-identifier="editorWrapper">
                <label>
                        <span data-template-property="label" />
                        <div class="t3-form-controls" data-identifier="inspectorEditorControlsWrapper">
@@ -8,5 +8,12 @@
                        </div>
                </label>
        </div>
+       <div class="t3-form-control-group form-group" data-template-property="validationErrorMessage">
+               <label><span data-template-property="validationErrorMessage-label" /></label>
+               <div class="t3-form-controls" data-identifier="inspectorEditorControlsWrapper">
+                       <input type="text" value="" data-template-property="validationErrorMessage-propertyPath" class="form-control">
+               </div>
+               <span data-template-property="validationErrorMessage-fieldExplanationText" />
+       </div>
 </div>
 </html>
diff --git a/typo3/sysext/form/Resources/Private/Backend/Partials/FormEditor/Inspector/ValidationErrorMessageEditor.html b/typo3/sysext/form/Resources/Private/Backend/Partials/FormEditor/Inspector/ValidationErrorMessageEditor.html
new file mode 100644 (file)
index 0000000..1eb903b
--- /dev/null
@@ -0,0 +1,13 @@
+<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" xmlns:formvh="http://typo3.org/ns/TYPO3/CMS/Form/ViewHelpers" data-namespace-typo3-fluid="true">
+<div class="form-editor">
+       <div class="t3-form-control-group form-group">
+               <label><span data-template-property="label" /></label>
+               <div data-identifier="inspectorEditorControlsGroup">
+                       <div class="t3-form-controls" data-identifier="inspectorEditorControlsWrapper">
+                               <input type="text" value="" data-template-property="propertyPath" class="form-control">
+                       </div>
+               </div>
+               <span data-template-property="fieldExplanationText" />
+       </div>
+</div>
+</html>
index ba2039a..40c0542 100644 (file)
             <trans-unit id="formEditor.elements.TextMixin.editor.validators.RegularExpression.label" xml:space="preserve">
                 <source>Regular expression</source>
             </trans-unit>
+
             <trans-unit id="formEditor.elements.TextMixin.validators.Alphanumeric.editor.header.label" xml:space="preserve">
                 <source>Alphanumeric</source>
             </trans-unit>
+            <trans-unit id="formEditor.elements.TextMixin.validators.Alphanumeric.editor.validationErrorMessage.label" xml:space="preserve">
+                <source>Custom error message</source>
+            </trans-unit>
+            <trans-unit id="formEditor.elements.TextMixin.validators.Alphanumeric.editor.validationErrorMessage.fieldExplanationText" xml:space="preserve">
+                <source>Error message which is shown if the validation does not succeed</source>
+            </trans-unit>
+
             <trans-unit id="formEditor.elements.TextMixin.validators.Text.editor.header.label" xml:space="preserve">
                 <source>Non-XML text</source>
             </trans-unit>
+            <trans-unit id="formEditor.elements.TextMixin.validators.Text.editor.validationErrorMessage.label" xml:space="preserve">
+                <source>Custom error message</source>
+            </trans-unit>
+            <trans-unit id="formEditor.elements.TextMixin.validators.Text.editor.validationErrorMessage.fieldExplanationText" xml:space="preserve">
+                <source>Error message which is shown if the validation does not succeed</source>
+            </trans-unit>
+
             <trans-unit id="formEditor.elements.TextMixin.validators.StringLength.editor.header.label" xml:space="preserve">
                 <source>String length</source>
             </trans-unit>
+            <trans-unit id="formEditor.elements.TextMixin.validators.StringLength.editor.validationErrorMessage.label" xml:space="preserve">
+                <source>Custom error message</source>
+            </trans-unit>
+            <trans-unit id="formEditor.elements.TextMixin.validators.StringLength.editor.validationErrorMessage.fieldExplanationText" xml:space="preserve">
+                <source>Error message which is shown if the validation does not succeed</source>
+            </trans-unit>
+
             <trans-unit id="formEditor.elements.TextMixin.validators.EmailAddress.editor.header.label" xml:space="preserve">
                 <source>Email</source>
             </trans-unit>
+            <trans-unit id="formEditor.elements.TextMixin.validators.EmailAddress.editor.validationErrorMessage.label" xml:space="preserve">
+                <source>Custom error message</source>
+            </trans-unit>
+            <trans-unit id="formEditor.elements.TextMixin.validators.EmailAddress.editor.validationErrorMessage.fieldExplanationText" xml:space="preserve">
+                <source>Error message which is shown if the validation does not succeed</source>
+            </trans-unit>
+
             <trans-unit id="formEditor.elements.TextMixin.validators.Integer.editor.header.label" xml:space="preserve">
                 <source>Integer number</source>
             </trans-unit>
+            <trans-unit id="formEditor.elements.TextMixin.validators.Integer.editor.validationErrorMessage.label" xml:space="preserve">
+                <source>Custom error message</source>
+            </trans-unit>
+            <trans-unit id="formEditor.elements.TextMixin.validators.Integer.editor.validationErrorMessage.fieldExplanationText" xml:space="preserve">
+                <source>Error message which is shown if the validation does not succeed</source>
+            </trans-unit>
+
             <trans-unit id="formEditor.elements.TextMixin.validators.Float.editor.header.label" xml:space="preserve">
                 <source>Floating-point number</source>
             </trans-unit>
+            <trans-unit id="formEditor.elements.TextMixin.validators.Float.editor.validationErrorMessage.label" xml:space="preserve">
+                <source>Custom error message</source>
+            </trans-unit>
+            <trans-unit id="formEditor.elements.TextMixin.validators.Float.editor.validationErrorMessage.fieldExplanationText" xml:space="preserve">
+                <source>Error message which is shown if the validation does not succeed</source>
+            </trans-unit>
+
             <trans-unit id="formEditor.elements.TextMixin.validators.Number.editor.header.label" xml:space="preserve">
                 <source>Number</source>
             </trans-unit>
+
             <trans-unit id="formEditor.elements.TextMixin.validators.NumberRange.editor.header.label" xml:space="preserve">
                 <source>Number range</source>
             </trans-unit>
+            <trans-unit id="formEditor.elements.TextMixin.validators.NumberRange.editor.validationErrorMessage.label" xml:space="preserve">
+                <source>Custom error message</source>
+            </trans-unit>
+            <trans-unit id="formEditor.elements.TextMixin.validators.NumberRange.editor.validationErrorMessage.fieldExplanationText" xml:space="preserve">
+                <source>Error message which is shown if the validation does not succeed</source>
+            </trans-unit>
+
             <trans-unit id="formEditor.elements.TextMixin.validators.RegularExpression.editor.header.label" xml:space="preserve">
                 <source>Regular expression</source>
             </trans-unit>
             <trans-unit id="formEditor.elements.TextMixin.validators.RegularExpression.editor.regex.fieldExplanationText" xml:space="preserve">
                 <source>Enter a valid PHP PCRE regular expression here.</source>
             </trans-unit>
+            <trans-unit id="formEditor.elements.TextMixin.validators.RegularExpression.editor.validationErrorMessage.label" xml:space="preserve">
+                <source>Custom error message</source>
+            </trans-unit>
+            <trans-unit id="formEditor.elements.TextMixin.validators.RegularExpression.editor.validationErrorMessage.fieldExplanationText" xml:space="preserve">
+                <source>Error message which is shown if the validation does not succeed</source>
+            </trans-unit>
 
             <trans-unit id="formEditor.elements.SelectionMixin.editor.inactiveOption.label" xml:space="preserve">
                 <source>Inactive option</source>
             <trans-unit id="formEditor.elements.FormElement.editor.requiredValidator.label" xml:space="preserve">
                 <source>Required field</source>
             </trans-unit>
+            <trans-unit id="formEditor.elements.FormElement.editor.requiredValidator.validationErrorMessage.label" xml:space="preserve">
+                <source>Custom error message</source>
+            </trans-unit>
+            <trans-unit id="formEditor.elements.FormElement.editor.requiredValidator.validationErrorMessage.fieldExplanationText" xml:space="preserve">
+                <source>Error message which is shown if the validation does not succeed</source>
+            </trans-unit>
 
             <trans-unit id="formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.label" xml:space="preserve">
                 <source>Grid viewport configuration</source>
             <trans-unit id="formEditor.elements.DatePicker.validators.DateTime.editor.header.label" xml:space="preserve">
                 <source>Date/time</source>
             </trans-unit>
+            <trans-unit id="formEditor.elements.DatePicker.validators.DateTime.editor.validationErrorMessage.label" xml:space="preserve">
+                <source>Custom error message</source>
+            </trans-unit>
+            <trans-unit id="formEditor.elements.DatePicker.validators.DateTime.editor.validationErrorMessage.fieldExplanationText" xml:space="preserve">
+                <source>Error message which is shown if the validation does not succeed</source>
+            </trans-unit>
 
             <trans-unit id="formEditor.elements.StaticText.label" xml:space="preserve">
                 <source>Static text</source>
index 56aa63e..13d0b0c 100644 (file)
@@ -85,6 +85,8 @@ define(['jquery',
         'Inspector-TextEditor': 'Inspector-TextEditor',
         'Inspector-Typo3WinBrowserEditor': 'Inspector-Typo3WinBrowserEditor',
         'Inspector-ValidatorsEditor': 'Inspector-ValidatorsEditor',
+        'Inspector-ValidationErrorMessageEditor': 'Inspector-ValidationErrorMessageEditor',
+
         inspectorFinishers: 'inspectorFinishers',
         inspectorValidators: 'inspectorValidators',
         propertyGridEditorAddRow: 'addRow',
@@ -274,6 +276,14 @@ define(['jquery',
             collectionName
           );
           break;
+        case 'Inspector-ValidationErrorMessageEditor':
+            renderValidationErrorMessageEditor(
+                editorConfiguration,
+                editorHtml,
+                collectionElementIdentifier,
+                collectionName
+            );
+            break;
         case 'Inspector-RemoveElementEditor':
           renderRemoveElementEditor(
             editorConfiguration,
@@ -599,6 +609,97 @@ define(['jquery',
       }
     };
 
+    /**
+     * @private
+     *
+     * @param object
+     * @param object
+     * @return null|string
+     * @throws 1489932939
+     * @throws 1489932940
+     */
+    function _getFirstAvailableValidationErrorMessage(errorCodes, propertyData) {
+      assert(
+        'array' === $.type(errorCodes),
+        'Invalid configuration "errorCodes"',
+        1489932939
+      );
+      assert(
+        'array' === $.type(propertyData),
+        'Invalid configuration "propertyData"',
+        1489932940
+      );
+
+      for (var i = 0, len1 = errorCodes.length; i < len1; ++i) {
+        for (var j = 0, len2 = propertyData.length; j < len2; ++j) {
+          if (parseInt(errorCodes[i]) === parseInt(propertyData[j]['code'])) {
+            if (getUtility().isNonEmptyString(propertyData[j]['message'])) {
+              return propertyData[j]['message'];
+            }
+          }
+        }
+      }
+
+      return null;
+    };
+
+    /**
+     * @private
+     *
+     * @param object
+     * @param object
+     * @param string
+     * @return object
+     * @throws 1489932942
+     */
+    function _renewValidationErrorMessages(errorCodes, propertyData, value) {
+      var errorCodeSubset;
+
+      assert(
+        'array' === $.type(propertyData),
+        'Invalid configuration "propertyData"',
+        1489932942
+      );
+
+      if (
+        !getUtility().isUndefinedOrNull(errorCodes)
+        && 'array' === $.type(errorCodes)
+      ) {
+        errorCodeSubset = [];
+        for (var i = 0, len1 = errorCodes.length; i < len1; ++i) {
+          var errorCodeFound = false;
+
+          for (var j = 0, len2 = propertyData.length; j < len2; ++j) {
+            if (parseInt(errorCodes[i]) === parseInt(propertyData[j]['code'])) {
+              errorCodeFound = true;
+              if (getUtility().isNonEmptyString(value)) {
+                // error code exists and should be updated because message is not empty
+                propertyData[j]['message'] = value;
+              } else {
+                // error code exists but should be removed because message is empty
+                propertyData.splice(j, 1);
+                --len2;
+              }
+            }
+          }
+
+          if (!errorCodeFound) {
+            // add new codes because message is not empty
+            if (getUtility().isNonEmptyString(value)) {
+              errorCodeSubset.push({
+                code: errorCodes[i],
+                message: value
+              });
+            }
+          }
+        }
+
+        propertyData = propertyData.concat(errorCodeSubset);
+      }
+
+      return propertyData;
+    };
+
     /* *************************************************************
      * Public Methodes
      * ************************************************************/
@@ -1150,6 +1251,85 @@ define(['jquery',
      * @param string collectionElementIdentifier
      * @param string collectionName
      * @return void
+     * @throws 1489874120
+     * @throws 1489874121
+     * @throws 1489874122
+     * @throws 1489874123
+     */
+    function renderValidationErrorMessageEditor(editorConfiguration, editorHtml, collectionElementIdentifier, collectionName) {
+      var propertyData, propertyPath, validationErrorMessage;
+      assert(
+        'object' === $.type(editorConfiguration),
+        'Invalid parameter "editorConfiguration"',
+        1489874121
+      );
+      assert(
+       'object' === $.type(editorHtml),
+       'Invalid parameter "editorHtml"',
+       1489874122
+      );
+      assert(
+        getUtility().isNonEmptyString(editorConfiguration['label']),
+        'Invalid configuration "label"',
+        1489874123
+      );
+      assert(
+        getUtility().isNonEmptyString(editorConfiguration['propertyPath']),
+        'Invalid configuration "propertyPath"',
+        1489874124
+      );
+
+      getHelper()
+        .getTemplatePropertyDomElement('label', editorHtml)
+        .append(editorConfiguration['label']);
+      if (getUtility().isNonEmptyString(editorConfiguration['fieldExplanationText'])) {
+        getHelper()
+          .getTemplatePropertyDomElement('fieldExplanationText', editorHtml)
+          .text(editorConfiguration['fieldExplanationText']);
+      } else {
+        getHelper()
+          .getTemplatePropertyDomElement('fieldExplanationText', editorHtml)
+          .remove();
+      }
+
+      propertyPath = getFormEditorApp().buildPropertyPath(
+        editorConfiguration['propertyPath']
+      );
+
+      propertyData = getCurrentlySelectedFormElement().get(propertyPath);
+
+      if (
+        !getUtility().isUndefinedOrNull(propertyData)
+        && 'array' === $.type(propertyData)
+      ) {
+        validationErrorMessage = _getFirstAvailableValidationErrorMessage(editorConfiguration['errorCodes'], propertyData);
+
+        if (!getUtility().isUndefinedOrNull(validationErrorMessage)) {
+          getHelper().getTemplatePropertyDomElement('propertyPath', editorHtml).val(validationErrorMessage);
+        }
+      }
+
+      getHelper().getTemplatePropertyDomElement('propertyPath', editorHtml).on('keyup paste', function() {
+        propertyData = getCurrentlySelectedFormElement().get(propertyPath);
+        if (getUtility().isUndefinedOrNull(propertyData)) {
+          propertyData = [];
+        }
+        getCurrentlySelectedFormElement().set(propertyPath, _renewValidationErrorMessages(
+          editorConfiguration['errorCodes'],
+          propertyData,
+          $(this).val()
+        ));
+      });
+    };
+
+    /**
+     * @public
+     *
+     * @param object editorConfiguration
+     * @param object editorHtml
+     * @param string collectionElementIdentifier
+     * @param string collectionName
+     * @return void
      * @throws 1475421048
      * @throws 1475421049
      * @throws 1475421050
@@ -1712,7 +1892,7 @@ define(['jquery',
      * @throws 1475417096
      */
     function renderRequiredValidatorEditor(editorConfiguration, editorHtml, collectionElementIdentifier, collectionName) {
-      var propertyPath, propertyValue, validatorIdentifier;
+      var propertyData, propertyPath, propertyValue, showValidationErrorMessage, validationErrorMessage, validationErrorMessagePropertyPath, validationErrorMessageTemplate, validationErrorMessageTemplateClone, validatorIdentifier;
       assert(
         'object' === $.type(editorConfiguration),
         'Invalid parameter "editorConfiguration"',
@@ -1747,15 +1927,74 @@ define(['jquery',
         propertyValue = '';
       }
 
+      validationErrorMessagePropertyPath = getFormEditorApp()
+        .buildPropertyPath(editorConfiguration['configurationOptions']['validationErrorMessage']['propertyPath']);
+
+      validationErrorMessageTemplate = getHelper()
+        .getTemplatePropertyDomElement('validationErrorMessage', $(editorHtml))
+        .clone();
+
+      getHelper()
+        .getTemplatePropertyDomElement('validationErrorMessage', $(editorHtml))
+        .remove();
+
+      showValidationErrorMessage = function() {
+        validationErrorMessageTemplateClone = $(validationErrorMessageTemplate).clone(true, true);
+        _getEditorWrapperDomElement(editorHtml).after(validationErrorMessageTemplateClone);
+
+        getHelper()
+          .getTemplatePropertyDomElement('validationErrorMessage-label', validationErrorMessageTemplateClone)
+          .append(editorConfiguration['configurationOptions']['validationErrorMessage']['label']);
+
+        getHelper()
+          .getTemplatePropertyDomElement('validationErrorMessage-fieldExplanationText', validationErrorMessageTemplateClone)
+          .append(editorConfiguration['configurationOptions']['validationErrorMessage']['fieldExplanationText']);
+
+        propertyData = getCurrentlySelectedFormElement().get(validationErrorMessagePropertyPath);
+        if (getUtility().isUndefinedOrNull(propertyData)) {
+          propertyData = [];
+        }
+
+        validationErrorMessage = _getFirstAvailableValidationErrorMessage(
+          editorConfiguration['configurationOptions']['validationErrorMessage']['errorCodes'],
+          propertyData
+        );
+        if (!getUtility().isUndefinedOrNull(validationErrorMessage)) {
+          getHelper()
+            .getTemplatePropertyDomElement('validationErrorMessage-propertyPath', validationErrorMessageTemplateClone)
+            .val(validationErrorMessage);
+        }
+
+        getHelper().getTemplatePropertyDomElement('validationErrorMessage-propertyPath', validationErrorMessageTemplateClone).on('keyup paste', function() {
+          propertyData = getCurrentlySelectedFormElement().get(validationErrorMessagePropertyPath);
+          if (getUtility().isUndefinedOrNull(propertyData)) {
+            propertyData = [];
+          }
+
+          getCurrentlySelectedFormElement().set(validationErrorMessagePropertyPath, _renewValidationErrorMessages(
+            editorConfiguration['configurationOptions']['validationErrorMessage']['errorCodes'],
+            propertyData,
+            $(this).val()
+          ));
+        });
+      }
+
       if (-1 !== getFormEditorApp().getIndexFromPropertyCollectionElement(validatorIdentifier, 'validators')) {
         $('input[type="checkbox"]', $(editorHtml)).prop('checked', true);
         if (getUtility().isNonEmptyString(propertyPath)) {
           getCurrentlySelectedFormElement().set(propertyPath, propertyValue);
         }
+        showValidationErrorMessage();
       }
 
       $('input[type="checkbox"]', $(editorHtml)).on('change', function() {
+        getHelper().getTemplatePropertyDomElement('validationErrorMessage', $(editorHtml))
+          .off()
+          .empty()
+          .remove();
+
         if ($(this).is(":checked")) {
+          showValidationErrorMessage();
           getPublisherSubscriber().publish(
             'view/inspector/collectionElement/new/selected',
             [validatorIdentifier, 'validators']
@@ -1772,6 +2011,17 @@ define(['jquery',
           if (getUtility().isNonEmptyString(propertyPath)) {
             getCurrentlySelectedFormElement().unset(propertyPath);
           }
+
+          propertyData = getCurrentlySelectedFormElement().get(validationErrorMessagePropertyPath);
+          if (getUtility().isUndefinedOrNull(propertyData)) {
+            propertyData = [];
+          }
+
+          getCurrentlySelectedFormElement().set(validationErrorMessagePropertyPath, _renewValidationErrorMessages(
+            editorConfiguration['configurationOptions']['validationErrorMessage']['errorCodes'],
+            propertyData,
+            ''
+          ));
         }
       });
     };
index 201ef7d..b0f02cf 100644 (file)
@@ -1236,7 +1236,7 @@ define(['jquery',
      * @publish view/collectionElement/removed
      */
     function removePropertyCollectionElement(collectionElementIdentifier, collectionName, formElement, disablePublishersOnSet) {
-      var collectionElementConfiguration;
+      var collectionElementConfiguration, propertyData, propertyPath;
 
       getFormEditorApp().removePropertyCollectionElement(collectionElementIdentifier, collectionName, formElement);
 
@@ -1250,6 +1250,22 @@ define(['jquery',
             for (var j = 0, len2 = collectionElementConfiguration['editors'][i]['additionalElementPropertyPaths'].length; j < len2; ++j) {
               getCurrentlySelectedFormElement().unset(collectionElementConfiguration['editors'][i]['additionalElementPropertyPaths'][j], true);
             }
+          } else if (collectionElementConfiguration['editors'][i]['identifier'] === 'validationErrorMessage') {
+            propertyPath = getFormEditorApp().buildPropertyPath(
+              collectionElementConfiguration['editors'][i]['propertyPath']
+            );
+            propertyData = getCurrentlySelectedFormElement().get(propertyPath);
+            if (!getUtility().isUndefinedOrNull(propertyData)) {
+              for (var j = 0, len2 = collectionElementConfiguration['editors'][i]['errorCodes'].length; j < len2; ++j) {
+                for (var k = 0, len3 = propertyData.length; k < len3; ++k) {
+                  if (parseInt(collectionElementConfiguration['editors'][i]['errorCodes'][j]) === parseInt(propertyData[k]['code'])) {
+                    propertyData.splice(k, 1);
+                    --len3;
+                  }
+                }
+              }
+              getCurrentlySelectedFormElement().set(propertyPath, propertyData);
+            }
           }
         }
       }