diff --git a/css/backoffice/components/_form.scss b/css/backoffice/components/_form.scss index 46c353de8..1d8189c09 100644 --- a/css/backoffice/components/_form.scss +++ b/css/backoffice/components/_form.scss @@ -50,3 +50,7 @@ form[aria-busy="true"] { opacity: .5; } + +.ibo-field legend{ + margin-top: 24px; +} \ No newline at end of file diff --git a/data/.compilation-symlinks b/data/.compilation-symlinks new file mode 100644 index 000000000..e69de29bb diff --git a/lib/composer/autoload_classmap.php b/lib/composer/autoload_classmap.php index 66c5f153b..9c6c4b2ec 100644 --- a/lib/composer/autoload_classmap.php +++ b/lib/composer/autoload_classmap.php @@ -475,22 +475,24 @@ return array( 'Combodo\\iTop\\Form\\Validator\\SelectObjectValidator' => $baseDir . '/sources/Form/Validator/SelectObjectValidator.php', 'Combodo\\iTop\\Forms\\Block\\AbstractFormBlock' => $baseDir . '/sources/Forms/Block/AbstractFormBlock.php', 'Combodo\\iTop\\Forms\\Block\\Base\\ChoiceFormBlock' => $baseDir . '/sources/Forms/Block/Base/ChoiceFormBlock.php', - 'Combodo\\iTop\\Forms\\Block\\Base\\StringFormBlock' => $baseDir . '/sources/Forms/Block/Base/StringFormBlock.php', + 'Combodo\\iTop\\Forms\\Block\\Base\\FormBlock' => $baseDir . '/sources/Forms/Block/Base/FormBlock.php', + 'Combodo\\iTop\\Forms\\Block\\Base\\TextAreaFormBlock' => $baseDir . '/sources/Forms/Block/Base/TextAreaFormBlock.php', + 'Combodo\\iTop\\Forms\\Block\\Base\\TextFormBlock' => $baseDir . '/sources/Forms/Block/Base/TextFormBlock.php', 'Combodo\\iTop\\Forms\\Block\\DataModel\\AttributeChoiceFormBlock' => $baseDir . '/sources/Forms/Block/DataModel/AttributeChoiceFormBlock.php', 'Combodo\\iTop\\Forms\\Block\\DataModel\\AttributeValueChoiceFormBlock' => $baseDir . '/sources/Forms/Block/DataModel/AttributeValueChoiceFormBlock.php', 'Combodo\\iTop\\Forms\\Block\\DataModel\\OqlFormBlock' => $baseDir . '/sources/Forms/Block/DataModel/OqlFormBlock.php', - 'Combodo\\iTop\\Forms\\Block\\FormBlock' => $baseDir . '/sources/Forms/Block/FormBlock.php', 'Combodo\\iTop\\Forms\\Block\\FormBlockException' => $baseDir . '/sources/Forms/Block/FormBlockException.php', 'Combodo\\iTop\\Forms\\Block\\FormBlockIOException' => $baseDir . '/sources/Forms/Block/IO/FormBlockIOException.php', 'Combodo\\iTop\\Forms\\Block\\IO\\AbstractFormIO' => $baseDir . '/sources/Forms/Block/IO/AbstractFormIO.php', + 'Combodo\\iTop\\Forms\\Block\\IO\\Converter\\AbstractConverter' => $baseDir . '/sources/Forms/Block/IO/Converter/AbstractConverter.php', + 'Combodo\\iTop\\Forms\\Block\\IO\\Converter\\OqlToClassConverter' => $baseDir . '/sources/Forms/Block/IO/Converter/OqlToClassConverter.php', + 'Combodo\\iTop\\Forms\\Block\\IO\\Converter\\StringToAttributeConverter' => $baseDir . '/sources/Forms/Block/IO/Converter/StringToAttributeConverter.php', 'Combodo\\iTop\\Forms\\Block\\IO\\FormBinding' => $baseDir . '/sources/Forms/Block/IO/FormBinding.php', 'Combodo\\iTop\\Forms\\Block\\IO\\FormInput' => $baseDir . '/sources/Forms/Block/IO/FormInput.php', 'Combodo\\iTop\\Forms\\Block\\IO\\FormOutput' => $baseDir . '/sources/Forms/Block/IO/FormOutput.php', 'Combodo\\iTop\\Forms\\Block\\IO\\Format\\AttributeIOFormat' => $baseDir . '/sources/Forms/Block/IO/Format/AttributeIOFormat.php', 'Combodo\\iTop\\Forms\\Block\\IO\\Format\\ClassIOFormat' => $baseDir . '/sources/Forms/Block/IO/Format/ClassIOFormat.php', - 'Combodo\\iTop\\Forms\\Converter\\AbstractOutputConverter' => $baseDir . '/sources/Forms/Converter/AbstractOutputConverter.php', - 'Combodo\\iTop\\Forms\\Converter\\OqlToClassConverter' => $baseDir . '/sources/Forms/Converter/OqlToClassConverter.php', - 'Combodo\\iTop\\Forms\\Converter\\StringToAttributeConverter' => $baseDir . '/sources/Forms/Converter/StringToAttributeConverter.php', + 'Combodo\\iTop\\Forms\\Block\\IO\\Format\\RawFormat' => $baseDir . '/sources/Forms/Block/IO/Format/RawFormat.php', 'Combodo\\iTop\\Forms\\FormBuilder\\DependencyHandler' => $baseDir . '/sources/Forms/FormBuilder/DependencyHandler.php', 'Combodo\\iTop\\Forms\\FormBuilder\\DependencyMap' => $baseDir . '/sources/Forms/FormBuilder/DependencyMap.php', 'Combodo\\iTop\\Forms\\FormBuilder\\FormBuilder' => $baseDir . '/sources/Forms/FormBuilder/FormBuilder.php', @@ -499,9 +501,10 @@ return array( 'Combodo\\iTop\\Forms\\FormBuilder\\FormTypeExtension' => $baseDir . '/sources/Forms/FormBuilder/FormTypeExtension.php', 'Combodo\\iTop\\Forms\\FormBuilder\\ResolvedFormType' => $baseDir . '/sources/Forms/FormBuilder/ResolvedFormType.php', 'Combodo\\iTop\\Forms\\FormBuilder\\ResolvedFormTypeFactory' => $baseDir . '/sources/Forms/FormBuilder/ResolvedFormTypeFactory.php', - 'Combodo\\iTop\\Forms\\FormType\\AttributeChoiceType' => $baseDir . '/sources/Forms/FormType/AttributeChoiceType.php', - 'Combodo\\iTop\\Forms\\FormType\\AttributeValueChoiceType' => $baseDir . '/sources/Forms/FormType/AttributeValueChoiceType.php', - 'Combodo\\iTop\\Forms\\FormType\\OqlType' => $baseDir . '/sources/Forms/FormType/OqlType.php', + 'Combodo\\iTop\\Forms\\FormType\\AttributeFormType' => $baseDir . '/sources/Forms/FormType/AttributeFormType.php', + 'Combodo\\iTop\\Forms\\FormType\\AttributeValueFormType' => $baseDir . '/sources/Forms/FormType/AttributeValueFormType.php', + 'Combodo\\iTop\\Forms\\FormType\\ChoiceFormType' => $baseDir . '/sources/Forms/FormType/ChoiceFormType.php', + 'Combodo\\iTop\\Forms\\FormType\\OqlFormType' => $baseDir . '/sources/Forms/FormType/OqlFormType.php', 'Combodo\\iTop\\Forms\\Forms' => $baseDir . '/sources/Forms/Forms.php', 'Combodo\\iTop\\Forms\\FormsException' => $baseDir . '/sources/Forms/FormsException.php', 'Combodo\\iTop\\Forms\\Twig\\Extension\\FormCompatibilityExtension' => $baseDir . '/sources/Forms/Twig/Extension/FormCompatibilityExtension.php', diff --git a/lib/composer/autoload_static.php b/lib/composer/autoload_static.php index 96e86989e..8954dcf19 100644 --- a/lib/composer/autoload_static.php +++ b/lib/composer/autoload_static.php @@ -856,22 +856,24 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Combodo\\iTop\\Form\\Validator\\SelectObjectValidator' => __DIR__ . '/../..' . '/sources/Form/Validator/SelectObjectValidator.php', 'Combodo\\iTop\\Forms\\Block\\AbstractFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/AbstractFormBlock.php', 'Combodo\\iTop\\Forms\\Block\\Base\\ChoiceFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/Base/ChoiceFormBlock.php', - 'Combodo\\iTop\\Forms\\Block\\Base\\StringFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/Base/StringFormBlock.php', + 'Combodo\\iTop\\Forms\\Block\\Base\\FormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/Base/FormBlock.php', + 'Combodo\\iTop\\Forms\\Block\\Base\\TextAreaFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/Base/TextAreaFormBlock.php', + 'Combodo\\iTop\\Forms\\Block\\Base\\TextFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/Base/TextFormBlock.php', 'Combodo\\iTop\\Forms\\Block\\DataModel\\AttributeChoiceFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/DataModel/AttributeChoiceFormBlock.php', 'Combodo\\iTop\\Forms\\Block\\DataModel\\AttributeValueChoiceFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/DataModel/AttributeValueChoiceFormBlock.php', 'Combodo\\iTop\\Forms\\Block\\DataModel\\OqlFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/DataModel/OqlFormBlock.php', - 'Combodo\\iTop\\Forms\\Block\\FormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/FormBlock.php', 'Combodo\\iTop\\Forms\\Block\\FormBlockException' => __DIR__ . '/../..' . '/sources/Forms/Block/FormBlockException.php', 'Combodo\\iTop\\Forms\\Block\\FormBlockIOException' => __DIR__ . '/../..' . '/sources/Forms/Block/IO/FormBlockIOException.php', 'Combodo\\iTop\\Forms\\Block\\IO\\AbstractFormIO' => __DIR__ . '/../..' . '/sources/Forms/Block/IO/AbstractFormIO.php', + 'Combodo\\iTop\\Forms\\Block\\IO\\Converter\\AbstractConverter' => __DIR__ . '/../..' . '/sources/Forms/Block/IO/Converter/AbstractConverter.php', + 'Combodo\\iTop\\Forms\\Block\\IO\\Converter\\OqlToClassConverter' => __DIR__ . '/../..' . '/sources/Forms/Block/IO/Converter/OqlToClassConverter.php', + 'Combodo\\iTop\\Forms\\Block\\IO\\Converter\\StringToAttributeConverter' => __DIR__ . '/../..' . '/sources/Forms/Block/IO/Converter/StringToAttributeConverter.php', 'Combodo\\iTop\\Forms\\Block\\IO\\FormBinding' => __DIR__ . '/../..' . '/sources/Forms/Block/IO/FormBinding.php', 'Combodo\\iTop\\Forms\\Block\\IO\\FormInput' => __DIR__ . '/../..' . '/sources/Forms/Block/IO/FormInput.php', 'Combodo\\iTop\\Forms\\Block\\IO\\FormOutput' => __DIR__ . '/../..' . '/sources/Forms/Block/IO/FormOutput.php', 'Combodo\\iTop\\Forms\\Block\\IO\\Format\\AttributeIOFormat' => __DIR__ . '/../..' . '/sources/Forms/Block/IO/Format/AttributeIOFormat.php', 'Combodo\\iTop\\Forms\\Block\\IO\\Format\\ClassIOFormat' => __DIR__ . '/../..' . '/sources/Forms/Block/IO/Format/ClassIOFormat.php', - 'Combodo\\iTop\\Forms\\Converter\\AbstractOutputConverter' => __DIR__ . '/../..' . '/sources/Forms/Converter/AbstractOutputConverter.php', - 'Combodo\\iTop\\Forms\\Converter\\OqlToClassConverter' => __DIR__ . '/../..' . '/sources/Forms/Converter/OqlToClassConverter.php', - 'Combodo\\iTop\\Forms\\Converter\\StringToAttributeConverter' => __DIR__ . '/../..' . '/sources/Forms/Converter/StringToAttributeConverter.php', + 'Combodo\\iTop\\Forms\\Block\\IO\\Format\\RawFormat' => __DIR__ . '/../..' . '/sources/Forms/Block/IO/Format/RawFormat.php', 'Combodo\\iTop\\Forms\\FormBuilder\\DependencyHandler' => __DIR__ . '/../..' . '/sources/Forms/FormBuilder/DependencyHandler.php', 'Combodo\\iTop\\Forms\\FormBuilder\\DependencyMap' => __DIR__ . '/../..' . '/sources/Forms/FormBuilder/DependencyMap.php', 'Combodo\\iTop\\Forms\\FormBuilder\\FormBuilder' => __DIR__ . '/../..' . '/sources/Forms/FormBuilder/FormBuilder.php', @@ -880,9 +882,10 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Combodo\\iTop\\Forms\\FormBuilder\\FormTypeExtension' => __DIR__ . '/../..' . '/sources/Forms/FormBuilder/FormTypeExtension.php', 'Combodo\\iTop\\Forms\\FormBuilder\\ResolvedFormType' => __DIR__ . '/../..' . '/sources/Forms/FormBuilder/ResolvedFormType.php', 'Combodo\\iTop\\Forms\\FormBuilder\\ResolvedFormTypeFactory' => __DIR__ . '/../..' . '/sources/Forms/FormBuilder/ResolvedFormTypeFactory.php', - 'Combodo\\iTop\\Forms\\FormType\\AttributeChoiceType' => __DIR__ . '/../..' . '/sources/Forms/FormType/AttributeChoiceType.php', - 'Combodo\\iTop\\Forms\\FormType\\AttributeValueChoiceType' => __DIR__ . '/../..' . '/sources/Forms/FormType/AttributeValueChoiceType.php', - 'Combodo\\iTop\\Forms\\FormType\\OqlType' => __DIR__ . '/../..' . '/sources/Forms/FormType/OqlType.php', + 'Combodo\\iTop\\Forms\\FormType\\AttributeFormType' => __DIR__ . '/../..' . '/sources/Forms/FormType/AttributeFormType.php', + 'Combodo\\iTop\\Forms\\FormType\\AttributeValueFormType' => __DIR__ . '/../..' . '/sources/Forms/FormType/AttributeValueFormType.php', + 'Combodo\\iTop\\Forms\\FormType\\ChoiceFormType' => __DIR__ . '/../..' . '/sources/Forms/FormType/ChoiceFormType.php', + 'Combodo\\iTop\\Forms\\FormType\\OqlFormType' => __DIR__ . '/../..' . '/sources/Forms/FormType/OqlFormType.php', 'Combodo\\iTop\\Forms\\Forms' => __DIR__ . '/../..' . '/sources/Forms/Forms.php', 'Combodo\\iTop\\Forms\\FormsException' => __DIR__ . '/../..' . '/sources/Forms/FormsException.php', 'Combodo\\iTop\\Forms\\Twig\\Extension\\FormCompatibilityExtension' => __DIR__ . '/../..' . '/sources/Forms/Twig/Extension/FormCompatibilityExtension.php', diff --git a/sources/Forms/Block/AbstractFormBlock.php b/sources/Forms/Block/AbstractFormBlock.php index 64d91ca30..069f9af5e 100644 --- a/sources/Forms/Block/AbstractFormBlock.php +++ b/sources/Forms/Block/AbstractFormBlock.php @@ -8,6 +8,8 @@ namespace Combodo\iTop\Forms\Block; use Combodo\iTop\Forms\Block\IO\FormInput; use Combodo\iTop\Forms\Block\IO\FormOutput; +use IssueLog; +use Symfony\Component\Filesystem\Exception\IOException; /** * Abstract form block. @@ -19,15 +21,6 @@ use Combodo\iTop\Forms\Block\IO\FormOutput; */ abstract class AbstractFormBlock { - /** @var string form block name */ - private string $sName; - - /** @var array form block options */ - private array $aOptions = []; - - /** @var array form sub blocks */ - private array $aSubFormBlocks = []; - /** @var array form block inputs */ private array $aFormInputs = []; @@ -37,26 +30,40 @@ abstract class AbstractFormBlock /** @var bool flag */ private bool $bIsAdded = false; + /** + * Return the form type. + * + * @return string + */ + abstract public function GetFormType(): string; + + /** + * Initialize options. + * + * @return array + */ + abstract public function InitOptions(): array; + /** * Constructor. * * @param string $sName * @param array $aOptions */ - public function __construct(string $sName, array $aOptions = []) + public function __construct(private readonly string $sName, private array $aOptions = []) { - $this->sName = $sName; - $this->aOptions = $aOptions; - + // Attach the form block $this->aOptions['form_block'] = $this; - $this->InitInputs(); - $this->InitOutputs(); + // Compute options $this->aOptions = array_merge($this->aOptions, $this->InitOptions()); - $this->BuildForm(); - } - abstract protected function BuildForm(); + // Initialize block inputs + $this->InitInputs(); + + // Initialize block outputs + $this->InitOutputs(); + } /** * Return the form block name. @@ -91,30 +98,7 @@ abstract class AbstractFormBlock } /** - * Add a sub form. - * - * @param AbstractFormBlock $oSubFormBlock - * - * @return $this - */ - public function AddSubFormBlock(AbstractFormBlock $oSubFormBlock): AbstractFormBlock - { - $this->aSubFormBlocks[] = $oSubFormBlock; - return $this; - } - - /** - * Get the sub forms. - * - * @return array - */ - public function GetSubFormBlocks(): array - { - return $this->aSubFormBlocks; - } - - /** - * Add an input declaration. + * Add an input. * * @param FormInput $oFormInput * @@ -127,7 +111,7 @@ abstract class AbstractFormBlock } /** - * Get an input declaration. + * Get an input. * * @param string $sName * @@ -144,7 +128,7 @@ abstract class AbstractFormBlock } /** - * Add an output declaration. + * Add an output. * * @param FormOutput $oFormOutput * @@ -157,7 +141,7 @@ abstract class AbstractFormBlock } /** - * Get an output declaration. + * Get an output. * * @param string $sName * @@ -173,6 +157,21 @@ abstract class AbstractFormBlock return $this->aFormOutputs[$sName]; } + /** + * Return the inputs. + * + * @return array + */ + public function GetInputs(): array + { + return $this->aFormInputs; + } + + /** + * Return the outputs. + * + * @return array + */ public function GetOutputs(): array { return $this->aFormOutputs; @@ -182,31 +181,66 @@ abstract class AbstractFormBlock * Attach an input to a block output. * * @param string $sInputName - * @param FormBlock $oOutputBlock + * @param AbstractFormBlock $oOutputBlock * @param string $sOutputName * * @return $this * @throws FormBlockException + * @throws FormBlockIOException */ - public function DependsOn(string $sInputName, FormBlock $oOutputBlock, string $sOutputName): AbstractFormBlock + public function DependsOnBlockOutput(string $sInputName, AbstractFormBlock $oOutputBlock, string $sOutputName): AbstractFormBlock { $oFormInput = $this->GetInput($sInputName); $oFormOutput = $oOutputBlock->GetOutput($sOutputName); - $oFormInput->Bind($oFormOutput); + $oFormInput->BindFromOutput($oFormOutput); return $this; } - public function DependsOnParent(string $sInputName, FormBlock $oParentBlock, string $sParentInputName): AbstractFormBlock + /** + * Attach an input to a parent block input. + * + * @param string $sInputName + * @param AbstractFormBlock $oParentBlock + * @param string $sParentInputName + * + * @return $this + * @throws FormBlockException + * @throws FormBlockIOException + */ + public function DependsOnParentBlockInput(string $sInputName, AbstractFormBlock $oParentBlock, string $sParentInputName): AbstractFormBlock { $oFormInput = $this->GetInput($sInputName); - $oParentFormInput = $oParentBlock->GetInput($sParentInputName); - $oFormInput->Bind($oParentFormInput); + $oParentFormInput = $oParentBlock->GetInput($sInputName); + $oFormInput->BindFromInput($oParentFormInput); return $this; } - public function HasConnections(): bool + /** + * Attach an output to a parent block outpu. + * + * @param string $sOutputName + * @param AbstractFormBlock $oParentBlock + * @param string $sParentInputName + * + * @return $this + * @throws FormBlockException + * @throws FormBlockIOException + */ + public function BindOutputToParentBlockOutput(string $sOutputName, AbstractFormBlock $oParentBlock, string $sParentOutputName): AbstractFormBlock + { + $oFormOutput = $this->GetOutput($sOutputName); + $oParentFormOutput = $oParentBlock->GetOutput($sParentOutputName); + $oParentFormOutput->BindFromOutput($oFormOutput); + + return $this; + } + + /** + * @return bool + */ + public function HasAtLeastOneBoundInput(): bool { foreach ($this->aFormInputs as $oFormInput) { if ($oFormInput->IsBound()) { @@ -216,6 +250,23 @@ abstract class AbstractFormBlock return false; } + /** + * @return bool + */ + public function HasAtLeastOneBoundOutput(): bool + { + /** @var FormOutput $oFormOutput */ + foreach ($this->aFormOutputs as $oFormOutput) { + if (count($oFormOutput->GetBindings()) > 0) { + return true; + } + } + return false; + } + + /** + * @return array + */ public function GetInputsBindings(): array { $aBindings = []; @@ -229,11 +280,30 @@ abstract class AbstractFormBlock return $aBindings; } - public function IsInputsReady(string $sEventType): bool + /** + * @return array + */ + public function GetOutputBindings(): array + { + $aBindings = []; + + /** @var FormInput $oFormInput */ + foreach ($this->aFormOutputs as $oFormOutput) { + if ($oFormOutput->IsBound()) { + $aBindings[$oFormOutput->GetName()] = $oFormOutput->GetBinding(); + } + } + return $aBindings; + } + + /** + * @return bool + */ + public function IsInputsReady(): bool { foreach ($this->aFormInputs as $oFormInput) { if ($oFormInput->IsBound()) { - if(!$oFormInput->IsDataReady($sEventType)) { + if(!$oFormInput->IsDataReady()) { return false; } } @@ -242,41 +312,84 @@ abstract class AbstractFormBlock return true; } + /** + * @return bool + */ public function IsAdded(): bool { return $this->bIsAdded; } + /** + * @param bool $bIsAdded + * + * @return void + */ public function SetAdded(bool $bIsAdded): void { $this->bIsAdded = $bIsAdded; } /** - * Return the form type. + * @param string $sEventType + * @param mixed $oData * - * @return string + * @return void */ - abstract public function GetFormType(): string; + public function ComputeOutputs(string $sEventType, mixed $oData): void + { + /** Iterate throw output @var FormOutput $oFormOutput */ + foreach ($this->aFormOutputs as $oFormOutput) { + + // Compute the output value + try{ + $oFormOutput->ComputeValue($sEventType, $oData); + } + catch(IOException $oException){ + IssueLog::Exception(sprintf('Unable to compute values for output %s of block %s', $oFormOutput->GetName(), $this->GetName()), $oException); + } + + } + } + + /** + * Propagate inputs values. + * + * @return void + */ + public function PropagateInputsValues(): void + { + foreach ($this->aFormInputs as $oFormInput) { + $oFormInput->PropagateBindingsValues(); + } + } /** * Initialize inputs. * * @return void */ - abstract public function InitInputs(): void; + public function InitInputs(): void + { + + } /** * Initialize outputs. * * @return void */ - abstract public function InitOutputs(): void; + public function InitOutputs() + { + + } + /** - * Initialize options. - * - * @return array + * @return true */ - abstract public function InitOptions(): array; + public function AllowAdd(): bool + { + return true; + } } \ No newline at end of file diff --git a/sources/Forms/Block/Base/ChoiceFormBlock.php b/sources/Forms/Block/Base/ChoiceFormBlock.php index 77adbf27f..fe65ea549 100644 --- a/sources/Forms/Block/Base/ChoiceFormBlock.php +++ b/sources/Forms/Block/Base/ChoiceFormBlock.php @@ -6,18 +6,28 @@ namespace Combodo\iTop\Forms\Block\Base; -use Combodo\iTop\Forms\Block\FormBlock; +use Combodo\iTop\Forms\Block\AbstractFormBlock; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; /** * Form block for choices. * */ -class ChoiceFormBlock extends FormBlock +class ChoiceFormBlock extends AbstractFormBlock { /** @inheritdoc */ public function GetFormType(): string { return ChoiceType::class; } + + /** @inheritdoc */ + public function InitOptions(): array + { + return [ + // debug purpose + 'required' => false, + 'placeholder' => 'Select an element...', + ]; + } } \ No newline at end of file diff --git a/sources/Forms/Block/Base/FormBlock.php b/sources/Forms/Block/Base/FormBlock.php new file mode 100644 index 000000000..85320ebd3 --- /dev/null +++ b/sources/Forms/Block/Base/FormBlock.php @@ -0,0 +1,82 @@ +BuildForm(); + } + + /** @inheritdoc */ + public function GetFormType(): string + { + return FormType::class; + } + + /** @inheritdoc */ + public function InitOptions(): array + { + return [ + 'compound' => true + ]; + } + + /** + * Add a sub form. + * + * @param AbstractFormBlock $oSubFormBlock + * + * @return $this + */ + public function AddSubFormBlock(AbstractFormBlock $oSubFormBlock): AbstractFormBlock + { + $this->aSubFormBlocks[] = $oSubFormBlock; + return $this; + } + + /** + * Get the sub forms. + * + * @return array + */ + public function GetSubFormBlocks(): array + { + return $this->aSubFormBlocks; + } + + /** + * Build the form. + * + * @return void + */ + protected function BuildForm(): void + { + + } + +} \ No newline at end of file diff --git a/sources/Forms/Block/Base/TextAreaFormBlock.php b/sources/Forms/Block/Base/TextAreaFormBlock.php new file mode 100644 index 000000000..c9f837c59 --- /dev/null +++ b/sources/Forms/Block/Base/TextAreaFormBlock.php @@ -0,0 +1,29 @@ +AddOutput(new FormOutput(self::OUTPUT_ATTRIBUTE, AttributeIOFormat::class, new StringToAttributeConverter())); } - /** @inheritdoc */ + /** @inheritdoc + * @throws FormBlockException + */ + public function AllowAdd(): bool + { + return $this->GetInput(self::INPUT_CLASS_NAME)->Value() != ''; + } + + /** @inheritdoc + * @throws FormBlockException + * @throws CoreException + */ public function UpdateOptions(): array { $aOptions = parent::GetOptions(); - $oBinding = $this->GetInput(self::INPUT_CLASS_NAME)->GetBinding(); - $oConnectionValue = $oBinding->oSourceIO->Value(); + $oValue = $this->GetInput(self::INPUT_CLASS_NAME)->Value(); + if($oValue == '') + return $aOptions; - $aAttributeCodes = \MetaModel::GetAttributesList($oConnectionValue); + $aAttributeCodes = MetaModel::GetAttributesList($oValue); $aAttributeCodes = array_combine($aAttributeCodes, $aAttributeCodes) ; $aOptions['choices'] = $aAttributeCodes; @@ -59,6 +82,6 @@ class AttributeChoiceFormBlock extends ChoiceFormBlock /** @inheritdoc */ public function GetFormType(): string { - return AttributeChoiceType::class; + return AttributeFormType::class; } } \ No newline at end of file diff --git a/sources/Forms/Block/DataModel/AttributeValueChoiceFormBlock.php b/sources/Forms/Block/DataModel/AttributeValueChoiceFormBlock.php index 13a504ded..889d298f1 100644 --- a/sources/Forms/Block/DataModel/AttributeValueChoiceFormBlock.php +++ b/sources/Forms/Block/DataModel/AttributeValueChoiceFormBlock.php @@ -7,10 +7,16 @@ namespace Combodo\iTop\Forms\Block\DataModel; use Combodo\iTop\Forms\Block\Base\ChoiceFormBlock; +use Combodo\iTop\Forms\Block\FormBlockException; +use Combodo\iTop\Forms\Block\IO\Converter\StringToAttributeConverter; use Combodo\iTop\Forms\Block\IO\Format\AttributeIOFormat; use Combodo\iTop\Forms\Block\IO\Format\ClassIOFormat; +use Combodo\iTop\Forms\Block\IO\Format\RawFormat; use Combodo\iTop\Forms\Block\IO\FormInput; -use Combodo\iTop\Forms\FormType\AttributeValueChoiceType; +use Combodo\iTop\Forms\Block\IO\FormOutput; +use Combodo\iTop\Forms\FormType\AttributeValueFormType; +use Exception; +use MetaModel; /** * Form block for choice of class attribute values. @@ -23,13 +29,15 @@ class AttributeValueChoiceFormBlock extends ChoiceFormBlock public const INPUT_CLASS_NAME = 'class_name'; public const INPUT_ATTRIBUTE = 'attribute'; + // Outputs + public const OUTPUT_VALUE = 'value'; + /** @inheritdoc */ public function InitOptions(array &$aOptions = []): array { $aOptions['multiple'] = true; - $aOptions['required'] = false; $aOptions['attr'] = [ - 'size' => 10, + 'size' => 5, 'style' => 'height: auto;' ]; @@ -44,23 +52,52 @@ class AttributeValueChoiceFormBlock extends ChoiceFormBlock $this->AddInput(new FormInput(self::INPUT_ATTRIBUTE, AttributeIOFormat::class)); } + /** @inheritdoc */ + public function InitOutputs(): void + { + parent::InitOutputs(); + $this->AddOutput(new FormOutput(self::OUTPUT_VALUE, RawFormat::class)); + } + + /** @inheritdoc + * @throws FormBlockException + * @throws Exception + */ + public function AllowAdd(): bool + { + $bDependentOk = $this->GetInput(self::INPUT_CLASS_NAME)->Value() != '' && $this->GetInput(self::INPUT_ATTRIBUTE)->Value() != ''; + + if($bDependentOk) { + $oAttDef = MetaModel::GetAttributeDef($this->GetInput(self::INPUT_CLASS_NAME)->Value()->__toString(), $this->GetInput(self::INPUT_ATTRIBUTE)->Value()->__toString()); + $aValues = $oAttDef->GetAllowedValues(); + return $aValues != null; + } + else{ + return false; + } + } + + /** @inheritdoc + * @throws Exception + */ public function UpdateOptions(): array { $aOptions = parent::GetOptions(); - $oBindingClassName = $this->GetInput(self::INPUT_CLASS_NAME)->GetBinding(); - if($oBindingClassName->oSourceIO->Value() === null || $oBindingClassName->oSourceIO->Value() == "") + $oClassName = $this->GetInput(self::INPUT_CLASS_NAME)->Value(); + if($oClassName == '') return $aOptions; - $oClassName = $oBindingClassName->oSourceIO->Value(); - $oBindingAttribute = $this->GetInput(self::INPUT_ATTRIBUTE)->GetBinding(); - if($oBindingAttribute->oSourceIO->Value() === null || $oBindingAttribute->oSourceIO->Value() == "") + $oAttribute = $this->GetInput(self::INPUT_ATTRIBUTE)->Value(); + if($oAttribute == '') return $aOptions; - $oAttribute = $oBindingAttribute->oSourceIO->Value(); - $oAttDef = \MetaModel::GetAttributeDef(strval($oClassName), strval($oAttribute)); + $oAttDef = MetaModel::GetAttributeDef(strval($oClassName), strval($oAttribute)); $aValues = $oAttDef->GetAllowedValues(); + if($aValues === null) + return $aOptions; + $aOptions['choices'] = array_flip($aValues); return $aOptions; @@ -69,7 +106,7 @@ class AttributeValueChoiceFormBlock extends ChoiceFormBlock /** @inheritdoc */ public function GetFormType(): string { - return AttributeValueChoiceType::class; + return AttributeValueFormType::class; } } \ No newline at end of file diff --git a/sources/Forms/Block/DataModel/OqlFormBlock.php b/sources/Forms/Block/DataModel/OqlFormBlock.php index 70c982d20..efbe9b984 100644 --- a/sources/Forms/Block/DataModel/OqlFormBlock.php +++ b/sources/Forms/Block/DataModel/OqlFormBlock.php @@ -6,21 +6,28 @@ namespace Combodo\iTop\Forms\Block\DataModel; -use Combodo\iTop\Forms\Block\Base\StringFormBlock; +use Combodo\iTop\Forms\Block\Base\TextAreaFormBlock; use Combodo\iTop\Forms\Block\IO\Format\ClassIOFormat; use Combodo\iTop\Forms\Block\IO\FormOutput; -use Combodo\iTop\Forms\Converter\OqlToClassConverter; +use Combodo\iTop\Forms\Block\IO\Converter\OqlToClassConverter; +use Combodo\iTop\Forms\FormType\OqlFormType; /** * Form block for oql expression. * * @package DataModel */ -class OqlFormBlock extends StringFormBlock +class OqlFormBlock extends TextAreaFormBlock { // outputs public const OUTPUT_SELECTED_CLASS = 'selected_class'; + /** @inheritdoc */ + public function GetFormType(): string + { + return OqlFormType::class; + } + /** @inheritdoc */ public function InitOutputs(): void { @@ -28,4 +35,14 @@ class OqlFormBlock extends StringFormBlock $this->AddOutput(new FormOutput(self::OUTPUT_SELECTED_CLASS, ClassIOFormat::class, new OqlToClassConverter())); } + /** @inheritdoc */ + public function InitOptions(): array + { + $aOptions = parent::InitOptions(); + $aOptions['with_ai_button'] = true; + return $aOptions; + } + + + } \ No newline at end of file diff --git a/sources/Forms/Block/FormBlock.php b/sources/Forms/Block/FormBlock.php deleted file mode 100644 index bae93e3ca..000000000 --- a/sources/Forms/Block/FormBlock.php +++ /dev/null @@ -1,46 +0,0 @@ -AddOutput(new FormOutput(self::OUTPUT_VALUE, 'string')); - } - - /** @inheritdoc */ - public function InitOptions(): array - { - return []; - } - - protected function BuildForm(): void - { - } -} \ No newline at end of file diff --git a/sources/Forms/Block/IO/AbstractFormIO.php b/sources/Forms/Block/IO/AbstractFormIO.php index 9c9df4760..c4d10e1ae 100644 --- a/sources/Forms/Block/IO/AbstractFormIO.php +++ b/sources/Forms/Block/IO/AbstractFormIO.php @@ -7,48 +7,168 @@ namespace Combodo\iTop\Forms\Block\IO; use Combodo\iTop\Forms\Block\AbstractFormBlock; +use Symfony\Component\Form\FormEvents; class AbstractFormIO { - + /** @var AbstractFormBlock The owner block */ private AbstractFormBlock $oOwnerBlock; + + /** @var string Name of the IO */ private string $sName; + + /** @var string Type of the IO data */ private string $sType; + /** @var array Stored values */ + private array $aValues = []; + + /** + * Constructor. + * + * @param string $sName + * @param string $sType + */ public function __construct(string $sName, string $sType) { $this->sName = $sName; $this->sType = $sType; } + /** + * Get the IO name. + * + * @return string + */ public function GetName(): string { return $this->sName; } - public function SetName(string $sName): void + /** + * Set the IO name. + * + * @param string $sName + * + * @return self + */ + public function SetName(string $sName): self { $this->sName = $sName; + return $this; } - public function GetType(): string + /** + * Get the IO data type. + * + * @return string + */ + public function GetDataType(): string { return $this->sType; } - public function SetType(string $sType): void - { - $this->sType = $sType; - } - + /** + * Get the owner block. + * + * @return AbstractFormBlock + */ public function GetOwnerBlock(): AbstractFormBlock { return $this->oOwnerBlock; } - public function SetOwnerBlock(AbstractFormBlock $oOwnerBlock): void + /** + * Set the owner block. + * + * @param AbstractFormBlock $oOwnerBlock + * + * @return $this + */ + public function SetOwnerBlock(AbstractFormBlock $oOwnerBlock): self { $this->oOwnerBlock = $oOwnerBlock; + return $this; } + /** + * Set the IO value. + * + * @param string $sEventType + * @param mixed $oValue + * + * @return self + */ + public function SetValue(string $sEventType, mixed $oValue): self + { + $this->aValues[$sEventType] = $oValue; + + return $this; + } + + /** + * Get the IO value. + * + * @param string $sEventType + * + * @return mixed + */ + public function GetValue(string $sEventType): mixed + { + return $this->aValues[$sEventType] ?? null; + } + + /** + * Return true if value exist. + * + * @return bool + */ + public function HasValue(): bool + { + $PostSetDataExist = array_key_exists(FormEvents::POST_SET_DATA, $this->aValues) && $this->aValues[FormEvents::POST_SET_DATA] !== null; + $PostSubmitExist = array_key_exists(FormEvents::POST_SUBMIT, $this->aValues) && $this->aValues[FormEvents::POST_SUBMIT] !== null; + return $PostSetDataExist || $PostSubmitExist; + } + + /** + * Return all values. + * + * @return array + */ + public function GetValues(): array + { + return $this->aValues; + } + + /** + * Set the IO values. + * + * @param array $aValues + * + * @return $this + */ + public function SetValues(array $aValues): self + { + $this->aValues = $aValues; + + return $this; + } + + /** + * Get the most relevant value. + * + * @return mixed + */ + public function Value(): mixed + { + if(array_key_exists(FormEvents::POST_SUBMIT, $this->aValues) ){ + return $this->aValues[FormEvents::POST_SUBMIT]; + } + if(array_key_exists(FormEvents::POST_SET_DATA, $this->aValues) ){ + return $this->aValues[FormEvents::POST_SET_DATA]; + } + return null; + } + + } \ No newline at end of file diff --git a/sources/Forms/Converter/AbstractOutputConverter.php b/sources/Forms/Block/IO/Converter/AbstractConverter.php similarity index 79% rename from sources/Forms/Converter/AbstractOutputConverter.php rename to sources/Forms/Block/IO/Converter/AbstractConverter.php index 6f2df7a66..14e37f4f3 100644 --- a/sources/Forms/Converter/AbstractOutputConverter.php +++ b/sources/Forms/Block/IO/Converter/AbstractConverter.php @@ -4,12 +4,12 @@ * @license http://opensource.org/licenses/AGPL-3.0 */ -namespace Combodo\iTop\Forms\Converter; +namespace Combodo\iTop\Forms\Block\IO\Converter; /** * Output converter. */ -abstract class AbstractOutputConverter +abstract class AbstractConverter { /** * Convert the date to output format. diff --git a/sources/Forms/Block/IO/Converter/OqlToClassConverter.php b/sources/Forms/Block/IO/Converter/OqlToClassConverter.php new file mode 100644 index 000000000..7e934c232 --- /dev/null +++ b/sources/Forms/Block/IO/Converter/OqlToClassConverter.php @@ -0,0 +1,41 @@ +oDestinationIO->SetValues($this->oSourceIO->GetValues()); + } } \ No newline at end of file diff --git a/sources/Forms/Block/IO/FormInput.php b/sources/Forms/Block/IO/FormInput.php index e1eba33fd..fd24744f0 100644 --- a/sources/Forms/Block/IO/FormInput.php +++ b/sources/Forms/Block/IO/FormInput.php @@ -10,27 +10,59 @@ use Combodo\iTop\Forms\Block\FormBlockIOException; class FormInput extends AbstractFormIO { - private FormBinding|null $oBinding = null; + private array $aBindingsToInputs = []; - public function Bind(AbstractFormIO $oSourceIO): void + /** + * @throws FormBlockIOException + */ + public function BindFromOutput(FormOutput $oSourceIO): void { - if($this->GetType() !== $oSourceIO->GetType()){ + if($this->GetDataType() !== $oSourceIO->GetDataType()){ throw new FormBlockIOException('Cannot connect input types incompatibles ' . $this->GetName() . ' from ' . $oSourceIO->GetOwnerBlock()->GetName() . ' ' . $oSourceIO->GetName()); } - $this->oBinding = new FormBinding($this, $oSourceIO); + $this->oBinding = $oSourceIO->BindToInput($this); } - public function GetBinding(): FormBinding + /** + * @throws FormBlockIOException + */ + public function BindFromInput(FormInput $oSourceIO): void + { + if($this->GetDataType() !== $oSourceIO->GetDataType()){ + throw new FormBlockIOException('Cannot connect input types incompatibles ' . $this->GetName() . ' from ' . $oSourceIO->GetOwnerBlock()->GetName() . ' ' . $oSourceIO->GetName()); + } + + $this->oBinding = $oSourceIO->BindToInput($this); + } + + /** + * @throws FormBlockIOException + */ + public function BindToInput(FormInput $oDestinationIO): FormBinding + { + if($this->GetDataType() !== $oDestinationIO->GetDataType()){ + throw new FormBlockIOException('Cannot connect input types incompatibles ' . $this->GetName() . ' from ' . $oDestinationIO->GetOwnerBlock()->GetName() . ' ' . $oDestinationIO->GetName()); + } + + $oBinding = new FormBinding($this, $oDestinationIO); + + $this->aBindingsToInputs[] = $oBinding; + + return $oBinding; + } + + + public function GetBinding(): ?FormBinding { return $this->oBinding; } - public function IsDataReady(string $sEventType): bool + public function IsDataReady(): bool { - return $this->oBinding->oSourceIO->HasValue($sEventType); + return $this->HasValue(); } public function IsBound(): bool @@ -38,4 +70,25 @@ class FormInput extends AbstractFormIO return $this->oBinding !== null; } + public function SetValues(array $aValues): AbstractFormIO + { + parent::SetValues($aValues); + + $this->PropagateBindingsValues(); + + return $this; + } + + /** + * Propagate the bindings values. + * + * @return void + */ + public function PropagateBindingsValues(): void + { + // propagate the value + foreach ($this->aBindingsToInputs as $oBinding) { + $oBinding->PropagateValues(); + } + } } \ No newline at end of file diff --git a/sources/Forms/Block/IO/FormOutput.php b/sources/Forms/Block/IO/FormOutput.php index 4b3b9f7ef..b8235f5c5 100644 --- a/sources/Forms/Block/IO/FormOutput.php +++ b/sources/Forms/Block/IO/FormOutput.php @@ -6,20 +6,42 @@ namespace Combodo\iTop\Forms\Block\IO; -use Combodo\iTop\Forms\Converter\AbstractOutputConverter; -use Symfony\Component\Form\FormEvents; +use Combodo\iTop\Forms\Block\FormBlockIOException; +use Combodo\iTop\Forms\Block\IO\Converter\AbstractConverter; class FormOutput extends AbstractFormIO { - private null|AbstractOutputConverter $oConverter; - private array $aValues = []; + /** @var AbstractConverter|null */ + private null|AbstractConverter $oConverter; - public function __construct(string $sName, string $sType, AbstractOutputConverter $oConverter = null) + private FormBinding|null $oBinding = null; + + /** @var array */ + private array $aBindingsToInputs = []; + + /** @var array */ + private array $aBindingsToOutputs = []; + + /** + * Constructor. + * + * @param string $sName + * @param string $sType + * @param AbstractConverter|null $oConverter + */ + public function __construct(string $sName, string $sType, AbstractConverter $oConverter = null) { parent::__construct($sName, $sType); $this->oConverter = $oConverter; } + /** + * Convert the value. + * + * @param mixed $oData + * + * @return mixed + */ public function ConvertValue(mixed $oData): mixed { if (is_null($this->oConverter)) { @@ -28,39 +50,101 @@ class FormOutput extends AbstractFormIO return $this->oConverter->Convert($oData); } - public function UpdateOutputValue(mixed $oData, string $sEventType): void + /** + * Compute the value. + * + * @param string $sEventType + * @param mixed $oData + * + * @return void + */ + public function ComputeValue(string $sEventType, mixed $oData): void { - $this->aValues[$sEventType] = $this->ConvertValue($oData); + $this->SetValue($sEventType, $this->ConvertValue($oData)); + + // propagate the bindings values + $this->PropagateBindingsValues(); } - public function GetValue(string $sEventType): mixed + /** + * Propagate the bindings values. + * + * @return void + */ + public function PropagateBindingsValues(): void { - return $this->aValues[$sEventType] ?? null; - } - - public function Value(): mixed - { - if(array_key_exists(FormEvents::POST_SUBMIT, $this->aValues) ){ - return $this->aValues[FormEvents::POST_SUBMIT]; - } - if(array_key_exists(FormEvents::POST_SET_DATA, $this->aValues) ){ - return $this->aValues[FormEvents::POST_SET_DATA]; + // propagate the value + foreach ($this->aBindingsToInputs as $oBinding) { + $oBinding->PropagateValues(); + } + + // propagate the value + foreach ($this->aBindingsToOutputs as $oBinding) { + $oBinding->PropagateValues(); } - return null; } - public function HasValue(string $sEventType): bool + /** + * Bind to input. + * + * @param FormInput $oDestinationIO + * + * @return FormBinding + */ + public function BindToInput(FormInput $oDestinationIO): FormBinding { - return array_key_exists($sEventType, $this->aValues) && $this->aValues[$sEventType] !== null; + $oBinding = new FormBinding($this, $oDestinationIO); + + $this->aBindingsToInputs[] = $oBinding; + + return $oBinding; } - public function HasValues(): bool + /** + * Bind to output. + * + * @param FormOutput $oDestinationIO + * + * @return FormBinding + */ + public function BindToOutput(FormOutput $oDestinationIO): FormBinding { - return count($this->aValues) > 0; + $oBinding = new FormBinding($this, $oDestinationIO); + + $this->aBindingsToOutputs[] = $oBinding; + + return $oBinding; } - public function GetValues(): array + public function GetBinding(): ?FormBinding { - return $this->aValues; + return $this->oBinding; + } + + /** + * @throws FormBlockIOException + */ + public function BindFromOutput(FormOutput $oSourceIO): void + { + if($this->GetDataType() !== $oSourceIO->GetDataType()){ + throw new FormBlockIOException('Cannot connect input types incompatibles ' . $this->GetName() . ' from ' . $oSourceIO->GetOwnerBlock()->GetName() . ' ' . $oSourceIO->GetName()); + } + + $this->oBinding = $oSourceIO->BindToOutput($this); + } + + public function IsBound(): bool + { + return $this->oBinding !== null; + } + + /** + * Get the bindings. + * + * @return array + */ + public function GetBindings(): array + { + return $this->aBindingsToInputs; } } \ No newline at end of file diff --git a/sources/Forms/Block/IO/Format/AttributeIOFormat.php b/sources/Forms/Block/IO/Format/AttributeIOFormat.php index 33cf63735..c62a342b5 100644 --- a/sources/Forms/Block/IO/Format/AttributeIOFormat.php +++ b/sources/Forms/Block/IO/Format/AttributeIOFormat.php @@ -2,7 +2,9 @@ namespace Combodo\iTop\Forms\Block\IO\Format; -class AttributeIOFormat +use JsonSerializable; + +class AttributeIOFormat implements JsonSerializable { public function __construct(public string $sAttributeName) { @@ -13,4 +15,9 @@ class AttributeIOFormat { return $this->sAttributeName; } + + public function jsonSerialize(): mixed + { + return $this->sAttributeName; + } } \ No newline at end of file diff --git a/sources/Forms/Block/IO/Format/ClassIOFormat.php b/sources/Forms/Block/IO/Format/ClassIOFormat.php index 982e31e44..f8d9b64bf 100644 --- a/sources/Forms/Block/IO/Format/ClassIOFormat.php +++ b/sources/Forms/Block/IO/Format/ClassIOFormat.php @@ -2,7 +2,9 @@ namespace Combodo\iTop\Forms\Block\IO\Format; -class ClassIOFormat +use JsonSerializable; + +class ClassIOFormat implements JsonSerializable { public function __construct(public string $sClassName) { @@ -13,4 +15,9 @@ class ClassIOFormat { return $this->sClassName; } + + public function jsonSerialize(): mixed + { + return $this->sClassName; + } } \ No newline at end of file diff --git a/sources/Forms/Block/IO/Format/RawFormat.php b/sources/Forms/Block/IO/Format/RawFormat.php new file mode 100644 index 000000000..04ab08f97 --- /dev/null +++ b/sources/Forms/Block/IO/Format/RawFormat.php @@ -0,0 +1,16 @@ +oValue); + } +} \ No newline at end of file diff --git a/sources/Forms/Converter/OqlToClassConverter.php b/sources/Forms/Converter/OqlToClassConverter.php deleted file mode 100644 index ed29150f6..000000000 --- a/sources/Forms/Converter/OqlToClassConverter.php +++ /dev/null @@ -1,25 +0,0 @@ -aDependentBlocks = $aDependentBlocks; - $this->oFormBuilder = $oFormBuilder; + // dependencies map + $this->oDependenciesMap = new DependencyMap($this->aDependentBlocks); - // add form ready listener + // Add form ready listener $this->AddFormReadyListener(); + + // Check the dependencies + $this->CheckDependencies($this->oFormBuilder); + + self::$aDependencyHandlers[] = $this; + } + + /** + * Return the form block. + * + * @return AbstractFormBlock + */ + public function GetFormBlock(): AbstractFormBlock + { + return $this->oFormBlock; } /** @@ -52,11 +74,20 @@ class DependencyHandler // Initialize the dependencies listeners once the form is built $this->oFormBuilder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) { - // dependencies map - $this->aDependenciesMap = new DependencyMap($this->aDependentBlocks); - /** Iterate throw listened blocks */ - foreach ($this->aDependenciesMap->GetListenedBlockNames() as $sOutputBlockName) { + foreach ($this->oDependenciesMap->GetListenedOutputBlockNames() as $sOutputBlockName) { + + // inner binding + if($sOutputBlockName === $this->oFormBlock->getName()) { + continue; + } + + $this->aDebugData[] = [ + 'builder' => $this->oFormBuilder->getName(), + 'event' => 'form.listen', + 'form' => $sOutputBlockName, + 'value' => null + ]; // Listen the output block POST_SET_DATA & POST_SUBMIT $this->oFormBuilder->get($sOutputBlockName)->addEventListener(FormEvents::POST_SET_DATA, $this->GetEventListeningCallback()); @@ -65,7 +96,6 @@ class DependencyHandler }); } - /** * Get the listening callback. * @@ -78,52 +108,108 @@ class DependencyHandler // Get the event type $sEventType = FormHelper::GetEventType($oEvent); + $this->aDebugData[] = [ + 'builder' => $this->oFormBuilder->getName(), + 'event' => $sEventType, + 'form' => $oEvent->getForm()->getName(), + 'value' => $oEvent->getData() + ]; + // Get the form $oForm = $oEvent->getForm(); - /** Iterate throw dependencies map... */ - $sOutputBlockName = $oForm->getName(); - if($this->aDependenciesMap->IsBlockHasOutputs($sOutputBlockName)){ - $oOutputBlock = $this->oFormBuilder->GetFormBlock($sOutputBlockName); - foreach ($this->aDependenciesMap->GetOutputsForBlock($sOutputBlockName) as $sOutputName) { - $oOutput = $oOutputBlock->GetOutput($sOutputName); - $oOutput->UpdateOutputValue($oEvent->getData(), $sEventType); - } + // Get the form block + $oFormBlock = $this->aSubBlocks[$oForm->getName()]; + + // Compute the block outputs with the data + if(!$oFormBlock instanceof FormBlock) { + $oFormBlock->ComputeOutputs($sEventType, $oEvent->getData()); } - - - foreach ($this->aDependentBlocks as $oDependentBlock) - { - - // When dependencies met, add the dependent field if not already done - if(!$oDependentBlock->IsAdded() && $oDependentBlock->IsInputsReady($sEventType)) { - - // Get the dependent field options - $aOptions = $oDependentBlock->UpdateOptions(); - - // Add the listener callback to the dependent field if it is also a dependency for another field - if($this->aDependenciesMap->IsTheBlockInDependencies($oDependentBlock->getName())) { - $aOptions = array_merge($aOptions, [ - 'listener_callback' => $this->GetEventListeningCallback(), - ]); - } - - // Mark the dependency as added - $oDependentBlock->SetAdded(true); - - // Add the dependent field to the form - $oForm->getParent()->add($oDependentBlock->GetName(), $oDependentBlock->GetFormType(), $aOptions); - - - } - - } - - + // Check dependencies + $this->CheckDependencies($oForm->getParent()); }; } + /** + * @param FormInterface|FormBuilderInterface $oForm + * + * @return void + */ + private function CheckDependencies(FormInterface|FormBuilderInterface $oForm): void + { + /** Iterate throw dependencies... @var AbstractFormBlock $oDependentBlock */ + foreach ($this->aDependentBlocks as $qBlockName => $oDependentBlock) + { + // When dependencies met, add the dependent field if not already done + if(!$oDependentBlock->IsAdded() && $oDependentBlock->IsInputsReady()) { + + // Get the dependent field options + $aOptions = $oDependentBlock->UpdateOptions(); + + // Add the listener callback to the dependent field if it is also a dependency for another field + if($this->oDependenciesMap->IsTheBlockInDependencies($oDependentBlock->getName())) { + + // Pass the listener call back to be registered by the dependency form builder + $aOptions = array_merge($aOptions, [ + 'builder_listener' => $this->GetEventListeningCallback(), + ]); + } + + if($oDependentBlock->AllowAdd()) { + + $this->aDebugData[] = [ + 'builder' => $this->oFormBuilder->getName(), + 'event' => 'form.add', + 'form' => $oDependentBlock->getName(), + 'value' => null + ]; + + // Mark the dependency as added + $oDependentBlock->SetAdded(true); + + // Add the dependent field to the form + $oForm->add($oDependentBlock->GetName(), $oDependentBlock->GetFormType(), $aOptions); + + } + + } + + if($oDependentBlock->IsAdded() && !$oDependentBlock->IsInputsReady()) { + $oForm->add($oDependentBlock->GetName(), HiddenType::class, [ + 'form_block' => $oDependentBlock, + 'prevent_form_build' => true, + ]); + } + + } + + } + + /** + * Get the debug data. + * + * @return array + */ + public function GetDebugData(): array + { + return $this->aDebugData; + } + + public function GetMap(): DependencyMap + { + return $this->oDependenciesMap; + } + + public function GetSubBlocks(): array + { + return $this->aSubBlocks; + } + + public function GetName(): string + { + return $this->sName; + } } \ No newline at end of file diff --git a/sources/Forms/FormBuilder/DependencyMap.php b/sources/Forms/FormBuilder/DependencyMap.php index 0ad376ad5..5757bdda8 100644 --- a/sources/Forms/FormBuilder/DependencyMap.php +++ b/sources/Forms/FormBuilder/DependencyMap.php @@ -8,6 +8,8 @@ namespace Combodo\iTop\Forms\FormBuilder; use Combodo\iTop\Forms\Block\FormBlock; use Combodo\iTop\Forms\Block\IO\FormBinding; +use Combodo\iTop\Forms\Block\IO\FormInput; +use Combodo\iTop\Forms\Block\IO\FormOutput; /** * Dependencies handler. @@ -15,8 +17,14 @@ use Combodo\iTop\Forms\Block\IO\FormBinding; */ class DependencyMap { - /** @var array dependencies map */ - private array $aDependenciesMap; + /** @var array output to outputs map map */ + private array $aOutputToInputsMap = []; + + /** @var array input to inputs map map */ + private array $aInputToInputsMap = []; + + /** @var array output to outputs */ + private array $aOutputToOutputsMap = []; /** * Constructor. @@ -36,38 +44,85 @@ class DependencyMap */ private function Init(): void { - /** iterate throw dependents blocks... @var FormBlock $oDependentBlock */ - foreach ($this->aDependentBlocks as $oDependentBlock) { + /** Iterate throw blocks with dependencies... @var FormBlock $oDependentBlock */ + foreach ($this->aDependentBlocks as $sBlockName => $oDependentBlock) { - /** iterate throw the block inputs connections... @var FormBinding $oBinding**/ - foreach ($oDependentBlock->GetInputsBindings() as $sInputName => $oBinding) { + /** Iterate throw the block inputs connections... @var FormBinding $oBinding**/ + foreach ($oDependentBlock->GetInputsBindings() as $oBinding) { - // connection information - $sOutputBlockName = $oBinding->oSourceIO->GetOwnerBlock()->GetName(); - $sOutputName = $oBinding->oSourceIO->GetName(); - - // initialize map - if (!isset($this->aDependenciesMap[$sOutputBlockName])) { - $this->aDependenciesMap[$sOutputBlockName] = []; + // Output to inputs map + if($oBinding->oSourceIO instanceof FormOutput + && $oBinding->oDestinationIO instanceof FormInput){ + $this->AddBindingToMap($this->aOutputToInputsMap, $oBinding); } - if (!isset($this->aDependenciesMap[$sOutputBlockName][$sOutputName])) { - $this->aDependenciesMap[$sOutputBlockName][$sOutputName] = []; + // Input to inputs map + if($oBinding->oSourceIO instanceof FormInput + && $oBinding->oDestinationIO instanceof FormInput){ + $this->AddBindingToMap($this->aInputToInputsMap, $oBinding); + } + // Output to outputs map + if($oBinding->oSourceIO instanceof FormOutput + && $oBinding->oDestinationIO instanceof FormOutput){ + $this->AddBindingToMap($this->aOutputToOutputsMap, $oBinding); } - // add to map - $this->aDependenciesMap[$sOutputBlockName][$sOutputName][] = $oBinding; + } + + /** Iterate throw the block inputs connections... @var FormBinding $oBinding**/ + foreach ($oDependentBlock->GetOutputBindings() as $oBinding) { + + // Output to outputs map + if($oBinding->oSourceIO instanceof FormOutput + && $oBinding->oDestinationIO instanceof FormOutput){ + $this->AddBindingToMap($this->aOutputToOutputsMap, $oBinding); + } } } + return; + } + + /** + * Add a binding to a map. + * + * @param array $map + * @param FormBinding $oBinding + * + * @return void + */ + private function AddBindingToMap(array &$map, FormBinding $oBinding): void + { + // Binding information + $sBlockName = $oBinding->oSourceIO->GetOwnerBlock()->GetName(); + $sIOName = $oBinding->oSourceIO->GetName(); + + // initialize map + if (!isset($map[$sBlockName])) { + $map[$sBlockName] = []; + } + if (!isset($map[$sBlockName][$sIOName])) { + $map[$sBlockName][$sIOName] = []; + } + + // add to map + $map[$sBlockName][$sIOName][] = $oBinding; } /** * @return array */ - public function GetListenedBlockNames(): array + public function GetListenedOutputBlockNames(): array { - return array_keys($this->aDependenciesMap); + $aResult = []; + + foreach(array_keys($this->aOutputToInputsMap) as $sOutputBlockName) { + if(!array_key_exists($sOutputBlockName, $this->aDependentBlocks)){ + $aResult[] = $sOutputBlockName; + } + } + + return $aResult; } /** @@ -77,7 +132,7 @@ class DependencyMap */ public function IsBlockHasOutputs(string $sBlockName): bool { - return array_key_exists($sBlockName, $this->aDependenciesMap); + return array_key_exists($sBlockName, $this->aOutputToInputsMap); } /** @@ -87,12 +142,12 @@ class DependencyMap */ public function GetOutputsForBlock(string $sBlockName): array { - return array_keys($this->aDependenciesMap[$sBlockName]); + return array_keys($this->aOutputToInputsMap[$sBlockName]); } public function GetOutputsDependenciesForBlock(string $sOutputBlockName): array { - return $this->aDependenciesMap[$sOutputBlockName]; + return $this->aOutputToInputsMap[$sOutputBlockName]; } public function IsTheBlockInDependencies(string $sBlockName): bool @@ -106,4 +161,19 @@ class DependencyMap return false; } + + public function GetOutputToInputs(): array + { + return $this->aOutputToInputsMap; + } + + public function GetInputToInputs(): array + { + return $this->aInputToInputsMap; + } + + public function GetOutputToOutputs(): array + { + return $this->aOutputToOutputsMap; + } } \ No newline at end of file diff --git a/sources/Forms/FormBuilder/FormBuilder.php b/sources/Forms/FormBuilder/FormBuilder.php index 40c011c0f..39089e1e0 100644 --- a/sources/Forms/FormBuilder/FormBuilder.php +++ b/sources/Forms/FormBuilder/FormBuilder.php @@ -7,6 +7,7 @@ namespace Combodo\iTop\Forms\FormBuilder; use Combodo\iTop\Forms\Block\AbstractFormBlock; +use Combodo\iTop\Forms\Block\Base\FormBlock; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Form\DataMapperInterface; @@ -23,57 +24,135 @@ use Traversable; class FormBuilder implements FormBuilderInterface, \IteratorAggregate { + /** @var DependencyHandler|null */ private ?DependencyHandler $oDependencyHandler = null; - private array $aFormBlocks = []; + + /** @var AbstractFormBlock */ + private AbstractFormBlock $oFormBlock; + + /** @var array sub blocks */ + private array $aSubFormBlocks = []; /** * Constructor. * * @param FormBuilderInterface $builder + * */ - public function __construct(private FormBuilderInterface $builder) + public function __construct(private readonly FormBuilderInterface $builder) { - $this->InitFormBlocks(); + /** Get the corresponding form block @var AbstractFormBlock $oFormBlock */ + $oFormBlock = $this->builder->getOption('form_block'); + + // Build the form + if($oFormBlock instanceof FormBlock) { + $this->BuildForm($oFormBlock); + } } + /** + * Build the form. + * + * @param FormBlock $oFormBlock + * + * @return void + */ + private function BuildForm(FormBlock $oFormBlock): void + { + // Hidden (ignore) + $aOptions = $this->builder->getOptions(); + if(array_key_exists('prevent_form_build', $aOptions) && $aOptions['prevent_form_build']) { + return; + } + + $aDependentBlocks = []; + /** Iterate throw the form sub blocks... @var FormBlock $oSubFormBlock */ + foreach ($oFormBlock->GetSubFormBlocks() as $oSubFormBlock) { + + // Add to the sub blocks array + $this->aSubFormBlocks[$oSubFormBlock->getName()] = $oSubFormBlock; + + // Handle sub block + $bHasDependency = $this->HandleSubBlock($oSubFormBlock); + + // Add to the dependencies array + if($bHasDependency){ + $aDependentBlocks[$oSubFormBlock->GetName()] = $oSubFormBlock; + } + + } + + // Create a dependency handler if needed + if (count($aDependentBlocks) > 0) { + $this->oDependencyHandler = new DependencyHandler($this->builder->getName(), $oFormBlock, $this, $this->aSubFormBlocks, $aDependentBlocks); + } + } + + /** + * Add a sub block. + * + * @param AbstractFormBlock $oSubFormBlock + * + * @return bool + */ + private function HandleSubBlock(AbstractFormBlock $oSubFormBlock): bool + { + + // Has at least one bounded input ? + if ($oSubFormBlock->HasAtLeastOneBoundInput()) { + + // Insert a hidden type to save the place + $this->builder->add($oSubFormBlock->GetName(), HiddenType::class, [ + 'form_block' => $oSubFormBlock, + 'prevent_form_build' => true, +// 'mapped' => false, +// 'disabled' => true, + ]); + + return true; + + } + else { + + // Directly insert the block corresponding form type + $this->add($oSubFormBlock->GetName(), $oSubFormBlock->GetFormType(), $oSubFormBlock->UpdateOptions()); + $oSubFormBlock->SetAdded(true); + + return false; + + } + } + + /** + * Get a sub form block. + * + * @param string $sName + * + * @return AbstractFormBlock|null + */ + public function GetSubFormBlock(string $sName): ?AbstractFormBlock + { + return $this->aSubFormBlocks[$sName] ?? null; + } + + /** + * Return the dependency handler attached to this builder. + * + * @return DependencyHandler|null + */ + protected function GetDependencyHandler(): ?DependencyHandler + { + return $this->oDependencyHandler; + } + + // pure decoration of FormBuilderInterface + public function add(string|FormBuilderInterface $child, ?string $type = null, array $options = []): static { $this->builder->add($child, $type, $options); return $this; } - private function InitFormBlocks() - { - $oFormBlock = $this->builder->getOption('form_block'); - if (is_null($oFormBlock)) { - return; - } - - $aDependentBlocks = []; - /** @var \Combodo\iTop\Forms\Block\FormBlock $oSubFormBlock */ - foreach ($oFormBlock->GetSubFormBlocks() as $oSubFormBlock) { - $this->aFormBlocks[$oSubFormBlock->getName()] = $oSubFormBlock; - if ($oSubFormBlock->HasConnections()) { - $this->builder->add($oSubFormBlock->GetName(), HiddenType::class); - $aDependentBlocks[] = $oSubFormBlock; - } else { - $this->add($oSubFormBlock->GetName(), $oSubFormBlock->GetFormType(), $oSubFormBlock->UpdateOptions()); - $oSubFormBlock->SetAdded(true); - } - } - - if (count($aDependentBlocks) > 0) { - $this->oDependencyHandler = new DependencyHandler($this, $aDependentBlocks); - } - } - - public function GetFormBlock(string $sName): ?AbstractFormBlock - { - return $this->aFormBlocks[$sName] ?? null; - } - - // pure decoration of FormBuilderInterface - public function getIterator(): Traversable { return $this->builder->getIterator(); diff --git a/sources/Forms/FormBuilder/FormHelper.php b/sources/Forms/FormBuilder/FormHelper.php index b5306f656..bc6dd45c9 100644 --- a/sources/Forms/FormBuilder/FormHelper.php +++ b/sources/Forms/FormBuilder/FormHelper.php @@ -4,6 +4,7 @@ namespace Combodo\iTop\Forms\FormBuilder; use Symfony\Component\Form\Event\PostSetDataEvent; use Symfony\Component\Form\Event\PostSubmitEvent; +use Symfony\Component\Form\Event\PreSubmitEvent; use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\FormEvents; @@ -24,6 +25,9 @@ class FormHelper } else if ($event instanceof PostSubmitEvent) { return FormEvents::POST_SUBMIT; } + else if ($event instanceof PreSubmitEvent) { + return FormEvents::PRE_SUBMIT; + } throw new FormBuilderException(sprintf('Unknown event type %s', get_class($event))); } diff --git a/sources/Forms/FormBuilder/FormTypeExtension.php b/sources/Forms/FormBuilder/FormTypeExtension.php index 5eb6aea4e..26a0a7dc4 100644 --- a/sources/Forms/FormBuilder/FormTypeExtension.php +++ b/sources/Forms/FormBuilder/FormTypeExtension.php @@ -6,6 +6,7 @@ namespace Combodo\iTop\Forms\FormBuilder; +use Combodo\iTop\Forms\Block\FormBlock; use Symfony\Component\Form\AbstractTypeExtension; use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\FormBuilderInterface; @@ -14,9 +15,14 @@ use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormView; use Symfony\Component\OptionsResolver\OptionsResolver; +/** + * Extension for form types. + * + */ class FormTypeExtension extends AbstractTypeExtension { + /** @inheritdoc */ public static function getExtendedTypes(): iterable { return [ @@ -24,27 +30,34 @@ class FormTypeExtension extends AbstractTypeExtension ]; } + /** @inheritdoc */ public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefined([ 'form_block', - 'listener_callback', + 'builder_listener', + 'prevent_form_build' ]); } + /** @inheritdoc */ public function buildForm(FormBuilderInterface $builder, array $options): void { - if(array_key_exists('listener_callback', $options)) { - $builder->addEventListener(FormEvents::POST_SET_DATA, $options['listener_callback']); - $builder->addEventListener(FormEvents::POST_SUBMIT, $options['listener_callback']); + if(array_key_exists('builder_listener', $options)) { + $builder->addEventListener(FormEvents::POST_SET_DATA, $options['builder_listener']); + $builder->addEventListener(FormEvents::POST_SUBMIT, $options['builder_listener']); } } + /** @inheritdoc */ public function buildView(FormView $view, FormInterface $form, array $options): void { if(array_key_exists('form_block', $options)) { $view->vars['form_block'] = $options['form_block']; + + $oFormBlock = $options['form_block']; + $view->vars['trigger_form_submit_on_modify'] = $oFormBlock->HasAtLeastOneBoundOutput(); } } diff --git a/sources/Forms/FormType/AttributeFormType.php b/sources/Forms/FormType/AttributeFormType.php new file mode 100644 index 000000000..0de0fc84e --- /dev/null +++ b/sources/Forms/FormType/AttributeFormType.php @@ -0,0 +1,18 @@ +addEventListener(FormEvents::PRE_SUBMIT, function (PreSubmitEvent $event) use ($options){ - - // reset value if not in available choices - if(!empty($event->getData()) && !$this->CheckValue($event->getData(), $options)){ - $event->getForm()->addError(new FormError("The value has been reset because it is not part of the available choices anymore.")); - $event->setData(null); - } - - }, 1); - } - - private function CheckValue($oValue, $options): bool - { - if(!is_array($oValue)){ - return false; - } - - foreach ($oValue as $v){ - if(!in_array($v, $options['choices'])){ - return false; - } - } - - return true; - } - - public static function GetOptionsFromInputs(array $inputs): array - { - $aValues = []; - - if(!empty($inputs['attribute'])){ - $oAttDef = \MetaModel::GetAttributeDef($inputs['object_class'], $inputs['attribute']); - $aValues = $oAttDef->GetAllowedValues(); - $aValues = $aValues !== null ? array_combine($aValues, $aValues) : []; - } - - return [ - 'choices' => $aValues - ]; - } -} \ No newline at end of file diff --git a/sources/Forms/FormType/AttributeValueFormType.php b/sources/Forms/FormType/AttributeValueFormType.php new file mode 100644 index 000000000..a41b98c00 --- /dev/null +++ b/sources/Forms/FormType/AttributeValueFormType.php @@ -0,0 +1,32 @@ +GetAllowedValues(); + $aValues = $aValues !== null ? array_combine($aValues, $aValues) : []; + } + + return [ + 'choices' => $aValues + ]; + } +} \ No newline at end of file diff --git a/sources/Forms/FormType/AttributeChoiceType.php b/sources/Forms/FormType/ChoiceFormType.php similarity index 71% rename from sources/Forms/FormType/AttributeChoiceType.php rename to sources/Forms/FormType/ChoiceFormType.php index 0bf2f81db..34c4a124e 100644 --- a/sources/Forms/FormType/AttributeChoiceType.php +++ b/sources/Forms/FormType/ChoiceFormType.php @@ -12,15 +12,19 @@ use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormError; use Symfony\Component\Form\FormEvents; -use Symfony\Component\OptionsResolver\OptionsResolver; -class AttributeChoiceType extends AbstractType +/** + * + */ +class ChoiceFormType extends AbstractType { + /** @inheritdoc */ public function getParent(): string { return ChoiceType::class; } + /** @inheritdoc */ public function buildForm(FormBuilderInterface $builder, array $options): void { // on pre submit @@ -35,13 +39,28 @@ class AttributeChoiceType extends AbstractType }, 1); } + /** + * @param $oValue + * @param $options + * + * @return bool + */ private function CheckValue($oValue, $options): bool { - - if(!in_array($oValue, $options['choices'])){ - return false; + // Check multi selection values + if(is_array($oValue)){ + foreach ($oValue as $v){ + if(!in_array($v, $options['choices'])){ + return false; + } + } + } + // Check single selection values + else{ + if(!in_array($oValue, $options['choices'])){ + return false; + } } - return true; } diff --git a/sources/Forms/FormType/OqlFormType.php b/sources/Forms/FormType/OqlFormType.php new file mode 100644 index 000000000..f807afe18 --- /dev/null +++ b/sources/Forms/FormType/OqlFormType.php @@ -0,0 +1,75 @@ +setDefault('help', 'An OQL query expression'); + + $resolver->setDefault('attr', [ + 'placeholder' => 'SELECT Contact', + ]); + + $resolver->setDefault('outputs', array( + 'selected_class' => function($oData) { + if($oData === null) + return null; + // extract selected class + preg_match('/SELECT\s+(\w+)/', $oData, $aMatches); + return $aMatches[1] ?? null; + } + )); + + $resolver->setDefined('with_ai_button'); + } + + /** @inheritdoc */ + public function buildForm(FormBuilderInterface $builder, array $options): void + { + parent::buildForm($builder, $options); + + // on pre submit + $builder->addEventListener(FormEvents::POST_SUBMIT, function (PostSubmitEvent $event) use ($options){ + + try{ + $oClassConverter = new OqlToClassConverter(); + $oClassConverter->Convert($event->getData()); + } + catch(Exception $e){ + $event->getForm()->addError(new FormError($e->getMessage())); + } + + }); + } + + /** @inheritdoc */ + public function buildView(FormView $view, FormInterface $form, array $options): void + { + parent::buildView($view, $form, $options); + + $view->vars['with_ai_button'] = $options['with_ai_button']; + } +} \ No newline at end of file diff --git a/sources/Forms/FormType/OqlType.php b/sources/Forms/FormType/OqlType.php deleted file mode 100644 index ce8226971..000000000 --- a/sources/Forms/FormType/OqlType.php +++ /dev/null @@ -1,33 +0,0 @@ -setDefault('outputs', array( - 'selected_class' => function($oData) { - if($oData === null) - return null; - // extract selected class - preg_match('/SELECT\s+(\w+)/', $oData, $aMatches); - return $aMatches[1] ?? null; - } - )); - } - -} \ No newline at end of file diff --git a/templates/application/forms/itop_console_layout.html.twig b/templates/application/forms/itop_console_layout.html.twig index f5f21a381..6d6162e55 100644 --- a/templates/application/forms/itop_console_layout.html.twig +++ b/templates/application/forms/itop_console_layout.html.twig @@ -6,7 +6,9 @@ {% if type == 'text' %}{% set ibo_class='ibo-input-string' %}{% else %}{% set ibo_class='ibo-input-' ~ type %}{% endif %} {% set attr = attr|merge({class: (attr.class|default('') ~ ' ibo-input ' ~ ibo_class)|trim}) %} {{- parent() -}} - onChange="this.form.requestSubmit();console.log('Auto submitting form due to change in field {{ full_name }}');" + {% if trigger_form_submit_on_modify %} + onChange="this.form.requestSubmit();console.log('Auto submitting form due to change in field {{ full_name }}');" + {% endif %} {%- endblock widget_attributes -%} {%- block form_label -%} @@ -18,28 +20,23 @@ {{- parent() -}} {%- endblock form_label -%} +{%- block form_label_content -%} + {{- parent() -}} + {% if with_ai_button is defined and with_ai_button %} +