From d506efa92d1bcd1f60876727f0bb9f91345971d1 Mon Sep 17 00:00:00 2001
From: Ralf Zimmermann <ralf.zimmermann@tritum.de>
Date: Sun, 26 Mar 2017 12:54:07 +0200
Subject: [PATCH] [TASK] EXT:form - make 'grid rows' independent from 'grid
 containers'

* Create 'grid rows' without 'grid container' wrappers
* Disable 'grid containers' within the form editor by default
  because twitter bootstrap prohibits container nesting

Resolves: #80455
Releases: master
Change-Id: I3997943858ac3b235094b765697f724cb1e4c95d
Reviewed-on: https://review.typo3.org/52166
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Susanne Moog <susanne.moog@typo3.org>
Tested-by: Susanne Moog <susanne.moog@typo3.org>
Reviewed-by: Frank Naegler <frank.naegler@typo3.org>
Tested-by: Frank Naegler <frank.naegler@typo3.org>
---
 .../Domain/Model/FormElements/GridRow.php     | 28 ----------
 ...ColumnClassAutoConfigurationViewHelper.php | 19 ++++---
 .../form/Configuration/Yaml/BaseSetup.yaml    | 12 +++++
 .../Configuration/Yaml/FormEditorSetup.yaml   |  2 -
 .../Public/JavaScript/Backend/FormEditor.js   | 13 +++++
 .../JavaScript/Backend/FormEditor/Core.js     | 24 +++++++++
 .../Backend/FormEditor/StageComponent.js      | 52 +++++++++++--------
 .../Backend/FormEditor/TreeComponent.js       | 12 ++---
 .../Backend/FormEditor/ViewModel.js           |  5 +-
 9 files changed, 96 insertions(+), 71 deletions(-)

