diff --git a/components/ILIAS/UI/resources/js/Input/Field/tagInput.js b/components/ILIAS/UI/resources/js/Input/Field/tagInput.js index e5d76e140ab8..ed030503f6b7 100755 --- a/components/ILIAS/UI/resources/js/Input/Field/tagInput.js +++ b/components/ILIAS/UI/resources/js/Input/Field/tagInput.js @@ -18,70 +18,87 @@ * * @author Fabian Schmid * @author Nils Haagen + * @author Richard Klees */ var il = il || {}; il.UI = il.UI || {}; il.UI.Input = il.UI.Input || {}; -(function ($) { - il.UI.Input.tagInput = (function ($) { +(function () { + il.UI.Input.tagInput = (function () { const instances = []; - const init = function (raw_id, config, value) { - let _CONFIG = {}; - const _getSettings = function () { - return { - whitelist: _CONFIG.options, - enforceWhitelist: !_CONFIG.userInput, - duplicates: _CONFIG.allowDuplicates, - maxTags: _CONFIG.maxItems, - originalInputValueFormat: (valuesArr) => valuesArr.map((item) => item.value), + const init = function (id, config) { + let options = []; + if (Array.isArray(config.tags)) { + options = config.tags.map((tag) => ({ + value: encodeURIComponent(tag.trim()), + display: tag, + searchBy: tag + })); + } + else if (typeof config.tags === 'object' && config.tags === null) { + for (let prop in config.tags) { + if (config.tags.hasOwnProperty(prop)) { + options.push({ + value: encodeURIComponent(prop.trim()), + display: config.tags[prop], + searchBy: config.tag[prop] + }); + } + } + } + else { + throw new Error("config.tags needs to be an Array or an object"); + } + + let elem = document.querySelector("#" + id + " .c-field-tag"); + let tagify = new Tagify(elem, + { + whitelist: options, + enforceWhitelist: !config.user_created_tags_allowed, dropdown: { - enabled: _CONFIG.dropdownSuggestionsStartAfter, - maxItems: _CONFIG.dropdownMaxItems, - closeOnSelect: _CONFIG.dropdownCloseOnSelect, - highlightFirst: _CONFIG.highlight, + enabled: config.suggestion_starts_after, + maxItems: 200, + closeOnSelect: false, + highlightFirst: true, + }, + duplicates: false, + delimiters: null, + maxTags: config.max_tags === -1 ? Infinity : config.max_tags, + templates: { + tag: (tagData) => + (` + +
+ ${tagData.display} +
+
`), + dropdownItem: (tagData) => + (`
+ ${tagData.display} +
`) }, + originalInputValueFormat: (values) => values.map((item) => item.value), transformTag(tagData) { if (!tagData.display) { tagData.display = tagData.value; - tagData.value = encodeURI(tagData.value); + tagData.value = encodeURIComponent(tagData.value); } tagData.display = tagData.display .replace(//g, '>'); }, - }; - }; - - // Initialize ID and Configuration - _CONFIG = $.extend(_CONFIG, config); - _CONFIG.id = document.querySelector(`#${raw_id} .c-input__field .c-field-tag__wrapper input`)?.id; - - const settings = _getSettings(); - settings.delimiters = null; - settings.templates = {}; - settings.templates.tag = function (tagData) { - return ` - -
- ${tagData.display} -
-
`; - }; - settings.templates.dropdownItem = function (tagData) { - return `
- ${tagData.display} -
`; - }; - - const input = document.getElementById(_CONFIG.id); - const tagify = new Tagify(input, settings); - - tagify.addTags(value); + } + ); + console.log(config.value); + tagify.addTags(config.value); - instances[raw_id] = tagify; + instances[id] = tagify; }; const getTagifyInstance = function (raw_id) { @@ -92,5 +109,5 @@ il.UI.Input = il.UI.Input || {}; init, getTagifyInstance, }; - }($)); -}($, il.UI.Input)); + }()); +}(il.UI.Input)); diff --git a/components/ILIAS/UI/src/Component/Input/Field/Rating.php b/components/ILIAS/UI/src/Component/Input/Field/Rating.php index ab22ae88f992..6b48e795ecfe 100644 --- a/components/ILIAS/UI/src/Component/Input/Field/Rating.php +++ b/components/ILIAS/UI/src/Component/Input/Field/Rating.php @@ -27,12 +27,6 @@ */ interface Rating extends FormInput { - /** - * This text is diplayed over the actual input/stars to describe the issue - * to be rated (as opposed to the input's label, where there is less space). - */ - public function withAdditionalText(string $text): static; - /** * You may indicated an avarage value, e.g from former ratings. * Give a value between 0 and 5. diff --git a/components/ILIAS/UI/src/Implementation/Component/Input/Container/Form/Renderer.php b/components/ILIAS/UI/src/Implementation/Component/Input/Container/Form/Renderer.php index eb2e9f461fb7..86cf74905319 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Input/Container/Form/Renderer.php +++ b/components/ILIAS/UI/src/Implementation/Component/Input/Container/Form/Renderer.php @@ -57,7 +57,7 @@ protected function renderStandard(Form\Standard $component, RendererInterface $d $tpl->setVariable("BUTTONS_TOP", $default_renderer->render($submit_button)); $tpl->setVariable("BUTTONS_BOTTOM", $default_renderer->render($submit_button)); - $tpl->setVariable("INPUTS", $default_renderer->render($component->getInputGroup())); + $tpl->setVariable("INPUTS", $default_renderer->render($component->getInputGroup()->getInputs())); return $tpl->get(); } diff --git a/components/ILIAS/UI/src/Implementation/Component/Input/Field/DateTime.php b/components/ILIAS/UI/src/Implementation/Component/Input/Field/DateTime.php index 55004ce53f81..a4bf8001a61c 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Input/Field/DateTime.php +++ b/components/ILIAS/UI/src/Implementation/Component/Input/Field/DateTime.php @@ -211,26 +211,6 @@ protected function getConstraintForRequirement(): ?Constraint } - /** - * Get config to be passed to the bootstrap picker. - * @return array mixed> - */ - public function getAdditionalPickerconfig(): array - { - return $this->additional_picker_config; - } - - /** - * The bootstrap picker can be configured, e.g. with a minimum date. - * @param array mixed> $config - */ - public function withAdditionalPickerconfig(array $config): self - { - $clone = clone $this; - $clone->additional_picker_config = array_merge($clone->additional_picker_config, $config); - return $clone; - } - public function getUpdateOnLoadCode(): Closure { return fn($id) => "$('#$id').on('input', function(event) { diff --git a/components/ILIAS/UI/src/Implementation/Component/Input/Field/Rating.php b/components/ILIAS/UI/src/Implementation/Component/Input/Field/Rating.php index 4550a485e633..eb7a0ca11841 100644 --- a/components/ILIAS/UI/src/Implementation/Component/Input/Field/Rating.php +++ b/components/ILIAS/UI/src/Implementation/Component/Input/Field/Rating.php @@ -45,29 +45,17 @@ protected function getFiveStarRatingScaleTransformation(): Transformation { return $this->refinery->custom()->transformation( static function ($v): ?FiveStarRatingScale { - if(is_null($v) || $v instanceof FiveStarRatingScale) { + if (is_null($v) || $v instanceof FiveStarRatingScale) { return $v; } - return FiveStarRatingScale::from((int)$v); + return FiveStarRatingScale::from((int) $v); } ); } - public function withAdditionalText(?string $text): static - { - $clone = clone $this; - $clone->text = $text; - return $clone; - } - - public function getAdditionalText(): ?string - { - return $this->text; - } - public function withValue($value): self { - if(!$value instanceof FiveStarRatingScale) { + if (!$value instanceof FiveStarRatingScale) { $value = $this->getFiveStarRatingScaleTransformation()->transform($value); } return parent::withValue($value); @@ -100,7 +88,7 @@ public function getUpdateOnLoadCode(): \Closure public function withCurrentAverage(?float $current_average): static { $max = count(FiveStarRatingScale::cases()) - 1; - if($current_average < 0 || $current_average > $max) { + if ($current_average < 0 || $current_average > $max) { throw new \InvalidArgumentException('current_average must be between 0 and ' . $max); } $clone = clone $this; diff --git a/components/ILIAS/UI/src/Implementation/Component/Input/Field/Renderer.php b/components/ILIAS/UI/src/Implementation/Component/Input/Field/Renderer.php index 2e1f91b43515..d6c465097cee 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Input/Field/Renderer.php +++ b/components/ILIAS/UI/src/Implementation/Component/Input/Field/Renderer.php @@ -78,352 +78,218 @@ class Renderer extends AbstractComponentRenderer private const CENTUM = 100; - /** - * @inheritdoc - */ public function render(Component\Component $component, RendererInterface $default_renderer): string { $component = $this->setSignals($component); - switch (true) { - case ($component instanceof F\OptionalGroup): - return $this->renderOptionalGroup($component, $default_renderer); - - case ($component instanceof F\SwitchableGroup): - return $this->renderSwitchableGroup($component, $default_renderer); - - case ($component instanceof F\Section): - return $this->renderSection($component, $default_renderer); - - case ($component instanceof F\Duration): - return $this->renderDurationField($component, $default_renderer); - - case ($component instanceof F\Link): - return $this->renderLinkField($component, $default_renderer); - - case ($component instanceof F\Group): - return $default_renderer->render($component->getInputs()); - - case ($component instanceof F\Text): - return $this->renderTextField($component, $default_renderer); - - case ($component instanceof F\Numeric): - return $this->renderNumericField($component, $default_renderer); - - case ($component instanceof F\Checkbox): - return $this->renderCheckboxField($component, $default_renderer); - - case ($component instanceof F\Tag): - return $this->renderTagField($component, $default_renderer); - - case ($component instanceof F\Password): - return $this->renderPasswordField($component, $default_renderer); - - case ($component instanceof F\Select): - return $this->renderSelectField($component, $default_renderer); - - case ($component instanceof F\Markdown): - return $this->renderMarkdownField($component, $default_renderer); + [$context_tpl, $label_id, $error_id, $byline_id] = $this->renderContextPass1($component); + [$component, $label_html, $input_id, $input_html] = $this->renderInnerPart($component, $default_renderer, $label_id, $error_id, $byline_id); - case ($component instanceof F\Textarea): - return $this->renderTextareaField($component, $default_renderer); - - case ($component instanceof F\Radio): - return $this->renderRadioField($component, $default_renderer); - - case ($component instanceof F\MultiSelect): - return $this->renderMultiSelectField($component, $default_renderer); - - case ($component instanceof F\DateTime): - return $this->renderDateTimeField($component, $default_renderer); - - case ($component instanceof F\File): - return $this->renderFileField($component, $default_renderer); - - case ($component instanceof F\Url): - return $this->renderUrlField($component, $default_renderer); - - case ($component instanceof F\Hidden): - return $this->renderHiddenField($component); - - case ($component instanceof F\ColorPicker): - return $this->renderColorPickerField($component, $default_renderer); - - case ($component instanceof F\Rating): - return $this->renderRatingField($component, $default_renderer); - - default: - $this->cannotHandleComponent($component); - } + return $this->renderContextPass2($component, $context_tpl, $label_id, $label_html, $input_id, $input_html); } - protected function wrapInFormContext( - FormInput $component, - string $label, - string $input_html, - ?string $id_for_label = null, - ?string $dependant_group_html = null - ): string { - $tpl = $this->getTemplate("tpl.context_form.html", true, true); - $tpl->setVariable("LABEL", $label); - $tpl->setVariable("INPUT", $input_html); - $tpl->setVariable("UI_COMPONENT_NAME", $this->getComponentCanonicalNameAttribute($component)); - $tpl->setVariable("INPUT_NAME", $component->getName()); + // RENDER CONTEXT - if ($component->getOnLoadCode() !== null) { - $binding_id = $this->bindJavaScript($component) ?? $this->createId(); - $tpl->setVariable("BINDING_ID", $binding_id); - } - - if ($id_for_label) { - $tpl->setCurrentBlock('for'); - $tpl->setVariable("ID", $id_for_label); - $tpl->parseCurrentBlock(); - } else { - $tpl->touchBlock('tabindex'); - } - - $byline = $component->getByline(); - if ($byline) { - $tpl->setVariable("BYLINE", $byline); - } - - $required = $component->isRequired(); - if ($required) { - $tpl->setCurrentBlock('required'); - $tpl->setVariable("REQUIRED_ARIA", $this->txt('required_field')); - $tpl->parseCurrentBlock(); - } + protected function renderContextPass1(Component\Component $component): array + { + $context_tpl = $this->buildContextTemplate(); + // outer div if ($component->isDisabled()) { - $tpl->touchBlock("disabled"); - } - - $error = $component->getError(); - if ($error) { - $error_id = $this->createId(); - $tpl->setVariable("ERROR_LABEL", $this->txt("ui_error")); - $tpl->setVariable("ERROR_ID", $error_id); - $tpl->setVariable("ERROR", $error); - if ($id_for_label) { - $tpl->setVariable("ERROR_FOR_ID", $id_for_label); - } + $context_tpl->touchBlock("disabled"); } + $context_tpl->setVariable("UI_COMPONENT_NAME", $this->getComponentCanonicalNameAttribute($component)); + $context_tpl->setVariable("INPUT_NAME", $component->getName()); - if ($dependant_group_html) { - $tpl->setVariable("DEPENDANT_GROUP", $dependant_group_html); - } - return $tpl->get(); - } + // label + $label_id = $this->createId(); - protected function applyName(FormInput $component, Template $tpl): ?string - { - $name = $component->getName(); - $tpl->setVariable("NAME", $name); - return $name; - } + $error_id = $this->applyError($component, $context_tpl); + $byline_id = $this->applyByline($component, $context_tpl); - protected function bindJSandApplyId(Component\JavaScriptBindable $component, Template $tpl): string - { - $id = $this->bindJavaScript($component) ?? $this->createId(); - $tpl->setVariable("ID", $id); - return $id; + return [$context_tpl, $label_id, $error_id ?? null, $byline_id ?? null]; } - /** - * Escape values for rendering with a Callable "$escape" - * In order to prevent XSS-attacks, values need to be stripped of - * special chars (such as quotes or tags). - * Needs vary according to the type of component, i.e.the html generated - * for this specific component and the placement of {VALUE} in its template. - * Please note: this may not work for customized templates! - */ - protected function applyValue(FormInput $component, Template $tpl, callable $escape = null): void + protected function renderContextPass2(Component\Component $component, Template $context_tpl, string $label_id, ?string $label_html, string $input_id, string $input_html): string { - $value = $component->getValue(); - if (!is_null($escape)) { - $value = $escape($value); - } - if (isset($value) && $value !== '') { - $tpl->setVariable("VALUE", $value); + $context_tpl->setVariable("BINDING_ID", $this->bindJavaScript($component) ?? $this->createId()); + if ($label_html) { + $context_tpl->setVariable("LABEL_ID", $label_id); + $context_tpl->setVariable("LABEL", $label_html); + if ($component->isRequired()) { + $context_tpl->setVariable("REQUIRED_ARIA", $this->txt('required_field')); + } + $context_tpl->setVariable("INPUT_ID", $input_id); } + $context_tpl->setVariable("INPUT", $input_html); + return $context_tpl->get(); } - protected function escapeSpecialChars(): Closure + protected function buildContextTemplate(): Template { - return function ($v) { - // with declare(strict_types=1) in place, - // htmlspecialchars will not silently convert to string anymore; - // therefore, the typecast must be explicit - return htmlspecialchars((string) $v, ENT_QUOTES, 'utf-8', false); - }; + return $this->getTemplate("tpl.context_form.html", true, true); } - protected function htmlEntities(): Closure - { - return function ($v) { - // with declare(strict_types=1) in place, - // htmlentities will not silently convert to string anymore; - // therefore, the typecast must be explicit - return htmlentities((string) $v, ENT_QUOTES, 'utf-8', false); - }; - } - protected function renderLinkField(F\Link $component, RendererInterface $default_renderer): string - { - $input_html = $default_renderer->render($component->getInputs()); - return $this->wrapInFormContext( - $component, - $component->getLabel(), - $input_html, - ); - } + // RENDER ACTUAL INPUT - protected function renderTextField(F\Text $component): string + protected function renderInnerPart(Component\Component $c, RendererInterface $dr, string $label_id, ?string $error_id, ?string $byline_id): array { - $tpl = $this->getTemplate("tpl.text.html", true, true); - $this->applyName($component, $tpl); + return match(get_class($c)) { + F\Text::class => $this->renderTextField($c, $dr, $label_id, $error_id, $byline_id), + F\Textarea::class => $this->renderTextareaField($c, $dr, $label_id, $error_id, $byline_id), + F\Markdown::class => $this->renderMarkdownField($c, $dr, $label_id, $error_id, $byline_id), + F\Url::class => $this->renderUrlField($c, $dr, $label_id, $error_id, $byline_id), + F\Numeric::class => $this->renderNumericField($c, $dr, $label_id, $error_id, $byline_id), + F\Password::class => $this->renderPasswordField($c, $dr, $label_id, $error_id, $byline_id), + F\DateTime::class => $this->renderDateTimeField($c, $dr, $label_id, $error_id, $byline_id), + F\ColorPicker::class => $this->renderColorPickerField($c, $dr, $label_id, $error_id, $byline_id), + F\Checkbox::class => $this->renderCheckboxField($c, $dr, $label_id, $error_id, $byline_id), + F\Select::class => $this->renderSelectField($c, $dr, $label_id, $error_id, $byline_id), + F\Hidden::class => $this->renderHiddenField($c, $dr, $label_id, $error_id, $byline_id), + F\Tag::class => $this->renderTagField($c, $dr, $label_id, $error_id, $byline_id), + F\Radio::class => $this->renderRadioField($c, $dr, $label_id, $error_id, $byline_id), + F\Rating::class => $this->renderRatingField($c, $dr, $label_id, $error_id, $byline_id), + F\MultiSelect::class => $this->renderMultiSelectField($c, $dr, $label_id, $error_id, $byline_id), + F\File::class => $this->renderFileField($c, $dr, $label_id, $error_id, $byline_id), - if ($component->getMaxLength()) { - $tpl->setVariable("MAX_LENGTH", $component->getMaxLength()); - } + F\Group::class => $this->renderGroup($c, $dr, $label_id, $error_id, $byline_id), + F\Section::class => $this->renderSection($c, $dr, $label_id, $error_id, $byline_id), + F\OptionalGroup::class => $this->renderOptionalGroup($c, $dr, $label_id, $error_id, $byline_id), + F\SwitchableGroup::class => $this->renderSwitchableGroup($c, $dr, $label_id, $error_id, $byline_id), + F\Link::class => $this->renderLinkField($c, $dr, $label_id, $error_id, $byline_id), + F\Duration::class => $this->renderDurationField($c, $dr, $label_id, $error_id, $byline_id), - $this->applyValue($component, $tpl, $this->escapeSpecialChars()); - $label_id = $this->createId(); - $tpl->setVariable('ID', $label_id); - return $this->wrapInFormContext($component, $component->getLabel(), $tpl->get(), $label_id); + default => $this->cannotHandleComponent($c) + }; } - protected function renderNumericField(F\Numeric $component, RendererInterface $default_renderer): string + protected function renderTextField(F\Text $component, $_, string $label_id, ?string $error_id, ?string $byline_id): array { - $tpl = $this->getTemplate("tpl.numeric.html", true, true); + $tpl = $this->getTemplate("tpl.text.html", true, true); $this->applyName($component, $tpl); + $this->applyDisabled($component, $tpl); $this->applyValue($component, $tpl, $this->escapeSpecialChars()); - - $label_id = $this->createId(); - $tpl->setVariable('ID', $label_id); - return $this->wrapInFormContext($component, $component->getLabel(), $tpl->get(), $label_id); + if ($component->getMaxLength()) { + $tpl->setVariable("MAX_LENGTH", "maxlength=" . $component->getMaxLength()); + } + return [$component, $component->getLabel(), $this->applyIDs($tpl, $label_id, $error_id, $byline_id), $tpl->get()]; } - protected function renderCheckboxField(F\Checkbox $component, RendererInterface $default_renderer): string + protected function renderTextareaField(F\TextArea $component, $_, string $label_id, ?string $error_id, ?string $byline_id): array { - $tpl = $this->getTemplate("tpl.checkbox.html", true, true); + $tpl = $this->getTemplate("tpl.textarea.html", true, true); $this->applyName($component, $tpl); - if ($component->getValue()) { - $tpl->touchBlock("value"); - } + $component = $component->withAdditionalOnLoadCode( + static function ($id): string { + return " + il.UI.Input.textarea.init('$id'); + "; + } + ); - $label_id = $this->createId(); - $tpl->setVariable('ID', $label_id); - return $this->wrapInFormContext($component, $component->getLabel(), $tpl->get(), $label_id); - } + $this->applyDisabled($component, $tpl); + $this->applyValue($component, $tpl, $this->escapeSpecialChars()); - protected function renderOptionalGroup(F\OptionalGroup $component, RendererInterface $default_renderer): string - { - $tpl = $this->getTemplate("tpl.optionalgroup_label.html", true, true); - $tpl->setVariable('LABEL', $component->getLabel()); - $tpl->setVariable("NAME", $component->getName()); - if ($component->getValue()) { - $tpl->setVariable("CHECKED", 'checked="checked"'); + if (0 < $component->getMaxLimit()) { + $tpl->setVariable('REMAINDER_TEXT', $this->txt('ui_chars_remaining')); + $tpl->setVariable('REMAINDER', $component->getMaxLimit() - strlen($component->getValue() ?? '')); + $tpl->setVariable('MAX_LIMIT', $component->getMaxLimit()); } - $label_id = $this->createId(); - $tpl->setVariable('ID', $label_id); - - $label = $tpl->get(); - $input_html = $default_renderer->render($component->getInputs()); + if (null !== $component->getMinLimit()) { + $tpl->setVariable('MIN_LIMIT', $component->getMinLimit()); + } - return $this->wrapInFormContext($component, $label, $input_html, $label_id); + return [$component, $component->getLabel(), $this->applyIDs($tpl, $label_id, $error_id, $byline_id), $tpl->get()]; } - protected function renderSwitchableGroup(F\SwitchableGroup $component, RendererInterface $default_renderer): string + protected function renderMarkdownField(F\Markdown $component, RendererInterface $default_renderer, string $label_id, ?string $error_id, ?string $byline_id): array { - $value = null; - if ($component->getValue() !== null) { - list($value, ) = $component->getValue(); - } + [$component, $label_html, $input_id, $input_html] = $this->renderTextareaField($component, $default_renderer, $label_id, $error_id, $byline_id); - $input_html = ''; - $groupswitch_disabled = $component->getDisabledGroupSwitch(); + $component = $component->withAdditionalOnLoadCode( + static function ($id) use ($component, $input_id): string { + return " + il.UI.Input.markdown.init( + '$input_id', + '{$component->getMarkdownRenderer()->getAsyncUrl()}', + '{$component->getMarkdownRenderer()->getParameterName()}' + ); + "; + } + ); - foreach ($component->getInputs() as $key => $group) { - $tpl = $this->getTemplate("tpl.switchablegroup_label.html", true, true); - $tpl->setVariable('LABEL', $group->getLabel()); - $tpl->setVariable("NAME", $component->getName()); - $tpl->setVariable("VALUE", $key); + $markdown_tpl = $this->getTemplate("tpl.markdown.html", true, true); + $markdown_tpl->setVariable('TEXTAREA', $input_html); - $label_id = $this->createId(); - $tpl->setVariable('ID', $label_id); + $markdown_tpl->setVariable( + 'PREVIEW', + $component->getMarkdownRenderer()->render( + $this->htmlEntities()($component->getValue() ?? '') + ) + ); + + $markdown_tpl->setVariable( + 'VIEW_CONTROLS', + $default_renderer->render( + $this->getUIFactory()->viewControl()->mode([ + $this->txt('ui_md_input_edit') => '#', + $this->txt('ui_md_input_view') => '#', + ], "") + ) + ); + + /** @var $markdown_actions_glyphs Component\Symbol\Glyph\Glyph[] */ + $markdown_actions_glyphs = [ + 'ACTION_HEADING' => $this->getUIFactory()->symbol()->glyph()->header(), + 'ACTION_LINK' => $this->getUIFactory()->symbol()->glyph()->link(), + 'ACTION_BOLD' => $this->getUIFactory()->symbol()->glyph()->bold(), + 'ACTION_ITALIC' => $this->getUIFactory()->symbol()->glyph()->italic(), + 'ACTION_ORDERED_LIST' => $this->getUIFactory()->symbol()->glyph()->numberedlist(), + 'ACTION_UNORDERED_LIST' => $this->getUIFactory()->symbol()->glyph()->bulletlist() + ]; - if ($key == $value) { - $tpl->setVariable("CHECKED", 'checked="checked"'); + foreach ($markdown_actions_glyphs as $tpl_variable => $glyph) { + if ($component->isDisabled()) { + $glyph = $glyph->withUnavailableAction(); } - if ($groupswitch_disabled) { - $tpl->setVariable("DISABLED", "disabled"); - if ($key == $value) { - $tpl->setVariable("HIDDEN_NAME", $component->getName()); - $tpl->setVariable("HIDDEN_VAL", (string) $key); - } else { - $group = $group->withDisabled(true); - } + $action = $this->getUIFactory()->button()->standard('', '#')->withSymbol($glyph); + + if ($component->isDisabled()) { + $action = $action->withUnavailableAction(); } - $input_html .= $this->wrapInFormContext( - $group, - $tpl->get(), - $default_renderer->render($group), - $label_id - ); + $markdown_tpl->setVariable($tpl_variable, $default_renderer->render($action)); } - - return $this->wrapInFormContext( - $component, - $component->getLabel(), - $input_html - ); + return [$component, $label_html, $input_id, $markdown_tpl->get()]; } - protected function renderTagField(F\Tag $component, RendererInterface $default_renderer): string + + protected function renderUrlField(F\Url $component, $_, string $label_id, ?string $error_id, ?string $byline_id): array { - $tpl = $this->getTemplate("tpl.tag_input.html", true, true); + $tpl = $this->getTemplate("tpl.url.html", true, true); $this->applyName($component, $tpl); + $this->applyDisabled($component, $tpl); + $this->applyValue($component, $tpl, $this->escapeSpecialChars()); + return [$component, $component->getLabel(), $this->applyIDs($tpl, $label_id, $error_id, $byline_id), $tpl->get()]; + } - $configuration = $component->getConfiguration(); - $value = $component->getValue(); - - if ($value) { - $value = array_map( - function ($v) { - return ['value' => urlencode($v), 'display' => $v]; - }, - $value - ); - } - - $component = $component->withAdditionalOnLoadCode( - function ($id) use ($configuration, $value) { - $encoded = json_encode($configuration); - $value = json_encode($value); - return "il.UI.Input.tagInput.init('{$id}', {$encoded}, {$value});"; - } - ); - - if ($component->isDisabled()) { - $tpl->setVariable("DISABLED", "disabled"); - $tpl->setVariable("READONLY", "readonly"); - } - - $label_id = $this->createId(); - $tpl->setVariable('ID', $label_id); - return $this->wrapInFormContext($component, $component->getLabel(), $tpl->get(), $label_id); + protected function renderNumericField(F\Numeric $component, $_, string $label_id, ?string $error_id, ?string $byline_id): array + { + $tpl = $this->getTemplate("tpl.numeric.html", true, true); + $this->applyName($component, $tpl); + $this->applyDisabled($component, $tpl); + $this->applyValue($component, $tpl, $this->escapeSpecialChars()); + return [$component, $component->getLabel(), $this->applyIDs($tpl, $label_id, $error_id, $byline_id), $tpl->get()]; } - protected function renderPasswordField(F\Password $component, RendererInterface $default_renderer): string + protected function renderPasswordField(F\Password $component, RendererInterface $default_renderer, string $label_id, ?string $error_id, ?string $byline_id): array { $tpl = $this->getTemplate("tpl.password.html", true, true); $this->applyName($component, $tpl); @@ -435,12 +301,12 @@ protected function renderPasswordField(F\Password $component, RendererInterface $component = $component->withAdditionalOnLoadCode(function ($id) use ($sig_reveal, $sig_mask) { return "$(document).on('$sig_reveal', function() { - const fieldContainer = document.querySelector('#$id .c-input__field .c-field-password'); + const fieldContainer = document.querySelector('#$id .c-field-password'); fieldContainer.classList.add('revealed'); fieldContainer.getElementsByTagName('input').item(0).type='text'; });" . "$(document).on('$sig_mask', function() { - const fieldContainer = document.querySelector('#$id .c-input__field .c-field-password'); + const fieldContainer = document.querySelector('#$id .c-field-password'); fieldContainer.classList.remove('revealed'); fieldContainer.getElementsByTagName('input').item(0).type='password'; });"; @@ -456,17 +322,87 @@ protected function renderPasswordField(F\Password $component, RendererInterface $tpl->setVariable('PASSWORD_MASK', $default_renderer->render($glyph_mask)); } + $this->applyDisabled($component, $tpl); $this->applyValue($component, $tpl, $this->escapeSpecialChars()); + return [$component, $component->getLabel(), $this->applyIDs($tpl, $label_id, $error_id, $byline_id), $tpl->get()]; + } - $label_id = $this->createId(); - $tpl->setVariable('ID', $label_id); - return $this->wrapInFormContext($component, $component->getLabel(), $tpl->get(), $label_id); + protected function renderDateTimeField(F\DateTime $component, $_, string $label_id, ?string $error_id, ?string $byline_id): array + { + $tpl = $this->getTemplate("tpl.datetime.html", true, true); + $this->applyName($component, $tpl); + $this->applyDisabled($component, $tpl); + + if ($component->getTimeOnly() === true) { + $format = $component::TIME_FORMAT; + $dt_type = self::TYPE_TIME; + } else { + $dt_type = self::TYPE_DATE; + $format = $this->getTransformedDateFormat( + $component->getFormat(), + self::DATEPICKER_FORMAT_MAPPING + ); + + if ($component->getUseTime() === true) { + $format .= ' ' . $component::TIME_FORMAT; + $dt_type = self::TYPE_DATETIME; + } + } + $tpl->setVariable("DTTYPE", $dt_type); + + $min_max_format = self::DATE_DATEPICKER_MINMAX_FORMAT; + if ($dt_type === self::TYPE_DATETIME) { + $min_max_format = self::DATETIME_DATEPICKER_MINMAX_FORMAT; + } + $min_date = $component->getMinValue(); + if (!is_null($min_date)) { + $tpl->setVariable("MIN_DATE", date_format($min_date, $min_max_format)); + } + $max_date = $component->getMaxValue(); + if (!is_null($max_date)) { + $tpl->setVariable("MAX_DATE", date_format($max_date, $min_max_format)); + } + + $this->applyValue($component, $tpl, function (?string $value) use ($dt_type) { + if ($value !== null) { + $value = new \DateTimeImmutable($value); + return $value->format(match ($dt_type) { + self::TYPE_DATETIME => self::HTML5_NATIVE_DATETIME_FORMAT, + self::TYPE_DATE => self::HTML5_NATIVE_DATE_FORMAT, + self::TYPE_TIME => self::HTML5_NATIVE_TIME_FORMAT, + }); + } + return null; + }); + + return [$component, $component->getLabel(), $this->applyIDs($tpl, $label_id, $error_id, $byline_id), $tpl->get()]; } - public function renderSelectField(F\Select $component, RendererInterface $default_renderer): string + protected function renderColorPickerField(F\ColorPicker $component, $_, string $label_id, ?string $error_id, ?string $byline_id): array + { + $tpl = $this->getTemplate("tpl.colorpicker.html", true, true); + $this->applyName($component, $tpl); + $this->applyDisabled($component, $tpl); + $this->applyValue($component, $tpl, $this->escapeSpecialChars()); + return [$component, $component->getLabel(), $this->applyIDs($tpl, $label_id, $error_id, $byline_id), $tpl->get()]; + } + + protected function renderCheckboxField(F\Checkbox $component, RendererInterface $_, string $label_id, ?string $error_id, ?string $byline_id): array + { + $tpl = $this->getTemplate("tpl.checkbox.html", true, true); + $this->applyName($component, $tpl); + $this->applyDisabled($component, $tpl); + if ($component->getValue()) { + $tpl->touchBlock("value"); + } + return [$component, $component->getLabel(), $this->applyIDs($tpl, $label_id, $error_id, $byline_id), $tpl->get()]; + } + + public function renderSelectField(F\Select $component, RendererInterface $_, string $label_id, ?string $error_id, ?string $byline_id): array { $tpl = $this->getTemplate("tpl.select.html", true, true); $this->applyName($component, $tpl); + $this->applyDisabled($component, $tpl); $value = $component->getValue(); //disable first option if required. @@ -495,164 +431,146 @@ public function renderSelectField(F\Select $component, RendererInterface $defaul $tpl->parseCurrentBlock(); } - $label_id = $this->createId(); - $tpl->setVariable('ID', $label_id); - return $this->wrapInFormContext($component, $component->getLabel(), $tpl->get(), $label_id); + return [$component, $component->getLabel(), $this->applyIDs($tpl, $label_id, $error_id, $byline_id), $tpl->get()]; + } + + protected function renderHiddenField(F\Hidden $component, RendererInterface $_, string $label_id, ?string $error_id, ?string $byline_id): array + { + $tpl = $this->getTemplate('tpl.hidden.html', true, true); + $this->applyName($component, $tpl); + $this->applyDisabled($component, $tpl); + $this->applyValue($component, $tpl, $this->escapeSpecialChars()); + return [$component, $component->getLabel(), $this->applyIDs($tpl, $label_id, $error_id, $byline_id), $tpl->get()]; } - protected function renderMarkdownField(F\Markdown $component, RendererInterface $default_renderer): string + protected function renderTagField(F\Tag $component, RendererInterface $_, string $label_id, ?string $error_id, ?string $byline_id): array { - /** @var $component F\Markdown */ + $tpl = $this->getTemplate("tpl.tag_input.html", true, true); + $this->applyName($component, $tpl); + $this->applyDisabled($component, $tpl); + + $config = new \StdClass(); + $config->disabled = $component->isDisabled(); + $config->max_tags = $component->getMaxTags(); + $config->tag_max_length = $component->getTagMaxLength(); + $config->user_created_tags_allowed = $component->areUserCreatedTagsAllowed(); + $config->suggestion_starts_after = $component->getSuggestionsStartAfter(); + $config->tags = $component->getTags(); + $config->value = $component->getValue(); + $config = json_encode($config); $component = $component->withAdditionalOnLoadCode( - static function ($id) use ($component): string { - return " - const id = document.querySelector('#$id .c-input__field textarea')?.id; - il.UI.Input.markdown.init( - id, - '{$component->getMarkdownRenderer()->getAsyncUrl()}', - '{$component->getMarkdownRenderer()->getParameterName()}' - ); - "; - } + fn($id) => + "il.UI.Input.tagInput.init('{$id}', {$config});" ); - $textarea_id = $this->createId(); - $textarea_tpl = $this->getPreparedTextareaTemplate($component); - $textarea_tpl->setVariable('ID', $textarea_id); - - $markdown_tpl = $this->getTemplate("tpl.markdown.html", true, true); - $markdown_tpl->setVariable('TEXTAREA', $textarea_tpl->get()); - - $markdown_tpl->setVariable( - 'PREVIEW', - $component->getMarkdownRenderer()->render( - $this->htmlEntities()($component->getValue() ?? '') - ) - ); + return [$component, $component->getLabel(), $this->applyIDs($tpl, $label_id, $error_id, $byline_id), $tpl->get()]; + } - $markdown_tpl->setVariable( - 'VIEW_CONTROLS', - $default_renderer->render( - $this->getUIFactory()->viewControl()->mode([ - $this->txt('ui_md_input_edit') => '#', - $this->txt('ui_md_input_view') => '#', - ], "") - ) - ); + protected function renderRadioField(F\Radio $component, RendererInterface $_, string $label_id, ?string $error_id, ?string $byline_id): array + { + $tpl = $this->getTemplate("tpl.radio.html", true, true); + $this->applyName($component, $tpl); + $this->applyDisabled($component, $tpl); + $id = $this->applyIDs($tpl, $label_id, $error_id, $byline_id); - /** @var $markdown_actions_glyphs Component\Symbol\Glyph\Glyph[] */ - $markdown_actions_glyphs = [ - 'ACTION_HEADING' => $this->getUIFactory()->symbol()->glyph()->header(), - 'ACTION_LINK' => $this->getUIFactory()->symbol()->glyph()->link(), - 'ACTION_BOLD' => $this->getUIFactory()->symbol()->glyph()->bold(), - 'ACTION_ITALIC' => $this->getUIFactory()->symbol()->glyph()->italic(), - 'ACTION_ORDERED_LIST' => $this->getUIFactory()->symbol()->glyph()->numberedlist(), - 'ACTION_UNORDERED_LIST' => $this->getUIFactory()->symbol()->glyph()->bulletlist() - ]; + $id_count = 0; + foreach ($component->getOptions() as $value => $label) { + $opt_id = $id . '_opt_' . (string) ($id_count++); - foreach ($markdown_actions_glyphs as $tpl_variable => $glyph) { - if ($component->isDisabled()) { - $glyph = $glyph->withUnavailableAction(); + if ($component->getValue() !== null && $component->getValue() == $value) { + $tpl->touchBlock("option_checked"); } - - $action = $this->getUIFactory()->button()->standard('', '#')->withSymbol($glyph); - if ($component->isDisabled()) { - $action = $action->withUnavailableAction(); + $tpl->touchBlock("option_disabled"); } - $markdown_tpl->setVariable($tpl_variable, $default_renderer->render($action)); - } - - return $this->wrapInFormContext($component, $component->getLabel(), $markdown_tpl->get()); - } + $tpl->setCurrentBlock('optionblock'); + $tpl->setVariable("OPTION_NAME", $component->getName()); + $tpl->setVariable("OPTION_ID", $opt_id); + $tpl->setVariable("OPTION_VALUE", $value); + $tpl->setVariable("OPTION_LABEL", $label); - protected function renderTextareaField(F\Textarea $component, RendererInterface $default_renderer): string - { - /** @var $component F\Textarea */ - $component = $component->withAdditionalOnLoadCode( - static function ($id): string { - return " - taId = document.querySelector('#$id .c-input__field textarea')?.id; - il.UI.Input.textarea.init(taId); - "; + $byline = $component->getBylineFor((string) $value); + if (!empty($byline)) { + $tpl->setVariable("OPTION_BYLINE_ID", $byline); + $tpl->setVariable("OPTION_BYLINE", $byline); } - ); - $tpl = $this->getPreparedTextareaTemplate($component); + $tpl->parseCurrentBlock(); + } - $label_id = $this->createId(); - $tpl->setVariable('ID', $label_id); - return $this->wrapInFormContext($component, $component->getLabel(), $tpl->get(), $label_id); + return [$component, $component->getLabel(), $id, $tpl->get()]; } - protected function getPreparedTextareaTemplate(F\Textarea $component): Template + protected function renderRatingField(F\Rating $component, RendererInterface $_, string $label_id, ?string $error_id, ?string $byline_id): array { - $tpl = $this->getTemplate("tpl.textarea.html", true, true); + $tpl = $this->getTemplate("tpl.rating.html", true, true); + $this->applyName($component, $tpl); + $this->applyDisabled($component, $tpl); + $id = $this->applyIDs($tpl, $label_id, $error_id, $byline_id); - if (0 < $component->getMaxLimit()) { - $tpl->setVariable('REMAINDER_TEXT', $this->txt('ui_chars_remaining')); - $tpl->setVariable('REMAINDER', $component->getMaxLimit() - strlen($component->getValue() ?? '')); - $tpl->setVariable('MAX_LIMIT', $component->getMaxLimit()); - } + $option_count = count(FiveStarRatingScale::cases()) - 1; - if (null !== $component->getMinLimit()) { - $tpl->setVariable('MIN_LIMIT', $component->getMinLimit()); + if ($average = $component->getCurrentAverage()) { + $average_title = sprintf($this->txt('rating_average'), $average); + $tpl->setVariable('AVERAGE_VALUE', $average_title); + $tpl->setVariable('AVERAGE_VALUE_PERCENT', $average / $option_count * self::CENTUM); } - $this->applyName($component, $tpl); - $this->applyValue($component, $tpl, $this->htmlEntities()); - return $tpl; - } - - protected function renderRadioField(F\Radio $component, RendererInterface $default_renderer): string - { - $tpl = $this->getTemplate("tpl.radio.html", true, true); - $id = $this->createId(); - - foreach ($component->getOptions() as $value => $label) { - $opt_id = $id . '_' . $value . '_opt'; + foreach (range($option_count, 1, -1) as $option) { + if ($component->getValue() === FiveStarRatingScale::from((int) $option)) { + $tpl->touchBlock("option_checked"); + } + if ($component->isDisabled()) { + $tpl->touchBlock("option_disabled"); + } $tpl->setCurrentBlock('optionblock'); - $tpl->setVariable("NAME", $component->getName()); - $tpl->setVariable("OPTIONID", $opt_id); - $tpl->setVariable("VALUE", $value); - $tpl->setVariable("LABEL", $label); + $tpl->setVariable('OPTION_ARIALABEL', $this->txt($option . 'stars')); + $tpl->setVariable('OPTION_VALUE', (string) $option); + $tpl->setVariable('OPTION_ID', $id . '-' . $option); + $tpl->setVariable('OPTION_NAME', $component->getName()); + $tpl->setVariable('FOO', (string) $option); + $tpl->parseCurrentBlock(); + } - if ($component->getValue() !== null && $component->getValue() == $value) { - $tpl->setVariable("CHECKED", 'checked="checked"'); + if (!$component->isRequired()) { + $tpl->setVariable('NEUTRAL_ID', $id . '-0'); + $tpl->setVariable('NEUTRAL_NAME', $component->getName()); + $tpl->setVariable('NEUTRAL_LABEL', $this->txt('reset_stars')); + + if ($component->getValue() === FiveStarRatingScale::NONE || is_null($component->getValue())) { + $tpl->touchBlock("neutral_checked"); } if ($component->isDisabled()) { - $tpl->setVariable("DISABLED", 'disabled="disabled"'); - } - - $byline = $component->getBylineFor((string) $value); - if (!empty($byline)) { - $tpl->setVariable("BYLINE", $byline); + $tpl->touchBlock("neutral_disabled"); } - - $tpl->parseCurrentBlock(); } - return $this->wrapInFormContext($component, $component->getLabel(), $tpl->get()); + return [$component, $component->getLabel(), $id, $tpl->get()]; } - protected function renderMultiSelectField(F\MultiSelect $component, RendererInterface $default_renderer): string + protected function renderMultiSelectField(F\MultiSelect $component, RendererInterface $_, string $label_id, ?string $error_id, ?string $byline_id): array { $tpl = $this->getTemplate("tpl.multiselect.html", true, true); + $name = $this->applyName($component, $tpl); + $id = $this->applyIDs($tpl, $label_id, $error_id, $byline_id); $options = $component->getOptions(); + $value = $component->getValue(); + $id_count = 0; if (count($options) > 0) { - $value = $component->getValue(); - $name = $this->applyName($component, $tpl); foreach ($options as $opt_value => $opt_label) { - $tpl->setCurrentBlock("option"); - $tpl->setVariable("NAME", $name); - $tpl->setVariable("VALUE", $opt_value); - $tpl->setVariable("LABEL", $opt_label); + $opt_id = $id . '_opt_' . (string) ($id_count++); + + $tpl->setCurrentBlock('option_block'); + $tpl->setVariable("OPTION_NAME", $name); + $tpl->setVariable("OPTION_ID", $opt_id); + $tpl->setVariable("OPTION_VALUE", $opt_value); + $tpl->setVariable("OPTION_LABEL", $opt_label); if ($value && in_array($opt_value, $value)) { - $tpl->setVariable("CHECKED", 'checked="checked"'); + $tpl->touchBlock("option_checked"); } $tpl->parseCurrentBlock(); } @@ -660,162 +578,154 @@ protected function renderMultiSelectField(F\MultiSelect $component, RendererInte $tpl->touchBlock("no_options"); } - return $this->wrapInFormContext($component, $component->getLabel(), $tpl->get()); + return [$component, $component->getLabel(), $id, $tpl->get()]; } - protected function renderDateTimeField(F\DateTime $component, RendererInterface $default_renderer): string + + protected function renderGroup(F\Group $component, RendererInterface $default_renderer, string $label_id, ?string $error_id, ?string $byline_id): array { - list($component, $tpl) = $this->internalRenderDateTimeField($component, $default_renderer); - $label_id = $this->createId(); - $tpl->setVariable('ID', $label_id); - return $this->wrapInFormContext($component, $component->getLabel(), $tpl->get(), $label_id); + $tpl = $this->getTemplate("tpl.group.html", true, true); + $this->applyName($component, $tpl); + $tpl->setVariable("ARIA_LABEL", $component->getLabel()); + $tpl->setVariable("INPUTS", $default_renderer->render($component->getInputs())); + return [$component, null, $this->applyIDs($tpl, $label_id, $error_id, $byline_id), $tpl->get()]; } - /** - * @return array - */ - protected function internalRenderDateTimeField(F\DateTime $component, RendererInterface $default_renderer): array + protected function renderSection(F\Section $component, RendererInterface $default_renderer, string $label_id, ?string $error_id, ?string $byline_id): array { - $tpl = $this->getTemplate("tpl.datetime.html", true, true); + $tpl = $this->getTemplate("tpl.section.html", true, true); $this->applyName($component, $tpl); + $tpl->setVariable("INPUTS", $default_renderer->render($component->getInputs())); - if ($component->getTimeOnly() === true) { - $format = $component::TIME_FORMAT; - $dt_type = self::TYPE_TIME; - } else { - $dt_type = self::TYPE_DATE; - $format = $this->getTransformedDateFormat( - $component->getFormat(), - self::DATEPICKER_FORMAT_MAPPING - ); - - if ($component->getUseTime() === true) { - $format .= ' ' . $component::TIME_FORMAT; - $dt_type = self::TYPE_DATETIME; - } - } + $headline_tpl = $this->getTemplate("tpl.headlines.html", true, true); + $headline_tpl->setVariable("HEADLINE", $component->getLabel()); + $nesting_level = $component->getNestingLevel() + 2; + if ($nesting_level > 6) { + $nesting_level = 6; + }; + $headline_tpl->setVariable("LEVEL", $nesting_level); - $tpl->setVariable("DTTYPE", $dt_type); + return [$component, $headline_tpl->get(), $this->applyIDs($tpl, $label_id, $error_id, $byline_id), $tpl->get()]; + } - $min_max_format = self::DATE_DATEPICKER_MINMAX_FORMAT; - if ($dt_type === self::TYPE_DATETIME) { - $min_max_format = self::DATETIME_DATEPICKER_MINMAX_FORMAT; + protected function renderOptionalGroup(F\OptionalGroup $component, RendererInterface $default_renderer, string $label_id, ?string $error_id, ?string $byline_id): array + { + $tpl = $this->getTemplate("tpl.optionalgroup.html", true, true); + $this->applyName($component, $tpl); + $this->applyDisabled($component, $tpl); + if ($component->getValue()) { + $tpl->touchBlock("value"); } - $min_date = $component->getMinValue(); - if (!is_null($min_date)) { - $tpl->setVariable("MIN_DATE", date_format($min_date, $min_max_format)); - } - $max_date = $component->getMaxValue(); - if (!is_null($max_date)) { - $tpl->setVariable("MAX_DATE", date_format($max_date, $min_max_format)); + if ($byline_id) { + $tpl->setVariable("BYLINE_ID", $byline_id); } - $this->applyValue($component, $tpl, function (?string $value) use ($dt_type) { - if ($value !== null) { - $value = new \DateTimeImmutable($value); - return $value->format(match ($dt_type) { - self::TYPE_DATETIME => self::HTML5_NATIVE_DATETIME_FORMAT, - self::TYPE_DATE => self::HTML5_NATIVE_DATE_FORMAT, - self::TYPE_TIME => self::HTML5_NATIVE_TIME_FORMAT, - }); - } - return null; - }); - return [$component, $tpl]; + $tpl->setVariable("INPUTS", $default_renderer->render($component->getInputs())); + + return [$component, $component->getLabel(), $this->applyIDs($tpl, $label_id, $error_id, null), $tpl->get()]; } - protected function renderDurationField(F\Duration $component, RendererInterface $default_renderer): string + protected function renderSwitchableGroup(F\SwitchableGroup $component, RendererInterface $default_renderer, string $label_id, ?string $error_id, ?string $byline_id): array { - $inputs = $component->getInputs(); - $input = array_shift($inputs); //from - list($input, $tpl) = $this->internalRenderDateTimeField($input, $default_renderer); - - $from_input_id = $this->createId(); - $tpl->setVariable('ID', $from_input_id); - $input_html = $this->wrapInFormContext($input, $input->getLabel(), $tpl->get(), $from_input_id); + $tpl = $this->getTemplate("tpl.switchablegroup.html", true, true); + $this->applyName($component, $tpl); + $id = $this->applyIDs($tpl, $label_id, $error_id, $byline_id); - $input = array_shift($inputs) //until - ->withAdditionalPickerconfig(['useCurrent' => false]); - list($input, $tpl) = $this->internalRenderDateTimeField($input, $default_renderer); - $until_input_id = $this->createId(); - $tpl->setVariable('ID', $until_input_id); - $input_html .= $this->wrapInFormContext($input, $input->getLabel(), $tpl->get(), $until_input_id); + $groupswitch_disabled = $component->getDisabledGroupSwitch(); - $tpl = $this->getTemplate("tpl.duration.html", true, true); - $tpl->setVariable('DURATION', $input_html); - return $this->wrapInFormContext($component, $component->getLabel(), $tpl->get());//, $from_input_id); - } + $id_count = 0; + foreach ($component->getInputs() as $value => $group) { + $opt_id = $id . '_opt_' . (string) ($id_count++); - protected function renderSection(F\Section $section, RendererInterface $default_renderer): string - { - $inputs_html = $default_renderer->render($section->getInputs()); + if ($component->getValue() !== null && $component->getValue() == $value) { + $tpl->touchBlock("option_checked"); + if ($groupswitch_disabled) { + $group = $group->withDisabled(true); + } + } + if ($component->isDisabled() || $groupswitch_disabled) { + $tpl->touchBlock("option_disabled"); + } - $headline_tpl = $this->getTemplate("tpl.headlines.html", true, true); - $headline_tpl->setVariable("HEADLINE", $section->getLabel()); - $nesting_level = $section->getNestingLevel() + 2; - if ($nesting_level > 6) { - $nesting_level = 6; - }; - $headline_tpl->setVariable("LEVEL", $nesting_level); + $tpl->setCurrentBlock('optionblock'); + $tpl->setVariable("OPTION_NAME", $component->getName()); + $tpl->setVariable("OPTION_ID", $opt_id); + $tpl->setVariable("OPTION_VALUE", $value); + $tpl->setVariable("OPTION_LABEL", $group->getLabel()); + + if ($groupswitch_disabled && $key == $value) { + $tpl->setVariable("HIDDEN_NAME", $component->getName()); + $tpl->setVariable("HIDDEN_VAL", (string) $key); + } + + $tpl->setVariable("INPUTS", $default_renderer->render($group)); - $headline_html = $headline_tpl->get(); + $tpl->parseCurrentBlock(); + } - return $this->wrapInFormContext($section, $headline_html, $inputs_html); + return [$component, $component->getLabel(), $this->applyIDs($tpl, $label_id, $error_id, $byline_id), $tpl->get()]; } - protected function renderUrlField(F\Url $component, RendererInterface $default_renderer): string + protected function renderLinkField(F\Link $component, RendererInterface $default_renderer, string $label_id, ?string $error_id, ?string $byline_id): array { - $tpl = $this->getTemplate("tpl.url.html", true, true); + $tpl = $this->getTemplate("tpl.link.html", true, true); $this->applyName($component, $tpl); - $this->applyValue($component, $tpl, $this->escapeSpecialChars()); + $tpl->setVariable("ARIA_LABEL", $component->getLabel()); + $tpl->setVariable("INPUTS", $default_renderer->render($component->getInputs())); + return [$component, $component->getLabel(), $this->applyIDs($tpl, $label_id, $error_id, $byline_id), $tpl->get()]; + } - $label_id = $this->createId(); - $tpl->setVariable('ID', $label_id); - return $this->wrapInFormContext($component, $component->getLabel(), $tpl->get(), $label_id); + protected function renderDurationField(F\Duration $component, RendererInterface $default_renderer, string $label_id, ?string $error_id, ?string $byline_id): array + { + $tpl = $this->getTemplate("tpl.duration.html", true, true); + $this->applyName($component, $tpl); + $tpl->setVariable("ARIA_LABEL", $component->getLabel()); + $tpl->setVariable("INPUTS", $default_renderer->render($component->getInputs())); + return [$component, $component->getLabel(), $this->applyIDs($tpl, $label_id, $error_id, $byline_id), $tpl->get()]; } - protected function renderFileField(FI\File $input, RendererInterface $default_renderer): string + protected function renderFileField(F\File $component, RendererInterface $default_renderer, string $label_id, ?string $error_id, ?string $byline_id): array { - $template = $this->getTemplate('tpl.file.html', true, true); - foreach ($input->getDynamicInputs() as $metadata_input) { + $tpl = $this->getTemplate('tpl.file.html', true, true); + foreach ($component->getDynamicInputs() as $metadata_input) { $file_info = null; if (null !== ($data = $metadata_input->getValue())) { - $file_id = (!$input->hasMetadataInputs()) ? - $data : $data[$input->getUploadHandler()->getFileIdentifierParameterName()] ?? null; + $file_id = (!$component->hasMetadataInputs()) ? + $data : $data[$component->getUploadHandler()->getFileIdentifierParameterName()] ?? null; if (null !== $file_id) { - $file_info = $input->getUploadHandler()->getInfoResult($file_id); + $file_info = $component->getUploadHandler()->getInfoResult($file_id); } } - $template = $this->renderFilePreview( - $input, + $tpl = $this->renderFilePreview( + $component, $metadata_input, $default_renderer, $file_info, - $template + $tpl ); } $file_preview_template = $this->getTemplate('tpl.file.html', true, true); $file_preview_template = $this->renderFilePreview( - $input, - $input->getTemplateForDynamicInputs(), + $component, + $component->getTemplateForDynamicInputs(), $default_renderer, null, $file_preview_template ); - $template->setVariable('FILE_PREVIEW_TEMPLATE', $file_preview_template->get('block_file_preview')); + $tpl->setVariable('FILE_PREVIEW_TEMPLATE', $file_preview_template->get('block_file_preview')); - $this->setHelpBlockForFileField($template, $input); + $this->setHelpBlockForFileField($tpl, $component); - $input = $this->initClientsideFileInput($input); + $component = $this->initClientsideFileInput($component); // display the action button (to choose files). - $template->setVariable('ACTION_BUTTON', $default_renderer->render( + $tpl->setVariable('ACTION_BUTTON', $default_renderer->render( $this->getUIFactory()->button()->shy( $input->getMaxFiles() <= 1 ? $this->txt('select_file_from_computer') @@ -824,89 +734,7 @@ protected function renderFileField(FI\File $input, RendererInterface $default_re ) )); - return $this->wrapInFormContext( - $input, - $input->getLabel(), - $template->get(), - ); - } - - protected function renderHiddenField(F\Hidden $input): string - { - $template = $this->getTemplate('tpl.hidden.html', true, true); - $this->applyName($input, $template); - $this->applyValue($input, $template, $this->escapeSpecialChars()); - if ($input->isDisabled()) { - $template->setVariable("DISABLED", 'disabled="disabled"'); - } - $this->bindJSandApplyId($input, $template); - return $template->get(); - } - - /** - * @inheritdoc - */ - public function registerResources(ResourceRegistry $registry): void - { - parent::registerResources($registry); - $registry->register('assets/js/tagify.min.js'); - $registry->register('assets/css/tagify.css'); - $registry->register('assets/js/tagInput.js'); - - $registry->register('assets/js/dropzone.min.js'); - $registry->register('assets/js/dropzone.js'); - $registry->register('assets/js/input.js'); - $registry->register('assets/js/core.js'); - $registry->register('assets/js/file.js'); - $registry->register('assets/js/input.factory.min.js'); - } - - /** - * @param Input $input - * @return F\FormInput|JavaScriptBindable - */ - protected function setSignals(F\FormInput $input) - { - $signals = null; - foreach ($input->getTriggeredSignals() as $s) { - $signals[] = [ - "signal_id" => $s->getSignal()->getId(), - "event" => $s->getEvent(), - "options" => $s->getSignal()->getOptions() - ]; - } - if ($signals !== null) { - $signals = json_encode($signals); - - $input = $input->withAdditionalOnLoadCode(function ($id) use ($signals) { - $code = "il.UI.input.setSignalsForId('$id', $signals);"; - return $code; - }); - - $input = $input->withAdditionalOnLoadCode($input->getUpdateOnLoadCode()); - } - return $input; - } - - /** - * Return the datetime format in a form fit for the JS-component of this input. - * Currently, this means transforming the elements of DateFormat to momentjs. - * http://eonasdan.github.io/bootstrap-datetimepicker/Options/#format - * http://momentjs.com/docs/#/displaying/format/ - */ - protected function getTransformedDateFormat( - DateFormat\DateFormat $origin, - array $mapping - ): string { - $ret = ''; - foreach ($origin->toArray() as $element) { - if (array_key_exists($element, $mapping)) { - $ret .= $mapping[$element]; - } else { - $ret .= $element; - } - } - return $ret; + return [$component, $component->getLabel(), $this->applyIDs($tpl, $label_id, $error_id, $byline_id), $tpl->get()]; } protected function renderFilePreview( @@ -994,84 +822,190 @@ protected function prepareDropzoneJsMimeTypes(array $mime_types): string return $mime_type_string; } - protected function renderColorPickerField(F\ColorPicker $component, RendererInterface $default_renderer): string + protected function setHelpBlockForFileField(Template $template, FI\File $input): void { - $tpl = $this->getTemplate("tpl.colorpicker.html", true, true); - $this->applyName($component, $tpl); - $tpl->setVariable('VALUE', $component->getValue()); + $template->setCurrentBlock('HELP_BLOCK'); - $label_id = $this->createId(); - $tpl->setVariable('ID', $label_id); - return $this->wrapInFormContext($component, $component->getLabel(), $tpl->get(), $label_id); + $template->setCurrentBlock('MAX_FILE_SIZE'); + $template->setVariable('FILE_SIZE_LABEL', $this->txt('file_notice')); + $template->setVariable('FILE_SIZE_VALUE', new DataSize($input->getMaxFileSize(), DataSize::Byte)); + $template->parseCurrentBlock(); + + $template->setCurrentBlock('MAX_FILES'); + $template->setVariable('FILES_LABEL', $this->txt('ui_file_upload_max_nr')); + $template->setVariable('FILES_VALUE', $input->getMaxFiles()); + $template->parseCurrentBlock(); + + $template->parseCurrentBlock(); } - protected function renderRatingField(F\Rating $component, RendererInterface $default_renderer): string + + // RENDERER HELPERS FOR ACTUAL INPUT + + protected function applyError(FormInput $component, Template $tpl): ?string { - $tpl = $this->getTemplate("tpl.rating.html", true, true); - $id = $this->createId(); - $aria_description_id = $id . '_desc'; - $tpl->setVariable('DESCRIPTION_SRC_ID', $aria_description_id); + $error = $component->getError(); + if (!$error) { + return null; + } - $option_count = count(FiveStarRatingScale::cases()) - 1; + $error_id = $this->createId(); + $tpl->setVariable("ERROR_ID", $error_id); + $tpl->setVariable("ERROR_LABEL", $this->txt("ui_error")); + $tpl->setVariable("ERROR", $error); - foreach (range($option_count, 1, -1) as $option) { - $tpl->setCurrentBlock('scaleoption'); - $tpl->setVariable('ARIALABEL', $this->txt($option . 'stars')); - $tpl->setVariable('OPT_VALUE', (string) $option); - $tpl->setVariable('OPT_ID', $id . '-' . $option); - $tpl->setVariable('NAME', $component->getName()); - $tpl->setVariable('DESCRIPTION_ID', $aria_description_id); + return $error_id; + } - if ($component->getValue() === FiveStarRatingScale::from((int) $option)) { - $tpl->setVariable("SELECTED", ' checked="checked"'); - } - if ($component->isDisabled()) { - $tpl->setVariable("DISABLED", 'disabled="disabled"'); - } - $tpl->parseCurrentBlock(); + protected function applyByline(FormInput $component, Template $tpl): ?string + { + $byline = $component->getByline(); + if (!$byline) { + return null; } - if (!$component->isRequired()) { - $tpl->setVariable('NEUTRAL_ID', $id . '-0'); - $tpl->setVariable('NEUTRAL_NAME', $component->getName()); - $tpl->setVariable('NEUTRAL_LABEL', $this->txt('reset_stars')); - $tpl->setVariable('NEUTRAL_DESCRIPTION_ID', $aria_description_id); + $byline_id = $this->createId(); + $tpl->setVariable("BYLINE_ID", $byline_id); + $tpl->setVariable("BYLINE", $byline); - if ($component->getValue() === FiveStarRatingScale::NONE || is_null($component->getValue())) { - $tpl->setVariable('NEUTRAL_SELECTED', ' checked="checked"'); - } + return $byline_id; + } + + protected function applyName(FormInput $component, Template $tpl): ?string + { + $name = $component->getName(); + $tpl->setVariable("NAME", $name); + return $name; + } + + protected function applyDisabled(FormInput $component, Template $tpl): void + { + if ($component->isDisabled()) { + $tpl->touchBlock("disabled"); } + } - if ($txt = $component->getAdditionalText()) { - $tpl->setVariable('TEXT', $txt); + protected function applyIds(Template $tpl, string $label_id, ?string $error_id, ?string $byline_id): string + { + $id = $this->createId(); + $tpl->setVariable("ID", $id); + $tpl->setVariable("LABEL_ID", $label_id); + $describedby = join(" ", array_filter([$error_id, $byline_id])); + if ($describedby) { + $tpl->setVariable("DESCRIBED_BY", $describedby); } + return $id; + } - if ($component->isDisabled()) { - $tpl->touchBlock('disabled'); + /** + * Escape values for rendering with a Callable "$escape" + * In order to prevent XSS-attacks, values need to be stripped of + * special chars (such as quotes or tags). + * Needs vary according to the type of component, i.e.the html generated + * for this specific component and the placement of {VALUE} in its template. + * Please note: this may not work for customized templates! + */ + protected function applyValue(FormInput $component, Template $tpl, callable $escape = null): void + { + $value = $component->getValue(); + if (!is_null($escape)) { + $value = $escape($value); } - if ($average = $component->getCurrentAverage()) { - $average_title = sprintf($this->txt('rating_average'), $average); - $tpl->setVariable('AVERAGE_VALUE', $average_title); - $tpl->setVariable('AVERAGE_VALUE_PERCENT', $average / $option_count * self::CENTUM); + if (isset($value) && $value !== '') { + $tpl->setVariable("VALUE", $value); } + } - return $this->wrapInFormContext($component, $component->getLabel(), $tpl->get()); + protected function bindJSandApplyId(Component\JavaScriptBindable $component, Template $tpl): string + { + $id = $this->bindJavaScript($component) ?? $this->createId(); + $tpl->setVariable("ID", $id); + return $id; } - private function setHelpBlockForFileField(Template $template, FI\File $input): void + protected function escapeSpecialChars(): Closure { - $template->setCurrentBlock('HELP_BLOCK'); + return function ($v) { + // with declare(strict_types=1) in place, + // htmlspecialchars will not silently convert to string anymore; + // therefore, the typecast must be explicit + return htmlspecialchars((string) $v, ENT_QUOTES, 'utf-8', false); + }; + } - $template->setCurrentBlock('MAX_FILE_SIZE'); - $template->setVariable('FILE_SIZE_LABEL', $this->txt('file_notice')); - $template->setVariable('FILE_SIZE_VALUE', new DataSize($input->getMaxFileSize(), DataSize::Byte)); - $template->parseCurrentBlock(); + protected function htmlEntities(): Closure + { + return function ($v) { + // with declare(strict_types=1) in place, + // htmlentities will not silently convert to string anymore; + // therefore, the typecast must be explicit + return htmlentities((string) $v, ENT_QUOTES, 'utf-8', false); + }; + } - $template->setCurrentBlock('MAX_FILES'); - $template->setVariable('FILES_LABEL', $this->txt('ui_file_upload_max_nr')); - $template->setVariable('FILES_VALUE', $input->getMaxFiles()); - $template->parseCurrentBlock(); + /** + * @inheritdoc + */ + public function registerResources(ResourceRegistry $registry): void + { + parent::registerResources($registry); + $registry->register('assets/js/tagify.min.js'); + $registry->register('assets/css/tagify.css'); + $registry->register('assets/js/tagInput.js'); - $template->parseCurrentBlock(); + $registry->register('assets/js/dropzone.min.js'); + $registry->register('assets/js/dropzone.js'); + $registry->register('assets/js/input.js'); + $registry->register('assets/js/core.js'); + $registry->register('assets/js/file.js'); + $registry->register('assets/js/input.factory.min.js'); + } + + /** + * @param Input $input + * @return F\FormInput|JavaScriptBindable + */ + protected function setSignals(F\FormInput $input) + { + $signals = null; + foreach ($input->getTriggeredSignals() as $s) { + $signals[] = [ + "signal_id" => $s->getSignal()->getId(), + "event" => $s->getEvent(), + "options" => $s->getSignal()->getOptions() + ]; + } + if ($signals !== null) { + $signals = json_encode($signals); + + $input = $input->withAdditionalOnLoadCode(function ($id) use ($signals) { + $code = "il.UI.input.setSignalsForId('$id', $signals);"; + return $code; + }); + + $input = $input->withAdditionalOnLoadCode($input->getUpdateOnLoadCode()); + } + return $input; + } + + /** + * Return the datetime format in a form fit for the JS-component of this input. + * Currently, this means transforming the elements of DateFormat to momentjs. + * http://eonasdan.github.io/bootstrap-datetimepicker/Options/#format + * http://momentjs.com/docs/#/displaying/format/ + */ + protected function getTransformedDateFormat( + DateFormat\DateFormat $origin, + array $mapping + ): string { + $ret = ''; + foreach ($origin->toArray() as $element) { + if (array_key_exists($element, $mapping)) { + $ret .= $mapping[$element]; + } else { + $ret .= $element; + } + } + return $ret; } } diff --git a/components/ILIAS/UI/src/Implementation/Component/Input/Field/Tag.php b/components/ILIAS/UI/src/Implementation/Component/Input/Field/Tag.php index 8d409650eb43..9a015ebbe10c 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Input/Field/Tag.php +++ b/components/ILIAS/UI/src/Implementation/Component/Input/Field/Tag.php @@ -47,7 +47,7 @@ class Tag extends FormInput implements C\Input\Field\Tag public const EVENT_BEFORE_ITEM_REMOVE = 'beforeItemRemove'; public const EVENT_BEFORE_ITEM_ADD = 'beforeItemAdd'; public const EVENT_ITEM_REMOVED = 'itemRemoved'; - public const INFINITE = 0; + public const INFINITE = -1; protected int $max_tags = self::INFINITE; protected int $tag_max_length = self::INFINITE; @@ -80,39 +80,6 @@ protected function addAdditionalTransformations(): void })); } - public function getConfiguration(): stdClass - { - $options = array_map( - fn($tag) => [ - 'value' => urlencode(trim($tag)), - 'display' => $tag, - 'searchBy' => $tag - ], - $this->getTags() - ); - - $configuration = new stdClass(); - $configuration->id = null; - $configuration->options = $options; - $configuration->selectedOptions = $this->getValue(); - $configuration->maxItems = 20; - $configuration->dropdownMaxItems = 200; - $configuration->dropdownCloseOnSelect = false; - $configuration->readonly = $this->isDisabled(); - $configuration->userInput = $this->areUserCreatedTagsAllowed(); - $configuration->dropdownSuggestionsStartAfter = $this->getSuggestionsStartAfter(); - $configuration->suggestionStarts = $this->getSuggestionsStartAfter(); - $configuration->maxChars = 2000; - $configuration->suggestionLimit = 50; - $configuration->debug = false; - $configuration->allowDuplicates = false; - $configuration->highlight = true; - $configuration->tagClass = "input-tag"; - $configuration->tagTextProp = "displayValue"; - - return $configuration; - } - /** * @inheritDoc */ diff --git a/components/ILIAS/UI/src/examples/Input/Field/Rating/base.php b/components/ILIAS/UI/src/examples/Input/Field/Rating/base.php index 4b982d42057a..fa937af0403f 100644 --- a/components/ILIAS/UI/src/examples/Input/Field/Rating/base.php +++ b/components/ILIAS/UI/src/examples/Input/Field/Rating/base.php @@ -28,10 +28,7 @@ function base() $renderer = $DIC->ui()->renderer(); $request = $DIC->http()->request(); - $txt = "This allows for a preceding text and longer questions to ask."; - $rating = $ui->input()->field()->rating("Rate with the Stars:", "change the rating") - ->withAdditionalText($txt) ->withValue(FiveStarRatingScale::AVERAGE); $rating_required = $ui->input()->field()->rating("Rate with the Stars:", 'this is required') ->withRequired(true); diff --git a/components/ILIAS/UI/src/templates/default/Input/tpl.checkbox.html b/components/ILIAS/UI/src/templates/default/Input/tpl.checkbox.html index 53e2e083a79f..617ead577038 100755 --- a/components/ILIAS/UI/src/templates/default/Input/tpl.checkbox.html +++ b/components/ILIAS/UI/src/templates/default/Input/tpl.checkbox.html @@ -1 +1 @@ - checked="checked" name="{NAME}" {DISABLED} class="c-field-checkbox" /> + aria-describedby="{DESCRIBED_BY}" disabled="disabled" value="checked" checked="checked"/> diff --git a/components/ILIAS/UI/src/templates/default/Input/tpl.colorpicker.html b/components/ILIAS/UI/src/templates/default/Input/tpl.colorpicker.html index 715d4a06168d..a3acb1a4e964 100755 --- a/components/ILIAS/UI/src/templates/default/Input/tpl.colorpicker.html +++ b/components/ILIAS/UI/src/templates/default/Input/tpl.colorpicker.html @@ -1 +1 @@ - name="{NAME}" value="{VALUE}" class="c-field-color-picker"/> +aria-describedby="{DESCRIBED_BY}" disabled="disabled" value="{VALUE}"/> diff --git a/components/ILIAS/UI/src/templates/default/Input/tpl.context_form.html b/components/ILIAS/UI/src/templates/default/Input/tpl.context_form.html index 116c46e1112f..1b6260713c6c 100755 --- a/components/ILIAS/UI/src/templates/default/Input/tpl.context_form.html +++ b/components/ILIAS/UI/src/templates/default/Input/tpl.context_form.html @@ -1,17 +1,15 @@ -
disabled="disabled" aria-describedby="{ERROR_ID}" id="{BINDING_ID}" tabindex="0"> +
+ + + - - -
- {INPUT} -
+ {INPUT}
{ERROR_LABEL}: {ERROR}
- + - -
\ No newline at end of file + diff --git a/components/ILIAS/UI/src/templates/default/Input/tpl.datetime.html b/components/ILIAS/UI/src/templates/default/Input/tpl.datetime.html index 5dc5061f612b..916d665dd4fd 100755 --- a/components/ILIAS/UI/src/templates/default/Input/tpl.datetime.html +++ b/components/ILIAS/UI/src/templates/default/Input/tpl.datetime.html @@ -1,5 +1 @@ -
- type="{DTTYPE}" value="{VALUE}" name="{NAME}" - min="{MIN_DATE}" max="{MAX_DATE}" - {DISABLED} class="c-field-datetime" /> -
+ aria-describedby="{DESCRIBED_BY}" disabled="disabled" min="{MIN_DATE}" max="{MAX_DATE}" value="{VALUE}"/> diff --git a/components/ILIAS/UI/src/templates/default/Input/tpl.duration.html b/components/ILIAS/UI/src/templates/default/Input/tpl.duration.html index 8a415b6c854b..28280043d197 100755 --- a/components/ILIAS/UI/src/templates/default/Input/tpl.duration.html +++ b/components/ILIAS/UI/src/templates/default/Input/tpl.duration.html @@ -1,2 +1,4 @@ -
{DURATION}
+
aria-describedby="{DESCRIBED_BY}" role="group" aria-label="{ARIA_LABEL}"> + {INPUTS} +
diff --git a/components/ILIAS/UI/src/templates/default/Input/tpl.group.html b/components/ILIAS/UI/src/templates/default/Input/tpl.group.html new file mode 100644 index 000000000000..4ac5c6e6fc2d --- /dev/null +++ b/components/ILIAS/UI/src/templates/default/Input/tpl.group.html @@ -0,0 +1,3 @@ +
aria-describedby="{DESCRIBED_BY}" role="group" aria-label="{ARIA_LABEL}"> + {INPUTS} +
diff --git a/components/ILIAS/UI/src/templates/default/Input/tpl.hidden.html b/components/ILIAS/UI/src/templates/default/Input/tpl.hidden.html index a5fce6fe34c4..d52db3416faf 100755 --- a/components/ILIAS/UI/src/templates/default/Input/tpl.hidden.html +++ b/components/ILIAS/UI/src/templates/default/Input/tpl.hidden.html @@ -1 +1 @@ - name="{NAME}" value="{VALUE}" {DISABLED}/> \ No newline at end of file + aria-describedby="{DESCRIBED_BY}" disabled="disabled" value="{VALUE}"/> diff --git a/components/ILIAS/UI/src/templates/default/Input/tpl.link.html b/components/ILIAS/UI/src/templates/default/Input/tpl.link.html new file mode 100644 index 000000000000..e73281956223 --- /dev/null +++ b/components/ILIAS/UI/src/templates/default/Input/tpl.link.html @@ -0,0 +1,3 @@ + diff --git a/components/ILIAS/UI/src/templates/default/Input/tpl.multiselect.html b/components/ILIAS/UI/src/templates/default/Input/tpl.multiselect.html index d885be68ac2c..30d06e6a1b97 100755 --- a/components/ILIAS/UI/src/templates/default/Input/tpl.multiselect.html +++ b/components/ILIAS/UI/src/templates/default/Input/tpl.multiselect.html @@ -1,7 +1,7 @@ -
    - -
  • - +
      aria-describedby="{DESCRIBED_BY}" disabled="disabled" role="group"> + +
    • disabled="disabled" checked="checked"/>
    • +
    • -
    • diff --git a/components/ILIAS/UI/src/templates/default/Input/tpl.numeric.html b/components/ILIAS/UI/src/templates/default/Input/tpl.numeric.html index c473e4d993d2..41b7b2f3dfb0 100755 --- a/components/ILIAS/UI/src/templates/default/Input/tpl.numeric.html +++ b/components/ILIAS/UI/src/templates/default/Input/tpl.numeric.html @@ -1 +1 @@ - value="{VALUE}" name="{NAME}" {DISABLED} class="c-field-number" /> + aria-describedby="{DESCRIBED_BY}" disabled="disabled" value="{VALUE}"/> diff --git a/components/ILIAS/UI/src/templates/default/Input/tpl.optionalgroup.html b/components/ILIAS/UI/src/templates/default/Input/tpl.optionalgroup.html new file mode 100644 index 000000000000..4576a6b6d6b9 --- /dev/null +++ b/components/ILIAS/UI/src/templates/default/Input/tpl.optionalgroup.html @@ -0,0 +1,7 @@ +
      aria-describedby="{DESCRIBED_BY}" role="group"> + name="{NAME}" disabled="disabled" value="checked" checked="checked"/ aria-labelledby="{BYLINE_ID}"> + +
      + {INPUTS} +
      +
      diff --git a/components/ILIAS/UI/src/templates/default/Input/tpl.optionalgroup_label.html b/components/ILIAS/UI/src/templates/default/Input/tpl.optionalgroup_label.html deleted file mode 100644 index 2630c5296cad..000000000000 --- a/components/ILIAS/UI/src/templates/default/Input/tpl.optionalgroup_label.html +++ /dev/null @@ -1,5 +0,0 @@ -{LABEL} - name="{NAME}" value="checked" {CHECKED}/> - - - diff --git a/components/ILIAS/UI/src/templates/default/Input/tpl.password.html b/components/ILIAS/UI/src/templates/default/Input/tpl.password.html index c2ddde61f995..923cc07c149b 100755 --- a/components/ILIAS/UI/src/templates/default/Input/tpl.password.html +++ b/components/ILIAS/UI/src/templates/default/Input/tpl.password.html @@ -1,11 +1,11 @@
      - name="{NAME}" value="{VALUE}" {DISABLED} autocomplete="off" /> - - - {PASSWORD_REVEAL} - - - {PASSWORD_MASK} - - + aria-describedby="{DESCRIBED_BY}" disabled="disabled" autocomplete="off" value="{VALUE}"/> + + + {PASSWORD_REVEAL} + + + {PASSWORD_MASK} + +
      diff --git a/components/ILIAS/UI/src/templates/default/Input/tpl.radio.html b/components/ILIAS/UI/src/templates/default/Input/tpl.radio.html index d63012468f7e..9e62fba8fe23 100755 --- a/components/ILIAS/UI/src/templates/default/Input/tpl.radio.html +++ b/components/ILIAS/UI/src/templates/default/Input/tpl.radio.html @@ -1,12 +1,12 @@ -
      +
      aria-describedby="{DESCRIBED_BY}" disabled="disabled" role="radiogroup">
      - name="{NAME}" value="{VALUE}" {CHECKED} {DISABLED}/> - + name="{OPTION_NAME}" aria-describedby="{OPTION_BYLINE_ID}" value="{OPTION_VALUE}" disabled="disabled" checked="checked"/> + - - - + + +
      diff --git a/components/ILIAS/UI/src/templates/default/Input/tpl.rating.html b/components/ILIAS/UI/src/templates/default/Input/tpl.rating.html index 87d7da3e2003..eb1565b2b683 100644 --- a/components/ILIAS/UI/src/templates/default/Input/tpl.rating.html +++ b/components/ILIAS/UI/src/templates/default/Input/tpl.rating.html @@ -1,28 +1,22 @@ -
      - - - {TEXT} - - -
      -
      title="{AVERAGE_VALUE}"> - -
      -
      -
      - +
      aria-describedby="{DESCRIBED_BY}" disabled="disabled" role="radiogroup"> +
      title="{AVERAGE_VALUE}"> + +
      +
      +
      + + +
      + name="{OPTION_NAME}" value="{OPTION_VALUE}" disabled="disabled" checked="checked"/> + +
      + +
      - - - - -
      - - -
      - - -
      - -
      -
      + +
      + + disabled="disabled" checked="checked"/> +
      + +
      diff --git a/components/ILIAS/UI/src/templates/default/Input/tpl.section.html b/components/ILIAS/UI/src/templates/default/Input/tpl.section.html new file mode 100644 index 000000000000..07355b882b8f --- /dev/null +++ b/components/ILIAS/UI/src/templates/default/Input/tpl.section.html @@ -0,0 +1,3 @@ +
      aria-describedby="{DESCRIBED_BY}" role="group"> + {INPUTS} +
      diff --git a/components/ILIAS/UI/src/templates/default/Input/tpl.select.html b/components/ILIAS/UI/src/templates/default/Input/tpl.select.html index 684f830abac0..205bcac57ca6 100755 --- a/components/ILIAS/UI/src/templates/default/Input/tpl.select.html +++ b/components/ILIAS/UI/src/templates/default/Input/tpl.select.html @@ -1,7 +1,7 @@ - aria-describedby="{DESCRIBED_BY}" disabled="disabled"> - \ No newline at end of file + diff --git a/components/ILIAS/UI/src/templates/default/Input/tpl.switchablegroup.html b/components/ILIAS/UI/src/templates/default/Input/tpl.switchablegroup.html new file mode 100644 index 000000000000..9aa009f43d01 --- /dev/null +++ b/components/ILIAS/UI/src/templates/default/Input/tpl.switchablegroup.html @@ -0,0 +1,17 @@ +
      aria-describedby="{DESCRIBED_BY}" role="group"> + +
      + name="{OPTION_NAME}" value="{OPTION_VALUE}" disabled="disabled" checked="checked"/> + + + + + + + {INPUTS} + +
      +
      + + +
      diff --git a/components/ILIAS/UI/src/templates/default/Input/tpl.switchablegroup_label.html b/components/ILIAS/UI/src/templates/default/Input/tpl.switchablegroup_label.html deleted file mode 100644 index 408c09ab8029..000000000000 --- a/components/ILIAS/UI/src/templates/default/Input/tpl.switchablegroup_label.html +++ /dev/null @@ -1,5 +0,0 @@ - name="{NAME}" value="{VALUE}" {CHECKED} {DISABLED}/> -{LABEL} - - - \ No newline at end of file diff --git a/components/ILIAS/UI/src/templates/default/Input/tpl.tag_input.html b/components/ILIAS/UI/src/templates/default/Input/tpl.tag_input.html index ae00fc0b8e42..1df1a71305b7 100755 --- a/components/ILIAS/UI/src/templates/default/Input/tpl.tag_input.html +++ b/components/ILIAS/UI/src/templates/default/Input/tpl.tag_input.html @@ -1,3 +1 @@ -
      - name="{NAME}" class="c-field-tag" {READONLY} value=""/> -
      + aria-describedby="{DESCRIBED_BY}" readonly disabled="disabled" value=""/> diff --git a/components/ILIAS/UI/src/templates/default/Input/tpl.text.html b/components/ILIAS/UI/src/templates/default/Input/tpl.text.html index 7b1b61b1bcd5..770dc738ae5c 100755 --- a/components/ILIAS/UI/src/templates/default/Input/tpl.text.html +++ b/components/ILIAS/UI/src/templates/default/Input/tpl.text.html @@ -1 +1 @@ - value="{VALUE}" name="{NAME}" {DISABLED} maxlength="{MAX_LENGTH}" class="c-field-text" /> + aria-describedby="{DESCRIBED_BY}" disabled="disabled" maxlength="{MAX_LENGTH}" value="{VALUE}"/> diff --git a/components/ILIAS/UI/src/templates/default/Input/tpl.textarea.html b/components/ILIAS/UI/src/templates/default/Input/tpl.textarea.html index ae456c787b00..0d880c19bfaa 100755 --- a/components/ILIAS/UI/src/templates/default/Input/tpl.textarea.html +++ b/components/ILIAS/UI/src/templates/default/Input/tpl.textarea.html @@ -1,11 +1,4 @@ - +
      {REMAINDER_TEXT} {REMAINDER} diff --git a/components/ILIAS/UI/src/templates/default/Input/tpl.url.html b/components/ILIAS/UI/src/templates/default/Input/tpl.url.html index 69f99e4fe3d0..c5f5a0a34991 100755 --- a/components/ILIAS/UI/src/templates/default/Input/tpl.url.html +++ b/components/ILIAS/UI/src/templates/default/Input/tpl.url.html @@ -1 +1 @@ - value="{VALUE}" name="{NAME}" {DISABLED} class="c-field-url" /> + aria-describedby="{DESCRIBED_BY}" disabled="disabled" value="{VALUE}"/>