[BUGFIX] Fix selectTree in FlexForms 38/49638/5
authorTymoteusz Motylewski <t.motylewski@gmail.com>
Tue, 30 Aug 2016 13:27:08 +0000 (15:27 +0200)
committerGeorg Ringer <georg.ringer@gmail.com>
Tue, 30 Aug 2016 13:32:44 +0000 (15:32 +0200)
Now SVG tree loads correctly in FlexForms.
It also fixes and issue with tree nodes displaying &amp; instead of &
Additionally acceptance test for category tree is fixed.
Add pointer cursor for tree item label.
Add min height for a tree (used when you have just a few categories)
Check for icon being an object before treating it as such.

Resolves: #77681
Releases: master
Change-Id: I33ec8c50419063ca9ef55d1cf804bde617e32554
Reviewed-on: https://review.typo3.org/49638
Reviewed-by: Wouter Wolters <typo3@wouterwolters.nl>
Tested-by: Wouter Wolters <typo3@wouterwolters.nl>
Tested-by: Bamboo TYPO3com <info@typo3.com>
Reviewed-by: Georg Ringer <georg.ringer@gmail.com>
Tested-by: Georg Ringer <georg.ringer@gmail.com>
Build/Resources/Public/Less/Component/svgTree.less
typo3/sysext/backend/Classes/Controller/SelectTreeController.php
typo3/sysext/backend/Classes/Form/Container/FlexFormElementContainer.php
typo3/sysext/backend/Classes/Form/Element/SelectTreeElement.php
typo3/sysext/backend/Classes/Tree/Renderer/ExtJsJsonTreeRenderer.php
typo3/sysext/backend/Classes/Tree/TreeRepresentationNode.php
typo3/sysext/backend/Resources/Public/Css/backend.css
typo3/sysext/backend/Resources/Public/JavaScript/FormEngine/Element/SelectTreeElement.js
typo3/sysext/backend/Resources/Public/JavaScript/FormEngine/Element/SvgTree.js
typo3/sysext/core/Tests/Acceptance/Backend/Formhandler/CategoryTreeCest.php

index ba7848e..774efa7 100644 (file)
@@ -8,10 +8,9 @@
     stroke-width: 1;
   }
 
-  .node .chevron {
-    cursor: pointer;
-  }
-  .tree-check {
-    cursor: pointer;
+  .node {
+    .chevron, text, .tree-check {
+      cursor: pointer;
+    }
   }
 }
\ No newline at end of file
index 4dac075..01a25f6 100644 (file)
@@ -46,15 +46,49 @@ class SelectTreeController
             'vanillaUid' => (int)$request->getQueryParams()['uid'],
             'command' => $request->getQueryParams()['command'],
         ];
+
         $fieldName = $request->getQueryParams()['field'];
         $formData = $formDataCompiler->compile($formDataCompilerInput);
-        $treeData = $formData['processedTca']['columns'][$fieldName]['config']['treeData'];
+
+        if ($formData['processedTca']['columns'][$fieldName]['config']['type'] === 'flex') {
+            $flexFormFieldName = $request->getQueryParams()['flex_form_field_name'];
+            $value = $this->searchForFieldInFlexStructure($formData['processedTca']['columns'][$fieldName]['config'], $flexFormFieldName);
+            $treeData = $value['config']['treeData'];
+        } else {
+            $treeData = $formData['processedTca']['columns'][$fieldName]['config']['treeData'];
+        }
+
         $json = json_encode($treeData['items']);
         $response->getBody()->write($json);
         return $response;
     }
 
     /**
+     * A workaround for flexforms - there is no easy way to get flex field by key, so we need to search for it
+     *
+     * @todo remove me once flexforms are refactored
+     *
+     * @param array $array
+     * @param string $needle
+     * @return array
+     */
+    protected function searchForFieldInFlexStructure(array $array, $needle)
+    {
+        $needle = trim($needle);
+        $iterator  = new \RecursiveArrayIterator($array);
+        $recursive = new \RecursiveIteratorIterator(
+            $iterator,
+            \RecursiveIteratorIterator::SELF_FIRST
+        );
+        foreach ($recursive as $key => $value) {
+            if ($key === $needle) {
+                return $value;
+            }
+        }
+        return [];
+    }
+
+    /**
      * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
      */
     protected function getBackendUser()
