N°8772 - dynamic form

This commit is contained in:
Benjamin Dalsass
2025-11-14 10:50:57 +01:00
parent 4d159ea3f1
commit e5058fb8f7
42 changed files with 880 additions and 540 deletions

View File

@@ -7,8 +7,10 @@
namespace Combodo\iTop\Forms\Block\Base;
use Combodo\iTop\Forms\Block\AbstractTypeFormBlock;
use Combodo\iTop\Forms\Block\IO\Converter\StringToBooleanConverter;
use Combodo\iTop\Forms\Block\IO\Format\BooleanIOFormat;
use Combodo\iTop\Forms\IO\Converter\StringToBooleanConverter;
use Combodo\iTop\Forms\IO\Format\BooleanIOFormat;
use Combodo\iTop\Forms\Register\IORegister;
use Combodo\iTop\Forms\Register\OptionsRegister;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
/**
@@ -27,16 +29,16 @@ class CheckboxFormBlock extends AbstractTypeFormBlock
}
/** @inheritdoc */
function InitBlockOptions(array &$aUserOptions): void
protected function RegisterOptions(OptionsRegister $oOptionsRegister): void
{
parent::InitBlockOptions($aUserOptions);
$aUserOptions['required'] = false;
parent::RegisterOptions($oOptionsRegister);
$oOptionsRegister->SetOption('required', false);
}
/** @inheritdoc */
function InitOutputs(): void
protected function RegisterIO(IORegister $oIORegister): void
{
parent::InitOutputs();
$this->AddOutput(self::OUTPUT_CHECKED, BooleanIOFormat::class, new StringToBooleanConverter());
parent::RegisterIO($oIORegister);
$oIORegister->AddOutput(self::OUTPUT_CHECKED, BooleanIOFormat::class, new StringToBooleanConverter());
}
}

View File

@@ -7,9 +7,10 @@
namespace Combodo\iTop\Forms\Block\Base;
use Combodo\iTop\Forms\Block\AbstractTypeFormBlock;
use Combodo\iTop\Forms\Block\FormType\ChoiceFormType;
use Combodo\iTop\Forms\Block\IO\Converter\ChoiceValueToLabelConverter;
use Combodo\iTop\Forms\Block\IO\Format\RawFormat;
use Combodo\iTop\Forms\FormType\Base\ChoiceFormType;
use Combodo\iTop\Forms\IO\Converter\ChoiceValueToLabelConverter;
use Combodo\iTop\Forms\IO\Format\RawFormat;
use Combodo\iTop\Forms\Register\IORegister;
/**
* Form block for choices.
@@ -26,9 +27,10 @@ class ChoiceFormBlock extends AbstractTypeFormBlock
return ChoiceFormType::class;
}
public function InitOutputs(): void
/** @inheritdoc */
protected function RegisterIO(IORegister $oIORegister): void
{
parent::InitOutputs();
$this->AddOutput(self::OUTPUT_LABEL, RawFormat::class, new ChoiceValueToLabelConverter($this));
parent::RegisterIO($oIORegister);
$oIORegister->AddOutput(self::OUTPUT_LABEL, RawFormat::class, new ChoiceValueToLabelConverter($this));
}
}

View File

@@ -0,0 +1,32 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Forms\Block\Base;
use Combodo\iTop\Forms\IO\FormInput;
use Combodo\iTop\Forms\Register\OptionsRegister;
/**
*
*/
class ChoiceFromInputs extends ChoiceFormBlock
{
/** @inheritdoc */
public function UpdateOptions(OptionsRegister $oOptionsRegister): void
{
parent::UpdateOptions($oOptionsRegister);
// Compute options based on inputs values
$aChoices = [];
/** @var FormInput $oInput */
foreach ($this->GetInputs() as $oInput) {
if($oInput->HasValue()){
$aChoices[strval($oInput->GetValue())] = $oInput->GetName();
}
}
$oOptionsRegister->SetOption('choices', $aChoices);
}
}

View File

