[FEATURE] ViewHelpers f:form.select.option and f:form.select.optgroup 81/48281/10
authorClaus Due <claus@namelesscoder.net>
Tue, 24 May 2016 13:56:56 +0000 (15:56 +0200)
committerSusanne Moog <susanne.moog@typo3.org>
Fri, 2 Dec 2016 13:49:23 +0000 (14:49 +0100)
Allows manually defining all options and optgroups for
the f:form.select parent field as tag contents of the
select field. The added ViewHelpers are TagBasedViewHelpers
which means they support all standard HTML attributes.

Note that while tag content rendering is now supported,
it is *STILL* not possible to create ``<option>`` tags
manually - you *HAVE* to use the form fields!

Change-Id: I5f122f746a5309671ad0faf2feccda2d29fef4c7
Releases: master
Resolves: #29399
Reviewed-on: https://review.typo3.org/48281
Reviewed-by: Jan Helke <typo3@helke.de>
Tested-by: Jan Helke <typo3@helke.de>
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Susanne Moog <susanne.moog@typo3.org>
Tested-by: Susanne Moog <susanne.moog@typo3.org>
typo3/sysext/core/Documentation/Changelog/master/Feature-29399-OptionViewHelperAndOptgroupViewHelperForUseWithSelectViewHelper.rst [new file with mode: 0644]
typo3/sysext/fluid/Classes/ViewHelpers/Form/Select/OptgroupViewHelper.php [new file with mode: 0644]
typo3/sysext/fluid/Classes/ViewHelpers/Form/Select/OptionViewHelper.php [new file with mode: 0644]
typo3/sysext/fluid/Classes/ViewHelpers/Form/SelectViewHelper.php
typo3/sysext/fluid/Tests/Unit/ViewHelpers/Form/SelectViewHelperTest.php

diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-29399-OptionViewHelperAndOptgroupViewHelperForUseWithSelectViewHelper.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-29399-OptionViewHelperAndOptgroupViewHelperForUseWithSelectViewHelper.rst
new file mode 100644 (file)
index 0000000..134aa3c
--- /dev/null
@@ -0,0 +1,41 @@
+.. include:: ../../Includes.txt
+
+=======================================================================================
+Feature: #29399 - OptionViewHelper and OptgroupViewHelper for use with SelectViewHelper
+=======================================================================================
+
+See :issue:`29399`
+
+Description
+===========
+
+Allows manually defining all options and optgroups for
+the f:form.select parent field as tag contents of the
+select field. The added ViewHelpers are TagBasedViewHelpers
+which means they support all standard HTML attributes.
+
+Note that while tag content rendering is now supported,
+it is *STILL* not possible to create ``<option>`` tags
+manually - you *HAVE* to use the form fields!
+
+Example:
+
+.. code-block:: html
+
+       <f:form.select name="myproperty">
+               <f:form.select.option value="1">Option one</f:form.select.option>
+               <f:form.select.option value="2">Option two</f:form.select.option>
+               <f:form.select.optgroup>
+                       <f:form.select.option value="3">Grouped option one</f:form.select.option>
+                       <f:form.select.option value="4">Grouped option twi</f:form.select.option>
+               </f:form.select.optgroup>
+       </f:form.select>
+
+
+Impact
+======
+
+* Adds two new ViewHelpers
+* Changes SelectViewHelper to allow tag content (but not manual options created without using f:form.select.*)
+
+.. index:: Fluid
\ No newline at end of file
diff --git a/typo3/sysext/fluid/Classes/ViewHelpers/Form/Select/OptgroupViewHelper.php b/typo3/sysext/fluid/Classes/ViewHelpers/Form/Select/OptgroupViewHelper.php
new file mode 100644 (file)
index 0000000..1b2c605
--- /dev/null
@@ -0,0 +1,56 @@
+<?php
+namespace TYPO3\CMS\Fluid\ViewHelpers\Form\Select;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+/**
+ * Adds custom `<optgroup>` tags inside an `<f:form.select>`,
+ * supports further child `<f:form.select.option>` tags.
+ *
+ * @api
+ */
+class OptgroupViewHelper extends \TYPO3\CMS\Fluid\ViewHelpers\Form\AbstractFormFieldViewHelper
+{
+    /**
+     * @var string
+     */
+    protected $tagName = 'optgroup';
+
+    /**
+     * @return void
+     */
+    public function initializeArguments()
+    {
+        $this->registerUniversalTagAttributes();
+        $this->registerArgument('additionalAttributes', 'array', 'Additional tag attributes. They will be added directly to the resulting HTML tag.');
+        $this->registerArgument('data', 'array', 'Additional data-* attributes. They will each be added with a "data-" prefix.');
+        $this->registerTagAttribute('label', 'string', 'Human-readable label property for the generated optgroup tag');
+        $this->registerTagAttribute('disabled', 'boolean', 'If true, option group is rendered as disabled', false, false);
+    }
+
+    /**
+     * @return string
+     */
+    public function render()
+    {
+        if ($this->arguments['disabled']) {
+            $this->tag->addAttributes('disabled', 'disabled');
+        } else {
+            $this->tag->removeAttribute('disabled');
+        }
+
+        $this->tag->setContent($this->renderChildren());
+        return $this->tag->render();
+    }
+}
diff --git a/typo3/sysext/fluid/Classes/ViewHelpers/Form/Select/OptionViewHelper.php b/typo3/sysext/fluid/Classes/ViewHelpers/Form/Select/OptionViewHelper.php
new file mode 100644 (file)
index 0000000..d09a5df
--- /dev/null
@@ -0,0 +1,88 @@
+<?php
+namespace TYPO3\CMS\Fluid\ViewHelpers\Form\Select;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+use TYPO3\CMS\Fluid\ViewHelpers\Form\SelectViewHelper;
+
+/**
+ * Adds custom `<option>` tags inside an `<f:form.select>`
+ *
+ * @api
+ */
+class OptionViewHelper extends \TYPO3\CMS\Fluid\ViewHelpers\Form\AbstractFormFieldViewHelper
+{
+    /**
+     * @var string
+     */
+    protected $tagName = 'option';
+
+    /**
+     * @return void
+     */
+    public function initializeArguments()
+    {
+        $this->registerUniversalTagAttributes();
+        $this->registerArgument('additionalAttributes', 'array', 'Additional tag attributes. They will be added directly to the resulting HTML tag.');
+        $this->registerArgument('data', 'array', 'Additional data-* attributes. They will each be added with a "data-" prefix.');
+        $this->registerTagAttribute('value', 'mixed', 'Value to be inserted in HTML tag - must be convertible to string!');
+        $this->registerTagAttribute('selected', 'boolean', 'If filled, overrides automatic detection of selected state for this option');
+    }
+
+    /**
+     * @return string
+     */
+    public function render()
+    {
+        if ($this->arguments['selected'] === null) {
+            // user did not provide a tag attribute value. Determine if we need to
+            // set this attribute - or remove it entirely to prevent an empty attribute.
+            if ($this->isValueSelected($this->arguments['value'])) {
+                $this->tag->addAttribute('selected', 'selected');
+            } else {
+                $this->tag->removeAttribute('selected');
+            }
+        }
+        $childContent = $this->renderChildren();
+        $this->tag->setContent($childContent);
+        if (!isset($this->arguments['value'])) {
+            $this->tag->addAttribute('value', $childContent);
+        }
+        $parentRequestedFormTokenFieldName = $this->viewHelperVariableContainer->get(
+            SelectViewHelper::class,
+            'registerFieldNameForFormTokenGeneration'
+        );
+        if ($parentRequestedFormTokenFieldName) {
+            // parent (select field) has requested this option must add one more
+            // entry in the token generation registry for one additional potential
+            // value of the field. Happens when "multiple" is true on parent.
+            $this->registerFieldNameForFormTokenGeneration($parentRequestedFormTokenFieldName);
+        }
+        return $this->tag->render();
+    }
+
+    /**
+     * @param mixed $value
+     * @return bool
+     */
+    protected function isValueSelected($value)
+    {
+        $selectedValue = $this->viewHelperVariableContainer->get(SelectViewHelper::class, 'selectedValue');
+        if (is_array($selectedValue)) {
+            return in_array($value, $selectedValue);
+        } elseif ($selectedValue instanceof \Iterator) {
+            return in_array($value, iterator_to_array($selectedValue));
+        }
+        return $value == $selectedValue;
+    }
+}
index 21ed9b4..672c9d2 100644 (file)
@@ -36,6 +36,22 @@ namespace TYPO3\CMS\Fluid\ViewHelpers\Form;
  *
  * If the select box is a multi-select box (multiple="1"), then "value" can be an array as well.
  *