diff --git a/typo3/sysext/form/Classes/Domain/Model/FormElements/GridRow.php b/typo3/sysext/form/Classes/Domain/Model/FormElements/GridRow.php
index 06f03b32fa52..5be61bd435c1 100644
--- a/typo3/sysext/form/Classes/Domain/Model/FormElements/GridRow.php
+++ b/typo3/sysext/form/Classes/Domain/Model/FormElements/GridRow.php
@@ -30,24 +30,6 @@ use TYPO3\CMS\Form\Domain\Exception\TypeDefinitionNotValidException;
 class GridRow extends Section implements GridRowInterface
 {
 
-    /**
-     * Register this element at the parent form, if there is a connection to the parent form.
-     *
-     * @return void
-     * @throws TypeDefinitionNotValidException
-     * @internal
-     */
-    public function registerInFormIfPossible()
-    {
-        if (!$this->getParentRenderable() instanceof GridContainerInterface) {
-            throw new TypeDefinitionNotValidException(
-                sprintf('Grid rows ("%s") only allowed within grid containers.', $this->getIdentifier()),
-                1489413805
-            );
-        }
-        parent::registerInFormIfPossible();
-    }
-
     /**
      * Add a new form element at the end of the grid row
      *
@@ -63,11 +45,6 @@ class GridRow extends Section implements GridRowInterface
                 sprintf('Grid containers ("%s") within grid rows ("%s") are not allowed.', $formElement->getIdentifier(), $this->getIdentifier()),
                 1489413379
             );
-        } elseif ($formElement instanceof GridRowInterface) {
-            throw new TypeDefinitionNotValidException(
-                sprintf('Grid rows ("%s") within grid rows ("%s") are not allowed.', $formElement->getIdentifier(), $this->getIdentifier()),
-                1489413696
-            );
         }
 
         $this->addRenderable($formElement);
@@ -91,11 +68,6 @@ class GridRow extends Section implements GridRowInterface
                 sprintf('Grid containers ("%s") within grid rows ("%s") are not allowed.', $element->getIdentifier(), $this->getIdentifier()),
                 1489413538
             );
-        } elseif ($element instanceof GridRowInterface) {
-            throw new TypeDefinitionNotValidException(
-                sprintf('Grid rows ("%s") within grid rows ("%s") are not allowed.', $element->getIdentifier(), $this->getIdentifier()),
-                1489413697
-            );
         }
 
         return $element;
diff --git a/typo3/sysext/form/Classes/ViewHelpers/GridColumnClassAutoConfigurationViewHelper.php b/typo3/sysext/form/Classes/ViewHelpers/GridColumnClassAutoConfigurationViewHelper.php
index e70c5f3bcb1d..93bf6e28afae 100644
--- a/typo3/sysext/form/Classes/ViewHelpers/GridColumnClassAutoConfigurationViewHelper.php
+++ b/typo3/sysext/form/Classes/ViewHelpers/GridColumnClassAutoConfigurationViewHelper.php
@@ -16,6 +16,7 @@ namespace TYPO3\CMS\Form\ViewHelpers;
  */
 
 use TYPO3\CMS\Fluid\Core\ViewHelper\AbstractViewHelper;
+use TYPO3\CMS\Form\Domain\Model\FormElements\GridContainerInterface;
 use TYPO3\CMS\Form\Domain\Model\Renderable\RootRenderableInterface;
 use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
 use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
@@ -63,23 +64,27 @@ class GridColumnClassAutoConfigurationViewHelper extends AbstractViewHelper
         $gridContainerElement = $gridRowElement->getParentRenderable();
         $gridRowEChildElements = $gridRowElement->getElementsRecursively();
 
-        $gridContainerViewPortConfiguration = $gridContainerElement->getProperties()['gridColumnClassAutoConfiguration'];
-        if (empty($gridContainerViewPortConfiguration)) {
-            return '';
+        if ($gridContainerElement instanceof GridContainerInterface) {
+            $gridViewPortConfiguration = $gridContainerElement->getProperties()['gridColumnClassAutoConfiguration'];
+        } else {
+            $gridViewPortConfiguration = $gridRowElement->getProperties()['gridColumnClassAutoConfiguration'];
         }
 
-        $gridSize = (int)$gridContainerViewPortConfiguration['gridSize'];
+        if (empty($gridViewPortConfiguration)) {
+            return '';
+        }
+        $gridSize = (int)$gridViewPortConfiguration['gridSize'];
 
         $columnsToCalculate = [];
         $usedColumns = [];
         foreach ($gridRowEChildElements as $childElement) {
             if (empty($childElement->getProperties()['gridColumnClassAutoConfiguration'])) {
-                foreach ($gridContainerViewPortConfiguration['viewPorts'] as $viewPortName => $configuration) {
+                foreach ($gridViewPortConfiguration['viewPorts'] as $viewPortName => $configuration) {
                     $columnsToCalculate[$viewPortName]['elements']++;
                 }
             } else {
                 $gridColumnViewPortConfiguration = $childElement->getProperties()['gridColumnClassAutoConfiguration'];
-                foreach ($gridContainerViewPortConfiguration['viewPorts'] as $viewPortName => $configuration) {
+                foreach ($gridViewPortConfiguration['viewPorts'] as $viewPortName => $configuration) {
                     $configuration = $gridColumnViewPortConfiguration['viewPorts'][$viewPortName];
                     if (
                         isset($configuration['numbersOfColumnsToUse'])
@@ -100,7 +105,7 @@ class GridColumnClassAutoConfigurationViewHelper extends AbstractViewHelper
         }
 
         $classes = [];
-        foreach ($gridContainerViewPortConfiguration['viewPorts'] as $viewPortName => $configuration) {
+        foreach ($gridViewPortConfiguration['viewPorts'] as $viewPortName => $configuration) {
             if (isset($usedColumns[$viewPortName]['concreteNumbersOfColumnsToUse'])) {
                 $numbersOfColumnsToUse = $usedColumns[$viewPortName]['concreteNumbersOfColumnsToUse'];
             } else {
diff --git a/typo3/sysext/form/Configuration/Yaml/BaseSetup.yaml b/typo3/sysext/form/Configuration/Yaml/BaseSetup.yaml
index a8b4fd30af2f..a0a500b2c30b 100644
--- a/typo3/sysext/form/Configuration/Yaml/BaseSetup.yaml
+++ b/typo3/sysext/form/Configuration/Yaml/BaseSetup.yaml
@@ -77,6 +77,7 @@ TYPO3:
                 _isGridContainerFormElement: true
               properties:
                 elementClassAttribute: 'container'
+                # overrules 'GridRow.properties.gridColumnClassAutoConfiguration'
                 gridColumnClassAutoConfiguration:
                   gridSize: 12
                   viewPorts:
@@ -95,6 +96,17 @@ TYPO3:
               implementationClassName: 'TYPO3\CMS\Form\Domain\Model\FormElements\GridRow'
               properties:
                 elementClassAttribute: 'row'
+                gridColumnClassAutoConfiguration:
+                  gridSize: 12
+                  viewPorts:
+                    xs:
+                      classPattern: 'col-xs-{@numbersOfColumnsToUse}'
+                    sm:
+                      classPattern: 'col-sm-{@numbersOfColumnsToUse}'
+                    md:
+                      classPattern: 'col-md-{@numbersOfColumnsToUse}'
+                    lg:
+                      classPattern: 'col-lg-{@numbersOfColumnsToUse}'
               renderingOptions:
                 _isCompositeFormElement: true
                 _isGridRowFormElement: true
diff --git a/typo3/sysext/form/Configuration/Yaml/FormEditorSetup.yaml b/typo3/sysext/form/Configuration/Yaml/FormEditorSetup.yaml
index 8716b328bd69..142d42045521 100644
--- a/typo3/sysext/form/Configuration/Yaml/FormEditorSetup.yaml
+++ b/typo3/sysext/form/Configuration/Yaml/FormEditorSetup.yaml
@@ -304,8 +304,6 @@ TYPO3:
             GridContainer:
               formEditor:
                 label: 'formEditor.elements.GridContainer.label'
-                group: container
-                groupSorting: 200
                 _isCompositeFormElement: true
                 _isGridContainerFormElement: true
                 iconIdentifier: 't3-form-icon-gridcontainer'
diff --git a/typo3/sysext/form/Resources/Public/JavaScript/Backend/FormEditor.js b/typo3/sysext/form/Resources/Public/JavaScript/Backend/FormEditor.js
index 14e42a55d5ca..9f620eb8fe67 100644
--- a/typo3/sysext/form/Resources/Public/JavaScript/Backend/FormEditor.js
+++ b/typo3/sysext/form/Resources/Public/JavaScript/Backend/FormEditor.js
@@ -812,6 +812,18 @@ define(['jquery',
                 );
             };
 
+            /**
+             * @public
+             *
+             * @param object formElement
+             * @return object|null
+             */
+            function findEnclosingGridRowFormElement(formElement) {
+                return _getRepository().findEnclosingGridRowFormElement(
+                    _getRepository().findFormElement(formElement)
+                );
+            };
+
             /**
              * @public
              *
@@ -1071,6 +1083,7 @@ define(['jquery',
                 getLastTopLevelElementOnCurrentPage: getLastTopLevelElementOnCurrentPage,
                 findEnclosingCompositeFormElementWhichIsNotOnTopLevel: findEnclosingCompositeFormElementWhichIsNotOnTopLevel,
                 findEnclosingGridContainerFormElement: findEnclosingGridContainerFormElement,
+                findEnclosingGridRowFormElement: findEnclosingGridRowFormElement,
                 isRootFormElementSelected: isRootFormElementSelected,
                 getLastFormElementWithinParentFormElement: getLastFormElementWithinParentFormElement,
                 getNonCompositeNonToplevelFormElements: getNonCompositeNonToplevelFormElements,
diff --git a/typo3/sysext/form/Resources/Public/JavaScript/Backend/FormEditor/Core.js b/typo3/sysext/form/Resources/Public/JavaScript/Backend/FormEditor/Core.js
index d1824db06565..9be812ad9890 100644
--- a/typo3/sysext/form/Resources/Public/JavaScript/Backend/FormEditor/Core.js
+++ b/typo3/sysext/form/Resources/Public/JavaScript/Backend/FormEditor/Core.js
@@ -1353,6 +1353,29 @@ define(['jquery'], function($) {
                 return formElement;
             };
 
+            /**
+             * @param object formElement
+             * @return object|null
+             * @throws 1490520271
+             */
+            function findEnclosingGridRowFormElement(formElement) {
+                var formElementTypeDefinition;
+                utility().assert('object' === $.type(formElement), 'Invalid parameter "formElement"', 1490520271);
+
+                formElementTypeDefinition = repository().getFormEditorDefinition('formElements', formElement.get('type'));
+                while (!formElementTypeDefinition['_isGridRowFormElement']) {
+                    if (formElementTypeDefinition['_isTopLevelFormElement']) {
+                        return null;
+                    }
+                    formElement = formElement.get('__parentRenderable');
+                    formElementTypeDefinition = repository().getFormEditorDefinition('formElements', formElement.get('type'));
+                }
+                if (formElementTypeDefinition['_isTopLevelFormElement']) {
+                    return null;
+                }
+                return formElement;
+            };
+
             /**
              * @param object formElement
              * @return object|null
@@ -1706,6 +1729,7 @@ define(['jquery'], function($) {
                 findEnclosingCompositeFormElementWhichIsNotOnTopLevel: findEnclosingCompositeFormElementWhichIsNotOnTopLevel,
                 findEnclosingCompositeFormElementWhichIsOnTopLevel: findEnclosingCompositeFormElementWhichIsOnTopLevel,
                 findEnclosingGridContainerFormElement: findEnclosingGridContainerFormElement,
+                findEnclosingGridRowFormElement: findEnclosingGridRowFormElement,
                 getIndexForEnclosingCompositeFormElementWhichIsOnTopLevelForFormElement: getIndexForEnclosingCompositeFormElementWhichIsOnTopLevelForFormElement,
                 getNonCompositeNonToplevelFormElements: getNonCompositeNonToplevelFormElements,
 
diff --git a/typo3/sysext/form/Resources/Public/JavaScript/Backend/FormEditor/StageComponent.js b/typo3/sysext/form/Resources/Public/JavaScript/Backend/FormEditor/StageComponent.js
index ff2a1174374a..69f7478e43ad 100644
--- a/typo3/sysext/form/Resources/Public/JavaScript/Backend/FormEditor/StageComponent.js
+++ b/typo3/sysext/form/Resources/Public/JavaScript/Backend/FormEditor/StageComponent.js
@@ -372,14 +372,10 @@ define(['jquery',
 
                     if (
                         formElementTypeDefinition['_isGridContainerFormElement']
-                        && getFormEditorApp().findEnclosingGridContainerFormElement(targetFormElementIdentifierPath)
-                    ) {
-                        return false;
-                    }
-
-                    if (
-                        formElementTypeDefinition['_isGridRowFormElement']
-                        && !targetFormElementTypeDefinition['_isGridContainerFormElement']
+                        && (
+                            getFormEditorApp().findEnclosingGridContainerFormElement(targetFormElementIdentifierPath)
+                            || getFormEditorApp().findEnclosingGridRowFormElement(targetFormElementIdentifierPath)
+                        )
                     ) {
                         return false;
                     }
@@ -701,10 +697,21 @@ define(['jquery',
                     disableElementTypes = [];
                     onlyEnableElementTypes = [];
                     if (formElementTypeDefinition['_isGridRowFormElement']) {
-                        onlyEnableElementTypes = ['GridRow'];
-                        disableElementTypes = [];
+                        if (getFormEditorApp().findEnclosingGridContainerFormElement(getCurrentlySelectedFormElement())) {
+                            onlyEnableElementTypes = ['GridRow'];
+                        } else if (getFormEditorApp().findEnclosingGridRowFormElement(getCurrentlySelectedFormElement().get('__parentRenderable'))) {
+                            disableElementTypes = ['GridContainer'];
+                        }
                     } else {
-                        disableElementTypes = ['GridRow'];
+                        if (
+                            !formElementTypeDefinition['_isGridContainerFormElement']
+                            && (
+                                getFormEditorApp().findEnclosingGridContainerFormElement(getCurrentlySelectedFormElement())
+                                || getFormEditorApp().findEnclosingGridRowFormElement(getCurrentlySelectedFormElement())
+                            )
+                        ) {
+                            disableElementTypes = ['GridContainer'];
+                        }
                     }
 
                     getPublisherSubscriber().publish('view/stage/abstract/elementToolbar/button/newElement/clicked', [
@@ -719,14 +726,18 @@ define(['jquery',
                 $(getHelper().getDomElementDataIdentifierSelector('abstractViewToolbarNewElementSplitButtonInside'), template).on('click', function(e) {
                     var disableElementTypes, onlyEnableElementTypes;
 
-                    disableElementTypes = ['GridRow'];
+                    disableElementTypes = [];
                     onlyEnableElementTypes = [];
                     if (formElementTypeDefinition['_isGridContainerFormElement']) {
                         onlyEnableElementTypes = ['GridRow'];
-                        disableElementTypes = [];
-                    } else if (formElementTypeDefinition['_isGridRowFormElement']) {
-                        onlyEnableElementTypes = [];
-                        disableElementTypes = ['GridContainer', 'GridRow'];
+                    } else if (
+                        formElementTypeDefinition['_isGridRowFormElement']
+                        || (
+                            getFormEditorApp().findEnclosingGridContainerFormElement(getCurrentlySelectedFormElement())
+                            || getFormEditorApp().findEnclosingGridRowFormElement(getCurrentlySelectedFormElement())
+                        )
+                    ) {
+                        disableElementTypes = ['GridContainer'];
                     }
 
                     getPublisherSubscriber().publish('view/stage/abstract/elementToolbar/button/newElement/clicked', [
@@ -742,14 +753,11 @@ define(['jquery',
                 getViewModel().hideComponent($(getHelper().getDomElementDataIdentifierSelector('abstractViewToolbarNewElementSplitButton'), template));
 
                 $(getHelper().getDomElementDataIdentifierSelector('abstractViewToolbarNewElement'), template).on('click', function(e) {
-                    var disableElementTypes, onlyEnableElementTypes;
+                    var disableElementTypes;
 
                     disableElementTypes = [];
-                    onlyEnableElementTypes = [];
-                    if (getFormEditorApp().findEnclosingGridContainerFormElement(formElement)) {
-                        disableElementTypes = ['GridContainer', 'GridRow'];
-                    } else {
-                        disableElementTypes = ['GridRow'];
+                    if (getFormEditorApp().findEnclosingGridRowFormElement(formElement)) {
+                        disableElementTypes = ['GridContainer'];
                     }
 
                     getPublisherSubscriber().publish(
diff --git a/typo3/sysext/form/Resources/Public/JavaScript/Backend/FormEditor/TreeComponent.js b/typo3/sysext/form/Resources/Public/JavaScript/Backend/FormEditor/TreeComponent.js
index f9c0ac090af6..03a45127fc95 100644
--- a/typo3/sysext/form/Resources/Public/JavaScript/Backend/FormEditor/TreeComponent.js
+++ b/typo3/sysext/form/Resources/Public/JavaScript/Backend/FormEditor/TreeComponent.js
@@ -325,14 +325,10 @@ define(['jquery',
 
                     if (
                         formElementTypeDefinition['_isGridContainerFormElement']
-                        && getFormEditorApp().findEnclosingGridContainerFormElement(targetFormElementIdentifierPath)
-                    ) {
-                        return false;
-                    }
-
-                    if (
-                        formElementTypeDefinition['_isGridRowFormElement']
-                        && !targetFormElementTypeDefinition['_isGridContainerFormElement']
+                        && (
+                            getFormEditorApp().findEnclosingGridContainerFormElement(targetFormElementIdentifierPath)
+                            || getFormEditorApp().findEnclosingGridRowFormElement(targetFormElementIdentifierPath)
+                        )
                     ) {
                         return false;
                     }
diff --git a/typo3/sysext/form/Resources/Public/JavaScript/Backend/FormEditor/ViewModel.js b/typo3/sysext/form/Resources/Public/JavaScript/Backend/FormEditor/ViewModel.js
index fe699912bcd7..cc4813a00c69 100644
--- a/typo3/sysext/form/Resources/Public/JavaScript/Backend/FormEditor/ViewModel.js
+++ b/typo3/sysext/form/Resources/Public/JavaScript/Backend/FormEditor/ViewModel.js
@@ -404,10 +404,7 @@ define(['jquery',
             $(getHelper().getDomElementDataIdentifierSelector('buttonStageNewElementBottom')).on('click', function(e) {
                 getPublisherSubscriber().publish(
                     'view/stage/abstract/button/newElement/clicked', [
-                        'view/insertElements/perform/bottom',
-                        {
-                            disableElementTypes: ['GridRow']
-                        }
+                        'view/insertElements/perform/bottom'
                     ]
                 );
             });
-- 
GitLab