@@ -7,8 +7,10 @@
namespace Combodo\iTop\Forms\Block\Base;
use Combodo\iTop\Forms\Block\AbstractTypeFormBlock;
use Combodo\iTop\Forms\Block\FormType\CollectionFormType;
use Combodo\iTop\Forms\Block\IO\Format\ClassIOFormat;
use Combodo\iTop\Forms\FormType\Base\CollectionFormType;
use Combodo\iTop\Forms\IO\Format\ClassIOFormat;
use Combodo\iTop\Forms\Register\IORegister;
use Combodo\iTop\Forms\Register\OptionsRegister;
/**
* Collection form type.
@@ -19,16 +21,8 @@ class CollectionBlock extends AbstractTypeFormBlock
// Inputs
public const INPUT_CLASS_NAME = 'class_name';
/**
* Constructor.
*
* @param string $sName
* @param array $aOptions
*/
public function __construct(string $sName, array $aOptions = [])
{
parent::__construct($sName, $aOptions);
}
/** @var FormBlock block */
protected AbstractTypeFormBlock $oPrototypeBlock;
/** @inheritdoc */
public function GetFormType(): string
@@ -36,47 +30,54 @@ class CollectionBlock extends AbstractTypeFormBlock
return CollectionFormType::class;
}
/** @inheritdoc */
public function InitInputs(): void
/**
* Get the prototype block.
*
* @return AbstractTypeFormBlock
*/
public function GetPrototypeBlock(): AbstractTypeFormBlock
{
parent::InitInputs();
$this->AddInput(self::INPUT_CLASS_NAME, ClassIOFormat::class);
return $this->oPrototypeBlock;
}
protected $oBlock;
public function GetOptions(): array
/** @inheritdoc */
protected function RegisterIO(IORegister $oIORegister): void
{
$aOptions = parent::GetOptions();
parent::RegisterIO($oIORegister);
$oIORegister->AddInput(self::INPUT_CLASS_NAME, ClassIOFormat::class);
}
// Convert block information in type information
if(isset($aOptions['block_entry_type'])) {
$aOptions['prototype'] = true;
$aOptions['allow_add'] = true;
$aOptions['prototype_options'] = [
'label' => false
];
}
/** @inheritdoc */
protected function RegisterOptions(OptionsRegister $oOptionsRegister): void
{
parent::RegisterOptions($oOptionsRegister);
$sBlockEntryType = $aOptions['block_entry_type'];
$sBlockEntryOptions = $aOptions['block_entry_options'];
$this->oBlock = new ($sBlockEntryType)('prototype', $sBlockEntryOptions);
$oOptionsRegister->SetOption('prototype', true);
$oOptionsRegister->SetOption('allow_add', true);
$oOptionsRegister->SetOption('prototype_options', [
'label' => false
]);
// $this->HandleBlockDependencies();
// not type options
$oOptionsRegister->SetOption('block_entry_type', FormBlock::class, false);
$oOptionsRegister->SetOption('block_entry_options', [], false);
}
/** @inheritdoc */
protected function AfterOptionsRegistered(OptionsRegister $oOptionsRegister): void
{
parent::AfterOptionsRegistered($oOptionsRegister);
$sBlockEntryType = $this->GetOption('block_entry_type');
$sBlockEntryOptions = $this->GetOption('block_entry_options');
$this->oPrototypeBlock = new ($sBlockEntryType)('prototype', $sBlockEntryOptions);
// $this->HandleBlockDependencies();
// $this->oBlock->SetParent($this->GetParent());
// $oBlock->DependsOn('company', 'company', AbstractFormBlock::OUTPUT_VALUE);
unset($aOptions['block_entry_type']);
unset($aOptions['block_entry_options']);
$aOptions['entry_type'] = $this->oBlock->GetFormType();
$aOptions['entry_options'] = $this->oBlock->GetOptions();
return $aOptions;
}
public function HandleBlockDependencies(): void
{
$oOptionsRegister->SetOption('entry_type', $this->oPrototypeBlock->GetFormType());
$oOptionsRegister->SetOption('entry_options', $this->oPrototypeBlock->GetOptions());
}
}

View File

@@ -7,6 +7,7 @@
namespace Combodo\iTop\Forms\Block\Base;
use Combodo\iTop\Forms\Block\AbstractTypeFormBlock;
use Combodo\iTop\Forms\Register\OptionsRegister;
use Symfony\Component\Form\Extension\Core\Type\DateType;
/**
@@ -21,9 +22,10 @@ class DateFormBlock extends AbstractTypeFormBlock
return DateType::class;
}
public function InitBlockOptions(array &$aUserOptions): void
/** @inheritdoc */
protected function RegisterOptions(OptionsRegister $oOptionsRegister): void
{
parent::InitBlockOptions($aUserOptions);
$aUserOptions['widget'] = 'single_text';
parent::RegisterOptions($oOptionsRegister);
$oOptionsRegister->SetOption('widget', 'single_text');
}
}

View File

@@ -7,6 +7,7 @@
namespace Combodo\iTop\Forms\Block\Base;
use Combodo\iTop\Forms\Block\AbstractTypeFormBlock;
use Combodo\iTop\Forms\Register\OptionsRegister;
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
/**
@@ -21,9 +22,10 @@ class DateTimeFormBlock extends AbstractTypeFormBlock
return DateTimeType::class;
}
public function InitBlockOptions(array &$aUserOptions): void
/** @inheritdoc */
protected function RegisterOptions(OptionsRegister $oOptionsRegister): void
{
parent::InitBlockOptions($aUserOptions);
$aUserOptions['widget'] = 'single_text';
parent::RegisterOptions($oOptionsRegister);
$oOptionsRegister->SetOption('widget', 'single_text');
}
}

View File

