Skip to content

Instantly share code, notes, and snippets.

@nostadt
Last active June 7, 2019 08:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nostadt/bdea1ec35e9563faa0facedfa67aa48a to your computer and use it in GitHub Desktop.
Save nostadt/bdea1ec35e9563faa0facedfa67aa48a to your computer and use it in GitHub Desktop.
Get nullable checkbox for SelectSingleElement (Dropdown) in T3 TCA back
<?php
(function () {
// nodeRegistry number / key is arbitrary. Key does not mattter that much. As long as it is unique.
$GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['nodeRegistry'][24234354] = [
'nodeName' => 'selectSingle',
'priority' => 90,
'class' => \Vendor\MyExt\Form\Element\SelectSingleWithNullElement::class,
];
})();
<?php
namespace Vendor\MyExt\Form\Element;
use TYPO3\CMS\Backend\Form\Element\SelectSingleElement;
use TYPO3\CMS\Backend\Form\InlineStackProcessor;
use TYPO3\CMS\Backend\Form\Utility\FormEngineUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\StringUtility;
/**
* Own implementation of renderType "selectSingleElement". We override because we want to make use of the null checkbox
* which most likely has been removed with 8 LTS. See EXT:my_ext/ext_localconf.php for registration.
*/
class SelectSingleWithNullElement extends SelectSingleElement
{
/**
* Render single element
*
* @return array As defined in initializeResultArray() of AbstractNode
*/
public function render()
{
$resultArray = $this->initializeResultArray();
$table = $this->data['tableName'];
$field = $this->data['fieldName'];
$row = $this->data['databaseRow'];
$parameterArray = $this->data['parameterArray'];
$config = $parameterArray['fieldConf']['config'];
$selectItems = $parameterArray['fieldConf']['config']['items'];
// Check against inline uniqueness
/** @var InlineStackProcessor $inlineStackProcessor */
$inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
$inlineStackProcessor->initializeByGivenStructure($this->data['inlineStructure']);
$uniqueIds = null;
if ($this->data['isInlineChild'] && $this->data['inlineParentUid']) {
// @todo: At least parts of this if is dead and/or broken: $uniqueIds is filled but never used.
// See InlineControlContainer where 'inlineData' 'unique' 'used' is set. What exactly is
// this if supposed to do and when should it kick in and what for?
$inlineObjectName = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']);
$inlineFormName = $inlineStackProcessor->getCurrentStructureFormPrefix();
if ($this->data['inlineParentConfig']['foreign_table'] === $table
&& $this->data['inlineParentConfig']['foreign_unique'] === $field
) {
$uniqueIds = $this->data['inlineData']['unique'][$inlineObjectName . '-' . $table]['used'];
$parameterArray['fieldChangeFunc']['inlineUnique'] = 'inline.updateUnique(this,'
. GeneralUtility::quoteJSvalue($inlineObjectName . '-' . $table) . ','
. GeneralUtility::quoteJSvalue($inlineFormName) . ','
. GeneralUtility::quoteJSvalue($row['uid']) . ');';
}
// hide uid of parent record for symmetric relations
if ($this->data['inlineParentConfig']['foreign_table'] === $table
&& (
$this->data['inlineParentConfig']['foreign_field'] === $field
|| $this->data['inlineParentConfig']['symmetric_field'] === $field
)
) {
$uniqueIds[] = $this->data['inlineParentUid'];
}
}
// Initialization:
$selectId = StringUtility::getUniqueId('tceforms-select-');
$selectedIcon = '';
$size = (int)$config['size'];
// Style set on <select/>
$options = '';
$disabled = false;
if (!empty($config['readOnly'])) {
$disabled = true;
}
// Prepare groups
$selectItemCounter = 0;
$selectItemGroupCount = 0;
$selectItemGroups = [];
$selectedValue = '';
$hasIcons = false;
if (!empty($parameterArray['itemFormElValue'])) {
$selectedValue = (string)$parameterArray['itemFormElValue'][0];
}
foreach ($selectItems as $item) {
if ($item[1] === '--div--') {
// IS OPTGROUP
if ($selectItemCounter !== 0) {
$selectItemGroupCount++;
}
$selectItemGroups[$selectItemGroupCount]['header'] = [
'title' => $item[0],
];
} else {
// IS ITEM
$icon = !empty($item[2]) ? FormEngineUtility::getIconHtml($item[2], $item[0], $item[0]) : '';
$selected = $selectedValue === (string)$item[1];
if ($selected) {
$selectedIcon = $icon;
}
$selectItemGroups[$selectItemGroupCount]['items'][] = [
'title' => $item[0],
'value' => $item[1],
'icon' => $icon,
'selected' => $selected,
];
$selectItemCounter++;
}
}
// Fallback icon
// @todo: assign a special icon for non matching values?
if (!$selectedIcon && $selectItemGroups[0]['items'][0]['icon']) {
$selectedIcon = $selectItemGroups[0]['items'][0]['icon'];
}
// Process groups
foreach ($selectItemGroups as $selectItemGroup) {
// suppress groups without items
if (empty($selectItemGroup['items'])) {
continue;
}
$optionGroup = is_array($selectItemGroup['header']);
$options .= ($optionGroup ? '<optgroup label="' . htmlspecialchars($selectItemGroup['header']['title'], ENT_COMPAT, 'UTF-8', false) . '">' : '');
if (is_array($selectItemGroup['items'])) {
foreach ($selectItemGroup['items'] as $item) {
$options .= '<option value="' . htmlspecialchars($item['value']) . '" data-icon="' .
htmlspecialchars($item['icon']) . '"'
. ($item['selected'] ? ' selected="selected"' : '') . '>' . htmlspecialchars($item['title'], ENT_COMPAT, 'UTF-8', false) . '</option>';
}
$hasIcons = !empty($item['icon']);
}
$options .= ($optionGroup ? '</optgroup>' : '');
}
$selectAttributes = [
'id' => $selectId,
'name' => $parameterArray['itemFormElName'],
'data-formengine-validation-rules' => $this->getValidationDataAsJsonString($config),
'class' => 'form-control form-control-adapt',
];
if ($size) {
$selectAttributes['size'] = $size;
}
if ($disabled) {
$selectAttributes['disabled'] = 'disabled';
}
$legacyWizards = $this->renderWizards();
$legacyFieldWizardHtml = implode(LF, $legacyWizards['fieldWizard']);
$fieldInformationResult = $this->renderFieldInformation();
$fieldInformationHtml = $fieldInformationResult['html'];
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldInformationResult, false);
$fieldWizardResult = $this->renderFieldWizard();
$fieldWizardHtml = $legacyFieldWizardHtml . $fieldWizardResult['html'];
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldWizardResult, false);
$html = [];
$html[] = '<div class="formengine-field-item t3js-formengine-field-item">';
if (!$disabled) {
$html[] = $fieldInformationHtml;
}
// CUSTOM: this condition + content custom. Rest of the render-method is taken from core.
if (isset($GLOBALS['TCA'][$table]['columns'][$column]['config']['eval']) && GeneralUtility::inList($GLOBALS['TCA'][$table]['columns'][$column]['config']['eval'], 'null')) {
$html[] = $this->renderNullHtml($row, $table, $field);
}
// CUSTOM END
$html[] = '<div class="form-control-wrap">';
$html[] = '<div class="form-wizards-wrap">';
$html[] = '<div class="form-wizards-element">';
if ($hasIcons) {
$html[] = '<div class="input-group">';
$html[] = '<span class="input-group-addon input-group-icon">';
$html[] = $selectedIcon;
$html[] = '</span>';
}
$html[] = '<select ' . GeneralUtility::implodeAttributes($selectAttributes, true) . '>';
$html[] = $options;
$html[] = '</select>';
if ($hasIcons) {
$html[] = '</div>';
}
$html[] = '</div>';
if (!$disabled) {
$html[] = '<div class="form-wizards-items-bottom">';
$html[] = $fieldWizardHtml;
$html[] = '</div>';
}
$html[] = '</div>';
$html[] = '</div>';
$html[] = '</div>';
$resultArray['requireJsModules'][] = ['TYPO3/CMS/Backend/FormEngine/Element/SelectSingleElement' => implode(LF, [
'function(SelectSingleElement) {',
'require([\'jquery\'], function($) {',
'$(function() {',
'SelectSingleElement.initialize(',
GeneralUtility::quoteJSvalue('#' . $selectId) . ',',
'{',
'onChange: function() {',
implode('', $parameterArray['fieldChangeFunc']),
'}',
'}',
');',
'});',
'});',
'}',
])];
$resultArray['html'] = implode(LF, $html);
return $resultArray;
}
/**
* @param array $row
* @param string $table
* @param string $field
*
* @return string
*/
protected function renderNullHtml(array &$row, string $table, string $field): string
{
$checked = $row[$field] !== null ? ' checked="checked"' : '';
$nullControlNameEscaped = htmlspecialchars("control[active][{$table}][{$row['uid']}][{$field}]");
$label = $GLOBALS['LANG']->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.nullCheckbox');
return <<<HTML
<div class="t3-form-field-disable"></div>
<div class="checkbox t3-form-field-eval-null-checkbox">
<label for="{$nullControlNameEscaped}">
<input type="hidden" name="{$nullControlNameEscaped}" value="0">
<input type="checkbox" name="{$nullControlNameEscaped}" id="{$nullControlNameEscaped}" value="1" {$checked}>
{$label}
</label>
</div>
HTML;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment