A possible solution for multiple checkboxes by evaluating the bitmask
Programmers often want to read some data into a form and then output it as text. There are a few examples of this here.
Sometimes programmers want to display the form data in the same form with multiple checkboxes so that the user can change the data. There are no examples of this and many programmers find it difficult to read the data bit by bit and then output it again.
Here is a working example (in BE and FE):
(Tested with Typo3 9.5.20 and 10.4.9)
In TCA the example of the question:
'days' => [
'exclude' => false,
'label' => 'LLL:EXT:example/Resources/Private/Language/locallang_db.xlf:tx_example_domain_model_week.days',
'config' => [
'type' => 'check',
'items' => [
['monday', ''],
['thuesday', ''],
['wednesday', ''],
['thursday', ''],
['friday', ''],
['saturday', ''],
['sunday', ''],
],
'default' => 0,
]
],
The model:
The type of the property must be integer.
However, getters and setters are arrays because we have a multiple checkbox and this is implemented with an array.
It is important to keep this in mind as it will create a problem that needs to be resolved.
class Week extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
{
/**
* Days
*
* @var int
*/
protected $days = 0;
/**
* Returns the days
*
* @return array $days
*/
public function getDays()
{
return $this->days;
}
/**
* Sets the days
*
* @param array $days
* @return void
*/
public function setDays($days)
{
$this->days = $days;
}
}
In controller
In the initializeCreateAction and the initializeUpdateAction we solve the problem of the different property types between integer and arrays.
Without this, we receive an error message that the array cannot be converted to an integer.
This code means that Extbase should keep the property type.
In the createAction and the updateAction we branch to the method countBits in CheckboxUtility to add the values of the selected checkboxes.
In the editAction and the updateAction we branch to the method convertDataForMultipleCheckboxes in CheckboxUtility in order to convert the values to be input and output.
/**
* initializeCreateAction
* @return void
*/
public function initializeCreateAction(): void
{
if ($this->arguments->hasArgument('newWeek')) {
$this->arguments->getArgument('newWeek')->getPropertyMappingConfiguration()->setTargetTypeForSubProperty('days', 'array');
}
}
/**
* action create
*
* @param Week $newWeek
* @return void
*/
public function createAction(Week $newWeek)
{
$days = (int)CheckboxUtility::countBits($newWeek->getDays());
$newWeek->setDays($days);
$this->weekRepository->add($newWeek);
$this->redirect('list');
}
/**
* action edit
*
* @param Week $week
* @return void
*/
public function editAction(Week $week)
{
$week->setDays(CheckboxUtility::convertDataForMultipleCheckboxes((int)$week->getDays()));
$this->view->assign('week', $week);
}
/**
* initializeUpdateAction
* @return void
*/
public function initializeUpdateAction(): void
{
if ($this->arguments->hasArgument('week')) {
$this->arguments->getArgument('week')->getPropertyMappingConfiguration()->setTargetTypeForSubProperty('days', 'array');
}
}
/**
* action update
*
* @param Week $week
* @return void
*/
public function updateAction(Week $week)
{
$days = (int)CheckboxUtility::countBits($week->getDays());
$week->setDays($days);
$this->weekRepository->update($week);
$this->redirect('list');
}
In Classes/Utility/CheckboxUtility.php
Read the code. The procedure is described at each point.
In method convertDataForMultipleCheckboxes the basic direction is as follows:
We have an integer value in the database, e.g. 109.
In binary notation: 1011011 (64 + 32 + 0 + 8 + 4 + 0 + 1 = 109)
In the form, this means that the first, third, fourth, sixth and seventh checkboxes are selected.
We read the binary value from left to right, at 1011011 in seven loops.
For example, let's read the first character (from the left) we overwrite the six characters on the right with 0. This results in the binary number 1000000, in decimal notation = 64.
For example, let's read the fourth character (from the left) we overwrite the three characters on the right with 0. This results in the binary number 1000, in decimal notation = 8.
When we have read this, we will get the result 64 + 32 + 0 + 8 + 4 + 0 + 1 because we read from left to right.
Therefore we turn the result around at the end so that each checkbox receives the correct value!
So we get this 1 + 0 + 4 + 8 + 0 + 32 + 64 because the first, third, fourth, sixth and seventh checkboxes are selected.
In method countBits we just add all integer values to one number.
namespace Vendor\Example\Utility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
class CheckboxUtility extends GeneralUtility
{
/**
* Convert an integer to binary and then convert each bit back to an integer for use with multiple checkboxes.
*
* @param int $value
* @return array
*/
public static function convertDataForMultipleCheckboxes(int $value): array
{
$bin = decbin($value); // convert dec to bin
$num = strlen($bin); // counts the bits
$res = array();
for ($i = 0; $i < $num; $i++) {
// loop through binary value
if ($bin[$i] !== 0) {
$bin_2 = str_pad($bin[$i], $num - $i, '0'); //pad string
$res[] = bindec($bin_2); // convert that bit to dec and push in array
}
}
return array_reverse($res); // reverse order and return
}
/**
* Adds the values of the checkboxes
*
* @param array $value
* @return int
*/
public static function countBits(array $value): int
{
foreach ($value as $key => $item) {
$res = $res + $item;
}
return $res;
}
}
In Templates or Partials
The argument multiple="1" is important here. This adds an additional dimension to the array of property days. (This can be seen in the website's source code).
It is important that we give the checkbox the correct value according to the binary notation.
When we have read the values from the database, the result is available to us as an array. So we read the additional dimension at the appropriate place (starting with 0) in the same order as the order of the checkboxes. e.g. the seventh value / checkbox: checked = "{week.days.6} == 64"
<f:form.checkbox
id="day_1"
property="days"
value="1"
multiple="1"
checked="{week.days.0} == 1" />
<label for="day_1" class="form-control-label">
<f:translate key="tx_example_domain_model_week.day1" />
</label>
<f:form.checkbox
id="day_2"
property="days"
value="2"
multiple="1"
checked="{week.days.1} == 2" />
<label for="day_2" class="form-control-label">
<f:translate key="tx_example_domain_model_week.day2" />
</label>
<f:form.checkbox
id="day_3"
property="days"
value="4"
multiple="1"
checked="{week.days.2} == 4" />
<label for="day_3" class="form-control-label">
<f:translate key="tx_example_domain_model_week.day3" />
</label>
<f:form.checkbox
id="day_4"
property="days"
value="8"
multiple="1"
checked="{week.days.3} == 8" />
<label for="day_4" class="form-control-label">
<f:translate key="tx_example_domain_model_week.day4" />
</label>
<f:form.checkbox
id="day_5"
property="days"
value="16"
multiple="1"
checked="{week.days.4} == 16" />
<label for="day_5" class="form-control-label">
<f:translate key="tx_example_domain_model_week.day5" />
</label>
<f:form.checkbox
id="day_6"
property="days"
value="32"
multiple="1"
checked="{week.days.5} == 32" />
<label for="day_6" class="form-control-label">
<f:translate key="tx_example_domain_model_week.day6" />
</label>
<f:form.checkbox
id="day_7"
property="days"
value="64"
multiple="1"
checked="{week.days.6} == 64" />
<label for="day_7" class="form-control-label">
<f:translate key="tx_example_domain_model_week.day7" />
</label>
... and now happy coding!