@@ -1,96 +0,0 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\ItopSdkFormDemonstrator\Form\Block;
use Combodo\iTop\Forms\Block\AbstractFormBlock;
use Combodo\iTop\Forms\Block\IO\Format\BooleanIOFormat;
use Combodo\iTop\Forms\Block\IO\Format\RawFormat;
use Combodo\iTop\Forms\FormsException;
use IssueLog;
use Symfony\Component\Form\FormEvents;
class ExpressionFormBlock extends AbstractFormBlock
{
const OUTPUT_RESULT = "result";
public function InitBlockOptions(array &$aUserOptions): void
{
parent::InitBlockOptions($aUserOptions);
}
public function InitOutputs(): void
{
parent::InitOutputs();
$this->AddOutput(self::OUTPUT_RESULT, BooleanIOFormat::class);
// $this->AddOutput(self::OUTPUT_RAW, RawFormat::class);
}
// public function InputHasChanged()
// {
// if (!$this->IsInputsDataReady()) {
// return;
// }
// $sExpression = $this->GetOptions()['expression'];
// $sValue = preg_replace_callback(
// "/\[\[(?<input>[^\]]+)]]/",
// function(array $aMatches): string {
// return $this->GetInput($aMatches['input'])->GetValue();
// },
// $sExpression);
//
// foreach ($this->GetInputs() as $oFormInput) {
// IssueLog::Info($oFormInput->GetName().' = '.$oFormInput->GetValue());
// }
// IssueLog::Info("Result of [$sExpression] is [$sValue]");
//
// $result = '';
// eval('$result = '.$sValue.';');
// IssueLog::Info("Result of [$sExpression] is eval to [$result]");
//
// $this->GetOutput(self::OUTPUT_RESULT)->SetValue(FormEvents::POST_SUBMIT, new BooleanIOFormat($result));
// $this->GetOutput(self::OUTPUT_VALUE)->SetValue(FormEvents::POST_SUBMIT, new RawFormat($result));
// }
public function InputHasChanged()
{
if (!$this->IsInputsDataReady()) {
return;
}
$sExpression = $this->GetOptions()['expression'];
$this->Compute($sExpression, FormEvents::POST_SET_DATA);
$this->Compute($sExpression, FormEvents::POST_SUBMIT);
}
public function Compute(string $sExpression, string $sEventType): void
{
try{
$sValue = preg_replace_callback(
"/\[\[(?<input>[^\]]+)]]/",
function(array $aMatches) use ($sEventType): ?string {
$oInput = $this->GetInput($aMatches['input']);
if(!$oInput->HasEventValue($sEventType)){
throw new FormsException('Unable to compute expression: input '.$aMatches['input'].' has no value for event '.$sEventType.'.');
}
return $oInput->GetValue($sEventType);
},
$sExpression);
$result = '';
eval('$result = '.$sValue.';');
$this->GetOutput(self::OUTPUT_RESULT)->SetValue($sEventType, new BooleanIOFormat($result));
$this->GetOutput(self::OUTPUT_VALUE)->SetValue($sEventType, new RawFormat($result));
}
catch(\Exception $e){
IssueLog::Exception('Compute expression block issue', $e);
}
}
}

View File

@@ -9,11 +9,13 @@ namespace Combodo\iTop\Forms\Block\Base;
use Combodo\iTop\Forms\Block\AbstractFormBlock;
use Combodo\iTop\Forms\Block\AbstractTypeFormBlock;
use Combodo\iTop\Forms\Block\FormBlockException;
use Combodo\iTop\Forms\Block\FormType\FormType;
use Combodo\iTop\Forms\FormType\Base\FormType;
use Combodo\iTop\Forms\Register\OptionsRegister;
use Combodo\iTop\Forms\FormBuilder\DependencyMap;
use Combodo\iTop\Forms\FormsException;
use Exception;
use ReflectionClass;
use ReflectionException;
/**
* Complex form type.
@@ -53,15 +55,11 @@ class FormBlock extends AbstractTypeFormBlock
}
/** @inheritdoc */
public function InitBlockOptions(array &$aUserOptions): void
protected function RegisterOptions(OptionsRegister $oOptionsRegister): void
{
parent::InitBlockOptions($aUserOptions);
$aUserOptions['compound'] = true;
$aUserOptions['attr'] = [
'class' => 'form',
];
parent::RegisterOptions($oOptionsRegister);
$oOptionsRegister->SetOption('compound', true);
$oOptionsRegister->SetOptionArrayValue('attr', 'class', 'form');
}
/**
@@ -69,12 +67,13 @@ class FormBlock extends AbstractTypeFormBlock
*
* @param string $sName block name
* @param string $sType block class name
* @param array $aSymfonyOptions options
* @param array $aOptions options
*
* @return $this
* @throws \ReflectionException
* @throws ReflectionException
*/
public function Add(string $sName, string $sType, array $aOptions): AbstractFormBlock
public function Add(string $sName, string $sType, array $aSymfonyOptions, array $aOptions = []): AbstractFormBlock
{
$oRef = new ReflectionClass($sType);
if($oRef->isSubclassOf(AbstractFormBlock::class) === false){
@@ -82,7 +81,7 @@ class FormBlock extends AbstractTypeFormBlock
}
$aOptions['priority'] = -count($this->aChildrenBlocks);
$oSubFormBlock = new ($sType)($sName, $aOptions);
$oSubFormBlock = new ($sType)($sName, $aSymfonyOptions, $aOptions);
$this->aChildrenBlocks[$sName] = $oSubFormBlock;
$oSubFormBlock->SetParent($this);