Breaking: #97330 - FormEngine element classes must create label or legend

See forge#97330

Description

When editing records in the backend, the FormEngine class structure located within EXT:backend/Classes/Form/ handles the generation of the editing view.

A change has been applied related to the rendering of single field labels, which is no longer done automatically by "container" classes: Single elements have to create the label themselves.

Extension that add own elements to FormEngine must be adapted, otherwise the element label is no longer rendered.

Impact

When the required changes are not applied to custom FormEngine element classes, the value of the TCA "label" property is not rendered.

Affected installations

Instances with custom FormEngine elements are affected. Custom elements need to be registered to the FormEngine's NodeFactory, candidates are found by looking at the $GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine'] array (for instance using the System > Configuration backend module provided by EXT:lowlevel). Classes registered using the sub keys nodeRegistry and nodeResolver may be affected. The extension scanner does not find affected classes.

Migration

Custom elements must take care of creating a <label> or <legend> tag on their own: If the element creates an <input>, or <select> tag, the <label> should have a for attribute that points to a field having an id attribute. This is important especially for accessibility. When no such target element exists, a <legend> embedded in a <fieldset> can be used. There are two helper methods in \TYPO3\CMS\Backend\Form\Element\AbstractFormElement to help with this: renderLabel() and wrapWithFieldsetAndLegend().

In practice, an element having an <input>, or <select> field should essentially look like this:

$resultArray = $this->initializeResultArray();
// Next line is only needed for extensions that need to keep TYPO3 v12 compatibility
$resultArray['labelHasBeenHandled'] = true;
$fieldId = StringUtility::getUniqueId('formengine-input-');
$html = [];
$html[] = $this->renderLabel($fieldId);
$html[] = '<div class="formengine-field-item t3js-formengine-field-item">';
$html[] =     '<div class="form-wizards-wrap">';
$html[] =         '<div class="form-wizards-element">';
$html[] =             '<div class="form-control-wrap">';
$html[] =                 '<input class="form-control" id="' . htmlspecialchars($fieldId) . '" value="..." type="text">';
$html[] =             '</div>';
$html[] =         '</div>';
$html[] =     '</div>';
$html[] = '</div>';
$resultArray['html'] = implode(LF, $html);
return $resultArray;
Copied!

The renderLabel() is a helper method to generate a <label> tag with a for attribute, and the same fieldId is used as id attribute in the <input> field to connect <label> and <input> with each other.

If there is no such field, a <legend> tag should be used:

$resultArray = $this->initializeResultArray();
// Next line is only needed for extensions that need to keep TYPO3 v12 compatibility
$resultArray['labelHasBeenHandled'] = true;
$html = [];
$html[] = '<div class="formengine-field-item t3js-formengine-field-item">';
$html[] =     '<div class="form-wizards-wrap">';
$html[] =         '<div class="form-wizards-element">';
$html[] =             '<div class="form-control-wrap">';
$html[] =                 Some custom element html
$html[] =             '</div>';
$html[] =         '</div>';
$html[] =     '</div>';
$html[] = '</div>';
$resultArray['html'] = $this->wrapWithFieldsetAndLegend(implode(LF, $html));
return $resultArray;
Copied!