Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UI/Input: Introduce dynamic input group #66

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions src/UI/Component/Input/Field/DynamicGroup.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

/**
* This file is part of ILIAS, a powerful learning management system
* published by ILIAS open source e-Learning e.V.
*
* ILIAS is licensed with the GPL-3.0,
* see https://www.gnu.org/licenses/gpl-3.0.en.html
* You should have received a copy of said license along with the
* source code, too.
*
* If this is not the case or you just want to try ILIAS, you'll find
* us at:
* https://www.ilias.de
* https://github.com/ILIAS-eLearning
*
*********************************************************************/

namespace ILIAS\UI\Component\Input\Field;

/**
* This describes file field.
*/
interface DynamicGroup extends HasDynamicInputs
{
}
31 changes: 31 additions & 0 deletions src/UI/Component/Input/Field/Factory.php
Original file line number Diff line number Diff line change
Expand Up @@ -756,4 +756,35 @@ public function colorPicker(string $label, ?string $byline = null): ColorPicker;
* @return \ILIAS\UI\Component\Input\Field\Markdown
*/
public function markdown(MarkdownRenderer $md_renderer, string $label, string $byline = null): Markdown;


/**
* ---
* description:
* purpose: >
* 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;
}
87 changes: 87 additions & 0 deletions src/UI/Implementation/Component/Input/Field/DynamicGroup.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?php

/**
* This file is part of ILIAS, a powerful learning management system
* published by ILIAS open source e-Learning e.V.
*
* ILIAS is licensed with the GPL-3.0,
* see https://www.gnu.org/licenses/gpl-3.0.en.html
* You should have received a copy of said license along with the
* source code, too.
*
* If this is not the case or you just want to try ILIAS, you'll find
* us at:
* https://www.ilias.de
* https://github.com/ILIAS-eLearning
*
*********************************************************************/
declare(strict_types=1);

namespace ILIAS\UI\Implementation\Component\Input\Field;

use ILIAS\UI\Component\Input\Container\Form\FormInput;
use ILIAS\Data\Factory as DataFactory;
use ILIAS\Refinery\Factory as Refinery;
use ILIAS\UI\Component as C;
use ILIAS\Refinery\Constraint;
use Closure;
use ilLanguage;

class DynamicGroup extends HasDynamicInputsBase implements C\Input\Field\DynamicGroup
{
public function __construct(
protected ilLanguage $language,
protected DataFactory $data_factory,
protected Refinery $refinery,
protected $template,
protected string $label,
protected ?string $byline
) {
parent::__construct(
$language,
$data_factory,
$refinery,
$label,
$template,
$byline
);
}

public function getUpdateOnLoadCode(): Closure
{
return static function () {
};
}

protected function getConstraintForRequirement(): ?Constraint
{
if ($this->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;
}
}
16 changes: 16 additions & 0 deletions src/UI/Implementation/Component/Input/Field/Factory.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
}

}
68 changes: 67 additions & 1 deletion src/UI/Implementation/Component/Input/Field/Renderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -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) . "'");
}
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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,
];
}

Expand Down Expand Up @@ -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
);
}

}
50 changes: 50 additions & 0 deletions src/UI/examples/Input/Field/DynamicGroup/base.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

declare(strict_types=1);

namespace ILIAS\UI\examples\Input\Field\DynamicGroup;

/**
* ---
* expected output: >
* 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
"<pre>" . print_r($result, true) . "</pre><br/>" .
$renderer->render($form);
}
25 changes: 25 additions & 0 deletions src/UI/templates/default/Input/tpl.dyngroup.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<div id="{ID}" class="ui-input-dyngroup">
<template>
<!-- BEGIN block_template -->
<div class="ui-input-dynamic-input">
{INPUTS}
<div class="ui-input-dynamic-input__controls">
<span data-action="remove">{CONTROLS_REMOVE}</span>
</div>
</div>
<!-- END block_template -->
</template>
<div class="ui-input-dynamic-inputs-list">
<!-- BEGIN block_group -->
<div class="ui-input-dynamic-input">
{INPUTS}
<div class="ui-input-dynamic-input__controls">
<span data-action="remove">{CONTROLS_REMOVE}</span>
</div>
</div>
<!-- END block_group -->
</div>

<hr>
{CONTROLS_ADD}
</div>
Loading