index c6df908..d141f61 100644 (file)
@@ -79,6 +79,8 @@ class FlexFormElementContainer extends AbstractContainer
                 // Set up options for single element
                 $fakeParameterArray = [
                     'fieldConf' => [
+                        // @todo review this field during flex refactoring
+                        'flexFormFieldName' => $flexFormFieldName,
                         'label' => $languageService->sL(trim($flexFormFieldArray['label'])),
                         'config' => $flexFormFieldArray['config'],
                         'children' => $flexFormFieldArray['children'],
index 71d7176..053356b 100644 (file)
@@ -26,11 +26,27 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
 class SelectTreeElement extends AbstractFormElement
 {
     /**
-     * Default height of the tree in pixels.
+     * Default number of tree nodes to show (determines tree height)
+     * when no ['config']['size'] is set
      *
-     * @const
+     * @var int
      */
-    const DEFAULT_HEIGHT = 280;
+    protected $itemsToShow = 280;
+
+    /**
+     * Number of items to show at last
+     * e.g. when you have only 2 items in a tree
+     *
+     * @var int
+     */
+    protected $minItemsToShow = 10;
+
+    /**
+     * Pixel height of a single tree node
+     *
+     * @var int
+     */
+    protected $itemHeight = 20;
 
     /**
      * Render tree widget
@@ -53,21 +69,24 @@ class SelectTreeElement extends AbstractFormElement
         $expanded = !empty($appearance['expandAll']);
         $showHeader = !empty($appearance['showHeader']);
         if (isset($config['size']) && (int)$config['size'] > 0) {
-            $height = min(count($config['items']), (int)$config['size']) * 20;
+            $height = min(max(count($config['items']), $this->minItemsToShow), (int)$config['size']);
         } else {
-            $height = static::DEFAULT_HEIGHT;
+            $height = $this->itemsToShow;
         }
+        $heightInPx = $height * $this->itemHeight;
 
         $treeWrapperId = 'tree_' . $formElementId;
 
+        $flexFormFieldName = !empty($parameterArray['fieldConf']['flexFormFieldName']) ? htmlspecialchars($parameterArray['fieldConf']['flexFormFieldName']) : '';
         $html = [];
         $html[] = '<div class="typo3-tceforms-tree">';
         $html[] = '    <input class="treeRecord" type="hidden"';
-        $html[] = '           ' . $this->getValidationDataAsDataAttribute($parameterArray['fieldConf']['config']);
+        $html[] = '           ' . $this->getValidationDataAsDataAttribute($config);
         $html[] = '           data-formengine-input-name="' . htmlspecialchars($parameterArray['itemFormElName']) . '"';
         $html[] = '           data-relatedfieldname="' . htmlspecialchars($parameterArray['itemFormElName']) . '"';
         $html[] = '           data-table="' . htmlspecialchars($this->data['tableName']) . '"';
         $html[] = '           data-field="' . htmlspecialchars($this->data['fieldName']) . '"';
+        $html[] = '           data-flex-form-field-name="' . $flexFormFieldName . '"';
         $html[] = '           data-uid="' . (int)$this->data['vanillaUid'] . '"';
         $html[] = '           data-command="' . htmlspecialchars($this->data['command']) . '"';
         $html[] = '           data-read-only="' . $readOnly . '"';
@@ -79,7 +98,7 @@ class SelectTreeElement extends AbstractFormElement
         $html[] = '           value="' . htmlspecialchars(implode(',', $config['treeData']['selectedNodes'])) . '"';
         $html[] = '    />';
         $html[] = '</div>';
-        $html[] = '<div id="' . $treeWrapperId . '" class="svg-tree-wrapper" style="height: ' . $height . 'px;"></div>';
+        $html[] = '<div id="' . $treeWrapperId . '" class="svg-tree-wrapper" style="height: ' . $heightInPx . 'px;"></div>';
         $html[] = '<script type="text/javascript">var ' . $treeWrapperId . ' = ' . $this->getTreeOnChangeJs() . '</script>';
 
         $resultArray['html'] = implode(LF, $html);
index 0202b44..553f295 100644 (file)
@@ -54,22 +54,28 @@ class ExtJsJsonTreeRenderer extends \TYPO3\CMS\Backend\Tree\Renderer\AbstractTre
      */
     protected function getNodeArray(\TYPO3\CMS\Backend\Tree\TreeRepresentationNode $node)
     {
-        $overlayIcon  = '';
-        if (is_object($node->getIcon()->getOverlayIcon())) {
-            $overlayIcon = $node->getIcon()->getOverlayIcon()->getMarkup(SvgIconProvider::MARKUP_IDENTIFIER_INLINE);
+        $overlayIconMarkup  = '';
+        if (is_object($node->getIcon())) {
+            $iconMarkup = $node->getIcon()->getMarkup(SvgIconProvider::MARKUP_IDENTIFIER_INLINE);
+            if (is_object($node->getIcon()->getOverlayIcon())) {
+                $overlayIcon = $node->getIcon()->getOverlayIcon()->getMarkup(SvgIconProvider::MARKUP_IDENTIFIER_INLINE);
+            }
+        } else {
+            $iconMarkup = $node->getIcon();
         }
         $nodeArray = array(
-            'iconTag' => $node->getIcon()->render(),
+            'iconTag' => $iconMarkup,
             'text' => htmlspecialchars($node->getLabel()),
             'leaf' => !$node->hasChildNodes(),
             'id' => htmlspecialchars($node->getId()),
             'uid' => htmlspecialchars($node->getId()),
 
             //svgtree
-            'icon' => $node->getIcon()->getMarkup(SvgIconProvider::MARKUP_IDENTIFIER_INLINE),
-            'overlayIcon' => $overlayIcon,
+            'icon' => $iconMarkup,
+            'overlayIcon' => $overlayIconMarkup,
             'identifier' => htmlspecialchars($node->getId()),
-            'name' => htmlspecialchars($node->getLabel()),
+            //no need for htmlspecialhars here as d3 is using 'textContent' property of the HTML DOM node
+            'name' => $node->getLabel(),
         );
 
         return $nodeArray;
index fc4ae29..1a03e2d 100644 (file)
@@ -44,7 +44,7 @@ class TreeRepresentationNode extends \TYPO3\CMS\Backend\Tree\TreeNode
     /**
      * Node Icon
      *
-     * @var string
+     * @var string | Icon
      */
     protected $icon = '';
 
index 3b9326d..39a738f 100644 (file)
@@ -8047,10 +8047,9 @@ button.close {
   stroke: #ddd;
   stroke-width: 1;
 }
-.svg-tree-wrapper .node .chevron {
-  cursor: pointer;
-}
-.svg-tree-wrapper .tree-check {
+.svg-tree-wrapper .node .chevron,
+.svg-tree-wrapper .node text,
+.svg-tree-wrapper .node .tree-check {
   cursor: pointer;
 }
 /*!
index d590b5e..e1fe1c4 100644 (file)
@@ -30,6 +30,7 @@ define(['jquery', 'TYPO3/CMS/Backend/FormEngine/Element/SelectTree'], function (
                 table: treeInput.data('table'),
                 field: treeInput.data('field'),
                 uid: treeInput.data('uid'),
+                flex_form_field_name: treeInput.data('flex-form-field-name'),
                 command: treeInput.data('command')
             };
             var $wrapper = treeInput.parent().siblings('.svg-tree-wrapper');
index 1fd3d24..0786ecc 100644 (file)
@@ -390,7 +390,7 @@ define(['jquery', 'd3'], function ($, d3) {
             var nodeEnter = nodes
                 .enter()
                 .append('g')
-                .attr('class', 'node')
+                .attr('class', this.getNodeClass)
                 .attr('transform', this.getNodeTransform);
 
             // append the chevron element
@@ -454,6 +454,16 @@ define(['jquery', 'd3'], function ($, d3) {
         },
 
         /**
+         * Computes the tree node class
+         *
+         * @param {Node} node
+         * @returns {String}
+         */
+        getNodeClass: function (node) {
+            return 'node identifier-' + node.data.identifier;
+        },
+
+        /**
          * Computes the tree item label based on the data
          *
          * @param {Node} node
index afc7388..5f04404 100644 (file)
@@ -55,11 +55,11 @@ class CategoryTreeCest
         $I->click('#recordlist-sys_category tr[data-uid="7"] a[data-original-title="Edit record"]');
         // Change title and level to root
         $I->fillField('input[data-formengine-input-name="data[sys_category][7][title]"]', 'level-1-4');
-        $I->click('div[ext\:tree-node-id="7"]');
-        $I->click('div[ext\:tree-node-id="3"]');
+        $I->click('.identifier-7 text');
+        $I->click('.identifier-3 text');
         $I->click('button[name="_savedok"]');
         // Wait for tree and check if isset level-1-4
-        $I->waitForElement('.x-panel.x-tree');
-        $I->see('level-1-4', '.x-panel.x-tree');
+        $I->waitForElement('.svg-tree-wrapper svg');
+        $I->see('level-1-4', '.svg-tree-wrapper svg .node text');
     }
 }