diff --git a/src/UI/Component/Input/Field/DynamicGroup.php b/src/UI/Component/Input/Field/DynamicGroup.php new file mode 100644 index 000000000000..3541c5d380c7 --- /dev/null +++ b/src/UI/Component/Input/Field/DynamicGroup.php @@ -0,0 +1,28 @@ + + * Dynamic groups are used to add (or remove) inputs to forms when the + * desired amount of fields cannot be determined initially. + * composition: > + * Dynamic groups are composed of other inputs, with the addition of + * glyphs to add or remove more inputs. + * effect: > + * Clicking the add glyph, a new input will be added to the group. + * This might well be an entire group of inputs, too. + * Added inputs may be removed by clicking the respective remove glyph. + * + * rules: + * usage: + * 1: X + * + * context: + * - Dynamic groups are used in UI Forms. + * + * --- + * @return \ILIAS\UI\Component\Input\Field\DynamicGroup + */ + public function dynamicGroup( + FormInput $template, + string $label, + ?string $byline = null + ): DynamicGroup; } diff --git a/src/UI/Implementation/Component/Input/Field/DynamicGroup.php b/src/UI/Implementation/Component/Input/Field/DynamicGroup.php new file mode 100644 index 000000000000..daaa51e5552d --- /dev/null +++ b/src/UI/Implementation/Component/Input/Field/DynamicGroup.php @@ -0,0 +1,87 @@ +requirement_constraint !== null) { + return $this->requirement_constraint; + } + + return $this->refinery->custom()->constraint( + function ($value) { + return (is_array($value) && count($value) > 0); + }, + function ($txt, $value) { + return $txt("dyngroup_required_fields"); + }, + ); + } + + protected function isClientSideValueOk($value): bool + { + if (!is_array($value)) { + return false; + } + foreach ($value as $data) { + if ($this->hasMetadataInputs()) { + if (!$this->dynamic_input_template->isClientSideValueOk($data)) { + return false; + } + } + } + + return true; + } +} diff --git a/src/UI/Implementation/Component/Input/Field/Factory.php b/src/UI/Implementation/Component/Input/Field/Factory.php index c8007c98f295..8a9922657b9a 100644 --- a/src/UI/Implementation/Component/Input/Field/Factory.php +++ b/src/UI/Implementation/Component/Input/Field/Factory.php @@ -235,4 +235,20 @@ public function markdown(I\MarkdownRenderer $md_renderer, string $label, string { return new Markdown($this->data_factory, $this->refinery, $md_renderer, $label, $byline); } + + public function dynamicGroup( + FormInput $template, + string $label, + ?string $byline = null + ): I\DynamicGroup { + return new DynamicGroup( + $this->lng, + $this->data_factory, + $this->refinery, + $template, + $label, + $byline + ); + } + } diff --git a/src/UI/Implementation/Component/Input/Field/Renderer.php b/src/UI/Implementation/Component/Input/Field/Renderer.php index 897fc27fa459..fbe9bc3fdc73 100644 --- a/src/UI/Implementation/Component/Input/Field/Renderer.php +++ b/src/UI/Implementation/Component/Input/Field/Renderer.php @@ -150,6 +150,9 @@ public function render(Component\Component $component, RendererInterface $defaul case ($component instanceof F\ColorPicker): return $this->renderColorPickerField($component); + case ($component instanceof F\DynamicGroup): + return $this->renderDynamicGroup($component, $default_renderer); + default: throw new LogicException("Cannot render '" . get_class($component) . "'"); } @@ -448,7 +451,7 @@ public function renderSelectField(F\Select $component): string $tpl->setVariable("HIDDEN", "hidden"); } - if(!($value && $component->isRequired())) { + if (!($value && $component->isRequired())) { $tpl->setVariable("VALUE", null); $tpl->setVariable("VALUE_STR", $component->isRequired() ? $this->txt('ui_select_dropdown_label') : '-'); $tpl->parseCurrentBlock(); @@ -912,6 +915,7 @@ protected function getComponentInterfaceName(): array Component\Input\Field\Hidden::class, Component\Input\Field\ColorPicker::class, Component\Input\Field\Markdown::class, + Component\Input\Field\DynamicGroup::class, ]; } @@ -1068,4 +1072,66 @@ protected function renderColorPickerField(F\ColorPicker $component): string return $this->wrapInFormContext($component, $tpl->get()); } + + protected function renderDynamicGroup(F\DynamicGroup $component, RendererInterface $default_renderer): string + { + $tpl = $this->getTemplate('tpl.dyngroup.html', true, true); + + foreach ($component->getDynamicInputs() as $input) { + //render the inputs/values + $tpl->setCurrentBlock('block_group'); + $tpl->setVariable('INPUTS', $default_renderer->render($input)); + $tpl->setVariable('CONTROLS_REMOVE', $default_renderer->render( + $this->getUIFactory()->symbol()->glyph()->close()->withAction("#") + )); + $tpl->parseCurrentBlock(); + } + + + //render template + $tpl->setCurrentBlock('block_template'); + $tpl->setVariable('INPUTS', $default_renderer->render( + $component->getTemplateForDynamicInputs() + )); + $tpl->setVariable('CONTROLS_REMOVE', $default_renderer->render( + $this->getUIFactory()->symbol()->glyph()->close()->withAction("#") + )); + $tpl->parseCurrentBlock(); + + + $dyn_template = $tpl->get('block_template'); + $component = $this->initClientsideRenderer($component, $dyn_template); + + + $tpl->setVariable( + 'CONTROLS_ADD', + $default_renderer->render( + $this->getUIFactory()->symbol()->glyph()->add() + ->withAction("#") + ->withAdditionalOnLoadCode( + fn($id) => " + $('#$id').click( + function(e) { + const field = e.target.closest('.ui-input-dyngroup'); + const list = field.querySelector('.ui-input-dynamic-inputs-list'); + const tpl = field.querySelector('template'); + const clone = tpl.content.cloneNode(true); + list.appendChild(clone); + } + ); + " + ) + ) + ); + + $js_id = $this->bindJSandApplyId($component, $tpl); + return $this->wrapInFormContext( + $component, + $tpl->get(), + $js_id, + "", + false + ); + } + } diff --git a/src/UI/examples/Input/Field/DynamicGroup/base.php b/src/UI/examples/Input/Field/DynamicGroup/base.php new file mode 100644 index 000000000000..4a390b929497 --- /dev/null +++ b/src/UI/examples/Input/Field/DynamicGroup/base.php @@ -0,0 +1,50 @@ + + * ILIAS shows a form without any inputs; instead, there is an add-glyph. + * When clicking the glyph, a group of inputs appears - this is reapeatable. + * Next to each group of inputs is a close-glyph to remove the input group. + * Saving the form will show all values from all groups, and the form itself + * will display the originally added fields. + * Since the input is set to required, the form will show an error if submitted + * without added fields. + * --- + */ +function base() +{ + global $DIC; + $ui = $DIC->ui()->factory(); + $renderer = $DIC->ui()->renderer(); + $request = $DIC->http()->request(); + + $template = $ui->input()->field()->group([ + $ui->input()->field()->numeric('a number'), + $ui->input()->field()->text('a string'), + $ui->input()->field()->markdown(new \ilUIMarkdownPreviewGUI(), 'a markdown'), + ]); + + $dyn = $ui->input()->field()->dynamicgroup( + $template, + "dyn group", + "add or remove fields" + ) + ->withRequired(true); + + $form = $ui->input()->container()->form()->standard('#', ['dyn' => $dyn]); + + $result = ''; + if ($request->getMethod() == "POST") { + $form = $form->withRequest($request); + $result = $form->getData(); + } + + return + "
" . print_r($result, true) . "