+ * = Custom options and option group rendering =
+ *
+ * Child nodes can be used to create a completely custom set of ``<option>`` and ``<optgroup>`` tags in a way compatible with
+ * the HMAC generation. To do so, leave out the ``options`` argument and use child ViewHelpers:
+ * <code title="Custom options and optgroup">
+ * <f:form.select name="myproperty">
+ *     <f:form.select.option value="1">Option one</f:form.select.option>
+ *     <f:form.select.option value="2">Option two</f:form.select.option>
+ *     <f:form.select.optgroup>
+ *         <f:form.select.option value="3">Grouped option one</f:form.select.option>
+ *         <f:form.select.option value="4">Grouped option twi</f:form.select.option>
+ *     </f:form.select.optgroup>
+ * </f:form.select>
+ * </code>
+ * Note: do not use vanilla ``<option>`` or ``<optgroup>`` tags! They will invalidate the HMAC generation!
+ *
  * = Usage on domain objects =
  *
  * If you want to output domain objects, you can just pass them as array into the "options" parameter.
@@ -83,7 +99,8 @@ class SelectViewHelper extends \TYPO3\CMS\Fluid\ViewHelpers\Form\AbstractFormFie
         $this->registerUniversalTagAttributes();
         $this->registerTagAttribute('size', 'string', 'Size of input field');
         $this->registerTagAttribute('disabled', 'string', 'Specifies that the input element should be disabled when the page loads');
-        $this->registerArgument('options', 'array', 'Associative array with internal IDs as key, and the values are displayed in the select box', true);
+        $this->registerArgument('options', 'array', 'Associative array with internal IDs as key, and the values are displayed in the select box. Can be combined with or replaced by child f:form.select.* nodes.');
+        $this->registerArgument('optionsAfterContent', 'boolean', 'If true, places auto-generated option tags after those rendered in the tag content. If false, automatic options come first.', false, false);
         $this->registerArgument('optionValueField', 'string', 'If specified, will call the appropriate getter on each object to determine the value.');
         $this->registerArgument('optionLabelField', 'string', 'If specified, will call the appropriate getter on each object to determine the label.');
         $this->registerArgument('sortByOptionLabel', 'boolean', 'If true, List will be sorted by label.', false, false);
@@ -112,7 +129,7 @@ class SelectViewHelper extends \TYPO3\CMS\Fluid\ViewHelpers\Form\AbstractFormFie
         if (empty($options)) {
             $options = ['' => ''];
         }
-        $this->tag->setContent($this->renderOptionTags($options));
+
         $this->addAdditionalIdentityPropertiesIfNeeded();
         $this->setErrorClassAttribute();
         $content = '';
@@ -124,9 +141,29 @@ class SelectViewHelper extends \TYPO3\CMS\Fluid\ViewHelpers\Form\AbstractFormFie
             for ($i = 0; $i < count($options); $i++) {
                 $this->registerFieldNameForFormTokenGeneration($name);
             }
+            // save the parent field name so that any child f:form.select.option
+            // tag will know to call registerFieldNameForFormTokenGeneration
+            $this->viewHelperVariableContainer->addOrUpdate(
+                static::class,
+                'registerFieldNameForFormTokenGeneration',
+                $name
+            );
         } else {
             $this->registerFieldNameForFormTokenGeneration($name);
         }
+
+        $this->viewHelperVariableContainer->addOrUpdate(static::class, 'selectedValue', $this->getSelectedValue());
+        $tagContent = $this->renderOptionTags($options);
+        $childContent = $this->renderChildren();
+        $this->viewHelperVariableContainer->remove(static::class, 'selectedValue');
+        $this->viewHelperVariableContainer->remove(static::class, 'registerFieldNameForFormTokenGeneration');
+        if ($this->arguments['optionsAfterContent']) {
+            $tagContent = $childContent . $tagContent;
+        } else {
+            $tagContent .= $childContent;
+        }
+
+        $this->tag->setContent($tagContent);
         $content .= $this->tag->render();
         return $content;
     }
index 2f8c134..b39e1ec 100644 (file)
@@ -31,7 +31,7 @@ class SelectViewHelperTest extends ViewHelperBaseTestcase
         parent::setUp();
         $this->arguments['name'] = '';
         $this->arguments['sortByOptionLabel'] = false;
-        $this->viewHelper = $this->getAccessibleMock(\TYPO3\CMS\Fluid\ViewHelpers\Form\SelectViewHelper::class, ['setErrorClassAttribute', 'registerFieldNameForFormTokenGeneration']);
+        $this->viewHelper = $this->getAccessibleMock(\TYPO3\CMS\Fluid\ViewHelpers\Form\SelectViewHelper::class, ['setErrorClassAttribute', 'registerFieldNameForFormTokenGeneration', 'renderChildren']);
         $this->tagBuilder = $this->createMock(\TYPO3Fluid\Fluid\Core\ViewHelper\TagBuilder::class);
         $this->viewHelper->_set('tag', $this->tagBuilder);
     }