ILIAS  trunk Revision v11.0_alpha-2638-g80c1d007f79
Renderer.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
22 
32 use LogicException;
33 use Closure;
40 use ILIAS\Data\URI;
42 
48 {
49  use ComponentHelper;
50 
51  public const DATETIME_DATEPICKER_MINMAX_FORMAT = 'Y-m-d\Th:m';
52  public const DATE_DATEPICKER_MINMAX_FORMAT = 'Y-m-d';
53  public const TYPE_DATE = 'date';
54  public const TYPE_DATETIME = 'datetime-local';
55  public const TYPE_TIME = 'time';
56  public const HTML5_NATIVE_DATETIME_FORMAT = 'Y-m-d H:i';
57  public const HTML5_NATIVE_DATE_FORMAT = 'Y-m-d';
58  public const HTML5_NATIVE_TIME_FORMAT = 'H:i';
59 
60  public const DATEPICKER_FORMAT_MAPPING = [
61  'd' => 'DD',
62  'jS' => 'Do',
63  'l' => 'dddd',
64  'D' => 'dd',
65  'S' => 'o',
66  'i' => 'mm',
67  'W' => '',
68  'm' => 'MM',
69  'F' => 'MMMM',
70  'M' => 'MMM',
71  'Y' => 'YYYY',
72  'y' => 'YY'
73  ];
74 
81  protected const FILE_UPLOAD_CHUNK_SIZE_FACTOR = 0.9;
82 
83  private const CENTUM = 100;
84 
88  public function render(Component\Component $component, RendererInterface $default_renderer): string
89  {
90  $component = $this->setSignals($component);
91 
92  switch (true) {
93  case ($component instanceof F\OptionalGroup):
94  return $this->renderOptionalGroup($component, $default_renderer);
95 
96  case ($component instanceof F\SwitchableGroup):
97  return $this->renderSwitchableGroup($component, $default_renderer);
98 
99  case ($component instanceof F\Section):
100  return $this->renderSection($component, $default_renderer);
101 
102  case ($component instanceof F\Duration):
103  return $this->renderDurationField($component, $default_renderer);
104 
105  case ($component instanceof F\Link):
106  return $this->renderLinkField($component, $default_renderer);
107 
108  case ($component instanceof F\Group):
109  return $default_renderer->render($component->getInputs());
110 
111  case ($component instanceof F\Text):
112  return $this->renderTextField($component, $default_renderer);
113 
114  case ($component instanceof F\Numeric):
115  return $this->renderNumericField($component, $default_renderer);
116 
117  case ($component instanceof F\Checkbox):
118  return $this->renderCheckboxField($component, $default_renderer);
119 
120  case ($component instanceof F\Tag):
121  return $this->renderTagField($component, $default_renderer);
122 
123  case ($component instanceof F\Password):
124  return $this->renderPasswordField($component, $default_renderer);
125 
126  case ($component instanceof F\Select):
127  return $this->renderSelectField($component, $default_renderer);
128 
129  case ($component instanceof F\Markdown):
130  return $this->renderMarkdownField($component, $default_renderer);
131 
132  case ($component instanceof F\Textarea):
133  return $this->renderTextareaField($component, $default_renderer);
134 
135  case ($component instanceof F\Radio):
136  return $this->renderRadioField($component, $default_renderer);
137 
138  case ($component instanceof F\MultiSelect):
139  return $this->renderMultiSelectField($component, $default_renderer);
140 
141  case ($component instanceof F\DateTime):
142  return $this->renderDateTimeField($component, $default_renderer);
143 
144  case ($component instanceof F\Image):
145  return $this->renderImageField($component, $default_renderer);
146 
147  case ($component instanceof F\File):
148  return $this->renderFileField($component, $default_renderer);
149 
150  case ($component instanceof F\Url):
151  return $this->renderUrlField($component, $default_renderer);
152 
153  case ($component instanceof F\Hidden):
154  return $this->renderHiddenField($component);
155 
156  case ($component instanceof F\ColorSelect):
157  return $this->renderColorSelectField($component, $default_renderer);
158 
159  case ($component instanceof F\Rating):
160  return $this->renderRatingField($component, $default_renderer);
161 
162  case ($component instanceof F\TreeMultiSelect):
163  return $this->renderTreeMultiSelectField($component, $default_renderer);
164 
165  case ($component instanceof F\TreeSelect):
166  return $this->renderTreeSelectField($component, $default_renderer);
167 
168  default:
169  $this->cannotHandleComponent($component);
170  }
171  }
172 
173  protected function wrapInFormContext(
174  FormInput $component,
175  string $label,
176  string $input_html,
177  ?string $id_for_label = null,
178  ?string $dependant_group_html = null
179  ): string {
180  $tpl = $this->getTemplate("tpl.context_form.html", true, true);
181 
182  $tpl->setVariable("LABEL", $label);
183  $tpl->setVariable("INPUT", $input_html);
184  $tpl->setVariable("UI_COMPONENT_NAME", $this->getComponentCanonicalNameAttribute($component));
185  $tpl->setVariable("INPUT_NAME", $component->getName());
186 
187  if ($component->getOnLoadCode() !== null) {
188  $binding_id = $this->bindJavaScript($component) ?? $this->createId();
189  $tpl->setVariable("BINDING_ID", $binding_id);
190  }
191 
192  if ($id_for_label) {
193  $tpl->setCurrentBlock('for');
194  $tpl->setVariable("ID", $id_for_label);
195  $tpl->parseCurrentBlock();
196  } else {
197  $tpl->touchBlock('tabindex');
198  }
199 
200  $byline = $component->getByline();
201  if ($byline) {
202  $tpl->setVariable("BYLINE", $byline);
203  }
204 
205  $required = $component->isRequired();
206  if ($required) {
207  $tpl->setCurrentBlock('required');
208  $tpl->setVariable("REQUIRED_ARIA", $this->txt('required_field'));
209  $tpl->parseCurrentBlock();
210  }
211 
212  if ($component->isDisabled()) {
213  $tpl->touchBlock("disabled");
214  }
215 
216  $error = $component->getError();
217  if ($error) {
218  $error_id = $this->createId();
219  $tpl->setVariable("ERROR_LABEL", $this->txt("ui_error"));
220  $tpl->setVariable("ERROR_ID", $error_id);
221  $tpl->setVariable("ERROR", $error);
222  if ($id_for_label) {
223  $tpl->setVariable("ERROR_FOR_ID", $id_for_label);
224  }
225  }
226 
227  if ($dependant_group_html) {
228  $tpl->setVariable("DEPENDANT_GROUP", $dependant_group_html);
229  }
230  return $tpl->get();
231  }
232 
233  protected function applyName(FormInput $component, Template $tpl): ?string
234  {
235  $name = $component->getName();
236  $tpl->setVariable("NAME", $name);
237  return $name;
238  }
239 
240  protected function bindJSandApplyId(Component\JavaScriptBindable $component, Template $tpl): string
241  {
242  $id = $this->bindJavaScript($component) ?? $this->createId();
243  $tpl->setVariable("ID", $id);
244  return $id;
245  }
246 
255  protected function applyValue(FormInput $component, Template $tpl, ?callable $escape = null): void
256  {
257  $value = $component->getValue();
258  if (!is_null($escape)) {
259  $value = $escape($value);
260  }
261  if (isset($value) && $value !== '') {
262  $tpl->setVariable("VALUE", $value);
263  }
264  }
265 
266  protected function escapeSpecialChars(): Closure
267  {
268  return function ($v) {
269  // with declare(strict_types=1) in place,
270  // htmlspecialchars will not silently convert to string anymore;
271  // therefore, the typecast must be explicit
272  return htmlspecialchars((string) $v, ENT_QUOTES, 'utf-8', false);
273  };
274  }
275 
276  protected function htmlEntities(): Closure
277  {
278  return function ($v) {
279  // with declare(strict_types=1) in place,
280  // htmlentities will not silently convert to string anymore;
281  // therefore, the typecast must be explicit
282  return htmlentities((string) $v, ENT_QUOTES, 'utf-8', false);
283  };
284  }
285 
286  protected function renderLinkField(F\Link $component, RendererInterface $default_renderer): string
287  {
288  $input_html = $default_renderer->render($component->getInputs());
289  return $this->wrapInFormContext(
290  $component,
291  $component->getLabel(),
292  $input_html,
293  );
294  }
295 
296  protected function renderTextField(F\Text $component): string
297  {
298  $tpl = $this->getTemplate("tpl.text.html", true, true);
299  $this->applyName($component, $tpl);
300 
301  if ($component->getMaxLength()) {
302  $tpl->setVariable("MAX_LENGTH", $component->getMaxLength());
303  }
304 
305  $this->applyValue($component, $tpl, $this->escapeSpecialChars());
306 
307  $label_id = $this->createId();
308  $tpl->setVariable('ID', $label_id);
309  return $this->wrapInFormContext($component, $component->getLabel(), $tpl->get(), $label_id);
310  }
311 
312  protected function renderNumericField(F\Numeric $component, RendererInterface $default_renderer): string
313  {
314  $tpl = $this->getTemplate("tpl.numeric.html", true, true);
315  $this->applyName($component, $tpl);
316  $this->applyValue($component, $tpl, $this->escapeSpecialChars());
317 
318  $tpl->setVariable("STEPSIZE", $component->getStepSize());
319 
320  $label_id = $this->createId();
321  $tpl->setVariable('ID', $label_id);
322  return $this->wrapInFormContext($component, $component->getLabel(), $tpl->get(), $label_id);
323  }
324 
325  protected function renderCheckboxField(F\Checkbox $component, RendererInterface $default_renderer): string
326  {
327  $tpl = $this->getTemplate("tpl.checkbox.html", true, true);
328  $this->applyName($component, $tpl);
329 
330  if ($component->getValue()) {
331  $tpl->touchBlock("value");
332  }
333 
334  $label_id = $this->createId();
335  $tpl->setVariable('ID', $label_id);
336  return $this->wrapInFormContext($component, $component->getLabel(), $tpl->get(), $label_id);
337  }
338 
339  protected function renderOptionalGroup(F\OptionalGroup $component, RendererInterface $default_renderer): string
340  {
341  $tpl = $this->getTemplate("tpl.optionalgroup_label.html", true, true);
342  $tpl->setVariable('LABEL', $component->getLabel());
343  $tpl->setVariable("NAME", $component->getName());
344  if ($component->getValue()) {
345  $tpl->setVariable("CHECKED", 'checked="checked"');
346  }
347 
348  $label_id = $this->createId();
349  $tpl->setVariable('ID', $label_id);
350 
351  $label = $tpl->get();
352  $input_html = $default_renderer->render($component->getInputs());
353 
354  return $this->wrapInFormContext($component, $label, $input_html, $label_id);
355  }
356 
357  protected function renderSwitchableGroup(F\SwitchableGroup $component, RendererInterface $default_renderer): string
358  {
359  $value = null;
360  if ($component->getValue() !== null) {
361  list($value, ) = $component->getValue();
362  }
363 
364  $input_html = '';
365  foreach ($component->getInputs() as $key => $group) {
366  $tpl = $this->getTemplate("tpl.switchablegroup_label.html", true, true);
367  $tpl->setVariable('LABEL', $group->getLabel());
368  $tpl->setVariable("NAME", $component->getName());
369  $tpl->setVariable("VALUE", $key);
370 
371  $label_id = $this->createId();
372  $tpl->setVariable('ID', $label_id);
373 
374  if ($key == $value) {
375  $tpl->setVariable("CHECKED", 'checked="checked"');
376  }
377 
378  $input_html .= $this->wrapInFormContext(
379  $group,
380  $tpl->get(),
381  $default_renderer->render($group),
382  $label_id
383  );
384  }
385 
386 
387  return $this->wrapInFormContext(
388  $component,
389  $component->getLabel(),
390  $input_html
391  );
392  }
393 
394  protected function renderTagField(F\Tag $component, RendererInterface $default_renderer): string
395  {
396  $tpl = $this->getTemplate("tpl.tag_input.html", true, true);
397  $this->applyName($component, $tpl);
398 
399  $configuration = $component->getConfiguration();
400  $value = $component->getValue();
401 
402  if ($value) {
403  $value = array_map(
404  function ($v) {
405  return ['value' => urlencode($v), 'display' => $v];
406  },
407  $value
408  );
409  }
410 
411  $component = $component->withAdditionalOnLoadCode(
412  function ($id) use ($configuration, $value) {
413  $encoded = json_encode($configuration);
414  $value = json_encode($value);
415  return "il.UI.Input.tagInput.init('{$id}', {$encoded}, {$value});";
416  }
417  );
418 
419  if ($component->isDisabled()) {
420  $tpl->setVariable("DISABLED", "disabled");
421  $tpl->setVariable("READONLY", "readonly");
422  }
423 
424  $label_id = $this->createId();
425  $tpl->setVariable('ID', $label_id);
426  return $this->wrapInFormContext($component, $component->getLabel(), $tpl->get(), $label_id);
427  }
428 
429  protected function renderPasswordField(F\Password $component, RendererInterface $default_renderer): string
430  {
431  $tpl = $this->getTemplate("tpl.password.html", true, true);
432  $this->applyName($component, $tpl);
433 
434  if ($component->getRevelation()) {
435  $component = $component->withResetSignals();
436  $sig_reveal = $component->getRevealSignal();
437  $sig_mask = $component->getMaskSignal();
438  $component = $component->withAdditionalOnLoadCode(function ($id) use ($sig_reveal, $sig_mask) {
439  return
440  "$(document).on('$sig_reveal', function() {
441  const fieldContainer = document.querySelector('#$id .c-input__field .c-field-password');
442  fieldContainer.classList.add('revealed');
443  fieldContainer.getElementsByTagName('input').item(0).type='text';
444  });" .
445  "$(document).on('$sig_mask', function() {
446  const fieldContainer = document.querySelector('#$id .c-input__field .c-field-password');
447  fieldContainer.classList.remove('revealed');
448  fieldContainer.getElementsByTagName('input').item(0).type='password';
449  });";
450  });
451 
452  $f = $this->getUIFactory();
453  $glyph_reveal = $f->symbol()->glyph()->eyeopen("#")
454  ->withOnClick($sig_reveal);
455  $glyph_mask = $f->symbol()->glyph()->eyeclosed("#")
456  ->withOnClick($sig_mask);
457 
458  $tpl->setVariable('PASSWORD_REVEAL', $default_renderer->render($glyph_reveal));
459  $tpl->setVariable('PASSWORD_MASK', $default_renderer->render($glyph_mask));
460  }
461 
462  $this->applyValue($component, $tpl, $this->escapeSpecialChars());
463 
464  $label_id = $this->createId();
465  $tpl->setVariable('ID', $label_id);
466  return $this->wrapInFormContext($component, $component->getLabel(), $tpl->get(), $label_id);
467  }
468 
469  public function renderSelectField(F\Select $component, RendererInterface $default_renderer): string
470  {
471  $tpl = $this->getTemplate("tpl.select.html", true, true);
472  $this->applyName($component, $tpl);
473 
474  $value = $component->getValue();
475  //disable first option if required.
476  $tpl->setCurrentBlock("options");
477  if (!$value) {
478  $tpl->setVariable("SELECTED", 'selected="selected"');
479  }
480  if ($component->isRequired() && !$value) {
481  $tpl->setVariable("DISABLED_OPTION", "disabled");
482  $tpl->setVariable("HIDDEN", "hidden");
483  }
484 
485  if (!($value && $component->isRequired())) {
486  $tpl->setVariable("VALUE", null);
487  $tpl->setVariable("VALUE_STR", $component->isRequired() ? $this->txt('ui_select_dropdown_label') : '-');
488  $tpl->parseCurrentBlock();
489  }
490 
491  foreach ($component->getOptions() as $option_key => $option_value) {
492  $tpl->setCurrentBlock("options");
493  if ($value == $option_key) {
494  $tpl->setVariable("SELECTED", 'selected="selected"');
495  }
496  $tpl->setVariable("VALUE", $option_key);
497  $tpl->setVariable("VALUE_STR", $option_value);
498  $tpl->parseCurrentBlock();
499  }
500 
501  $label_id = $this->createId();
502  $tpl->setVariable('ID', $label_id);
503  return $this->wrapInFormContext($component, $component->getLabel(), $tpl->get(), $label_id);
504  }
505 
506  protected function renderMarkdownField(F\Markdown $component, RendererInterface $default_renderer): string
507  {
509  $component = $component->withAdditionalOnLoadCode(
510  static function ($id) use ($component): string {
511  return "
512  const id = document.querySelector('#$id .c-input__field textarea')?.id;
513  il.UI.Input.markdown.init(
514  id,
515  '{$component->getMarkdownRenderer()->getAsyncUrl()}',
516  '{$component->getMarkdownRenderer()->getParameterName()}'
517  );
518  ";
519  }
520  );
521 
522  $textarea_id = $this->createId();
523  $textarea_tpl = $this->getPreparedTextareaTemplate($component);
524  $textarea_tpl->setVariable('ID', $textarea_id);
525 
526  $markdown_tpl = $this->getTemplate("tpl.markdown.html", true, true);
527  $markdown_tpl->setVariable('TEXTAREA', $textarea_tpl->get());
528 
529  $markdown_tpl->setVariable(
530  'PREVIEW',
531  $component->getMarkdownRenderer()->render(
532  $this->htmlEntities()($component->getValue() ?? '')
533  )
534  );
535 
536  $markdown_tpl->setVariable(
537  'VIEW_CONTROLS',
538  $default_renderer->render(
539  $this->getUIFactory()->viewControl()->mode([
540  $this->txt('ui_md_input_edit') => '#',
541  $this->txt('ui_md_input_view') => '#',
542  ], "")
543  )
544  );
545 
547  $markdown_actions_glyphs = [
548  'ACTION_HEADING' => $this->getUIFactory()->symbol()->glyph()->header(),
549  'ACTION_LINK' => $this->getUIFactory()->symbol()->glyph()->link(),
550  'ACTION_BOLD' => $this->getUIFactory()->symbol()->glyph()->bold(),
551  'ACTION_ITALIC' => $this->getUIFactory()->symbol()->glyph()->italic(),
552  'ACTION_ORDERED_LIST' => $this->getUIFactory()->symbol()->glyph()->numberedlist(),
553  'ACTION_UNORDERED_LIST' => $this->getUIFactory()->symbol()->glyph()->bulletlist()
554  ];
555 
556  foreach ($markdown_actions_glyphs as $tpl_variable => $glyph) {
557  if ($component->isDisabled()) {
558  $glyph = $glyph->withUnavailableAction();
559  }
560 
561  $action = $this->getUIFactory()->button()->standard('', '#')->withSymbol($glyph);
562 
563  if ($component->isDisabled()) {
564  $action = $action->withUnavailableAction();
565  }
566 
567  $markdown_tpl->setVariable($tpl_variable, $default_renderer->render($action));
568  }
569 
570  return $this->wrapInFormContext($component, $component->getLabel(), $markdown_tpl->get());
571  }
572 
573  protected function renderTextareaField(F\Textarea $component, RendererInterface $default_renderer): string
574  {
576  $component = $component->withAdditionalOnLoadCode(
577  static function ($id): string {
578  return "
579  taId = document.querySelector('#$id .c-input__field textarea')?.id;
580  il.UI.Input.textarea.init(taId);
581  ";
582  }
583  );
584 
585  $tpl = $this->getPreparedTextareaTemplate($component);
586 
587  $label_id = $this->createId();
588  $tpl->setVariable('ID', $label_id);
589  return $this->wrapInFormContext($component, $component->getLabel(), $tpl->get(), $label_id);
590  }
591 
592  protected function getPreparedTextareaTemplate(F\Textarea $component): Template
593  {
594  $tpl = $this->getTemplate("tpl.textarea.html", true, true);
595 
596  if (0 < $component->getMaxLimit()) {
597  $tpl->setVariable('REMAINDER_TEXT', $this->txt('ui_chars_remaining'));
598  $tpl->setVariable('REMAINDER', $component->getMaxLimit() - strlen($component->getValue() ?? ''));
599  $tpl->setVariable('MAX_LIMIT', $component->getMaxLimit());
600  }
601 
602  if (null !== $component->getMinLimit()) {
603  $tpl->setVariable('MIN_LIMIT', $component->getMinLimit());
604  }
605 
606  $this->applyName($component, $tpl);
607  $this->applyValue($component, $tpl, $this->htmlEntities());
608  return $tpl;
609  }
610 
611  protected function renderRadioField(F\Radio $component, RendererInterface $default_renderer): string
612  {
613  $tpl = $this->getTemplate("tpl.radio.html", true, true);
614  $id = $this->createId();
615 
616  foreach ($component->getOptions() as $value => $label) {
617  $opt_id = $id . '_' . $value . '_opt';
618 
619  $tpl->setCurrentBlock('optionblock');
620  $tpl->setVariable("NAME", $component->getName());
621  $tpl->setVariable("OPTIONID", $opt_id);
622  $tpl->setVariable("VALUE", $value);
623  $tpl->setVariable("LABEL", $label);
624 
625  if ($component->getValue() !== null && $component->getValue() == $value) {
626  $tpl->setVariable("CHECKED", 'checked="checked"');
627  }
628  if ($component->isDisabled()) {
629  $tpl->setVariable("DISABLED", 'disabled="disabled"');
630  }
631 
632  $byline = $component->getBylineFor((string) $value);
633  if (!empty($byline)) {
634  $tpl->setVariable("BYLINE", $byline);
635  }
636 
637  $tpl->parseCurrentBlock();
638  }
639 
640  return $this->wrapInFormContext($component, $component->getLabel(), $tpl->get());
641  }
642 
643  protected function renderMultiSelectField(F\MultiSelect $component, RendererInterface $default_renderer): string
644  {
645  $tpl = $this->getTemplate("tpl.multiselect.html", true, true);
646 
647  $options = $component->getOptions();
648  if (count($options) > 0) {
649  $value = $component->getValue();
650  $name = $this->applyName($component, $tpl);
651  foreach ($options as $opt_value => $opt_label) {
652  $tpl->setCurrentBlock("option");
653  $tpl->setVariable("NAME", $name);
654  $tpl->setVariable("VALUE", $opt_value);
655  $tpl->setVariable("LABEL", $opt_label);
656 
657  if ($value && in_array($opt_value, $value)) {
658  $tpl->setVariable("CHECKED", 'checked="checked"');
659  }
660  $tpl->parseCurrentBlock();
661  }
662  } else {
663  $tpl->touchBlock("no_options");
664  }
665 
666  return $this->wrapInFormContext($component, $component->getLabel(), $tpl->get());
667  }
668 
669  protected function renderDateTimeField(F\DateTime $component, RendererInterface $default_renderer): string
670  {
671  list($component, $tpl) = $this->internalRenderDateTimeField($component, $default_renderer);
672  $label_id = $this->createId();
673  $tpl->setVariable('ID', $label_id);
674  return $this->wrapInFormContext($component, $component->getLabel(), $tpl->get(), $label_id);
675  }
676 
680  protected function internalRenderDateTimeField(F\DateTime $component, RendererInterface $default_renderer): array
681  {
682  $tpl = $this->getTemplate("tpl.datetime.html", true, true);
683  $this->applyName($component, $tpl);
684 
685  if ($component->getTimeOnly() === true) {
686  $format = $component::TIME_FORMAT;
687  $dt_type = self::TYPE_TIME;
688  } else {
689  $dt_type = self::TYPE_DATE;
690  $format = $this->getTransformedDateFormat(
691  $component->getFormat(),
692  self::DATEPICKER_FORMAT_MAPPING
693  );
694 
695  if ($component->getUseTime() === true) {
696  $format .= ' ' . $component::TIME_FORMAT;
697  $dt_type = self::TYPE_DATETIME;
698  }
699  }
700 
701  $tpl->setVariable("DTTYPE", $dt_type);
702 
703  $min_max_format = self::DATE_DATEPICKER_MINMAX_FORMAT;
704  if ($dt_type === self::TYPE_DATETIME) {
705  $min_max_format = self::DATETIME_DATEPICKER_MINMAX_FORMAT;
706  }
707 
708  $min_date = $component->getMinValue();
709  if (!is_null($min_date)) {
710  $tpl->setVariable("MIN_DATE", date_format($min_date, $min_max_format));
711  }
712  $max_date = $component->getMaxValue();
713  if (!is_null($max_date)) {
714  $tpl->setVariable("MAX_DATE", date_format($max_date, $min_max_format));
715  }
716 
717  $this->applyValue($component, $tpl, function (?string $value) use ($dt_type) {
718  if ($value !== null) {
719  $value = new \DateTimeImmutable($value);
720  return $value->format(match ($dt_type) {
721  self::TYPE_DATETIME => self::HTML5_NATIVE_DATETIME_FORMAT,
722  self::TYPE_DATE => self::HTML5_NATIVE_DATE_FORMAT,
723  self::TYPE_TIME => self::HTML5_NATIVE_TIME_FORMAT,
724  });
725  }
726  return null;
727  });
728  return [$component, $tpl];
729  }
730 
731  protected function renderDurationField(F\Duration $component, RendererInterface $default_renderer): string
732  {
733  $inputs = $component->getInputs();
734 
735  $input = array_shift($inputs); //from
736  list($input, $tpl) = $this->internalRenderDateTimeField($input, $default_renderer);
737 
738  $from_input_id = $this->createId();
739  $tpl->setVariable('ID', $from_input_id);
740  $input_html = $this->wrapInFormContext($input, $input->getLabel(), $tpl->get(), $from_input_id);
741 
742  $input = array_shift($inputs) //until
743  ->withAdditionalPickerconfig(['useCurrent' => false]);
744  list($input, $tpl) = $this->internalRenderDateTimeField($input, $default_renderer);
745  $until_input_id = $this->createId();
746  $tpl->setVariable('ID', $until_input_id);
747  $input_html .= $this->wrapInFormContext($input, $input->getLabel(), $tpl->get(), $until_input_id);
748 
749  $tpl = $this->getTemplate("tpl.duration.html", true, true);
750  $tpl->setVariable('DURATION', $input_html);
751  return $this->wrapInFormContext($component, $component->getLabel(), $tpl->get());//, $from_input_id);
752  }
753 
754  protected function renderSection(F\Section $section, RendererInterface $default_renderer): string
755  {
756  $inputs_html = $default_renderer->render($section->getInputs());
757 
758  $headline_tpl = $this->getTemplate("tpl.headlines.html", true, true);
759  $headline_tpl->setVariable("HEADLINE", $section->getLabel());
760  $nesting_level = $section->getNestingLevel() + 2;
761  if ($nesting_level > 6) {
762  $nesting_level = 6;
763  };
764  $headline_tpl->setVariable("LEVEL", $nesting_level);
765 
766  $headline_html = $headline_tpl->get();
767 
768  return $this->wrapInFormContext($section, $headline_html, $inputs_html);
769  }
770 
771  protected function renderUrlField(F\Url $component, RendererInterface $default_renderer): string
772  {
773  $tpl = $this->getTemplate("tpl.url.html", true, true);
774  $this->applyName($component, $tpl);
775  $this->applyValue($component, $tpl, $this->escapeSpecialChars());
776 
777  $label_id = $this->createId();
778  $tpl->setVariable('ID', $label_id);
779  return $this->wrapInFormContext($component, $component->getLabel(), $tpl->get(), $label_id);
780  }
781 
782  protected function renderImageField(F\Image $input, RendererInterface $default_renderer): string
783  {
784  return $this->renderFileField($input, $default_renderer);
785  }
786 
787  protected function renderFileField(F\File $input, RendererInterface $default_renderer): string
788  {
789  $template = $this->getTemplate('tpl.file.html', true, true);
790  foreach ($input->getGeneratedDynamicInputs() as $metadata_input) {
791  $file_info = null;
792  if (null !== ($data = $metadata_input->getValue())) {
793  $file_id = (!$input->hasMetadataInputs()) ? $data : $data[0] ?? null;
794 
795  if (null !== $file_id) {
796  $file_info = $input->getUploadHandler()->getInfoResult($file_id);
797  }
798  }
799 
800  $template = $this->renderFilePreview(
801  $input,
802  $metadata_input,
803  $default_renderer,
804  $file_info,
805  $template
806  );
807  }
808 
809  $file_preview_template = $this->getTemplate('tpl.file.html', true, true);
810  $file_preview_template = $this->renderFilePreview(
811  $input,
812  $input->getTemplateForDynamicInputs(),
813  $default_renderer,
814  null,
815  $file_preview_template
816  );
817 
818  $template->setVariable('FILE_PREVIEW_TEMPLATE', $file_preview_template->get('block_file_preview'));
819 
820  $this->setHelpBlockForFileField($template, $input);
821 
822  $input = $this->initClientsideFileInput($input);
823 
824  // display the action button (to choose files).
825  $template->setVariable('ACTION_BUTTON', $default_renderer->render(
826  $this->getUIFactory()->button()->shy(
827  $this->txt('select_files_from_computer'),
828  '#'
829  )
830  ));
831 
832  return $this->wrapInFormContext(
833  $input,
834  $input->getLabel(),
835  $template->get(),
836  );
837  }
838 
839  protected function renderHiddenField(F\Hidden $input): string
840  {
841  $template = $this->getTemplate('tpl.hidden.html', true, true);
842  $this->applyName($input, $template);
843  $this->applyValue($input, $template, $this->escapeSpecialChars());
844  if ($input->isDisabled()) {
845  $template->setVariable("DISABLED", 'disabled="disabled"');
846  }
847  $this->bindJSandApplyId($input, $template);
848  return $template->get();
849  }
850 
854  public function registerResources(ResourceRegistry $registry): void
855  {
856  parent::registerResources($registry);
857  $registry->register('assets/js/tagify.js');
858  $registry->register('assets/css/tagify.css');
859  $registry->register('assets/js/tagInput.js');
860 
861  $registry->register('assets/js/dropzone.min.js');
862  $registry->register('assets/js/dropzone.js');
863  $registry->register('assets/js/input.js');
864  $registry->register('assets/js/core.js');
865  $registry->register('assets/js/file.js');
866  // workaround to manipulate the order of scripts
867  $registry->register('assets/js/drilldown.min.js');
868  $registry->register('assets/js/input.factory.min.js');
869  }
870 
875  protected function setSignals(F\FormInput $input)
876  {
877  $signals = null;
878  foreach ($input->getTriggeredSignals() as $s) {
879  $signals[] = [
880  "signal_id" => $s->getSignal()->getId(),
881  "event" => $s->getEvent(),
882  "options" => $s->getSignal()->getOptions()
883  ];
884  }
885  if ($signals !== null) {
886  $signals = json_encode($signals);
887 
888  $input = $input->withAdditionalOnLoadCode(function ($id) use ($signals) {
889  $code = "il.UI.input.setSignalsForId('$id', $signals);";
890  return $code;
891  });
892 
893  $input = $input->withAdditionalOnLoadCode($input->getUpdateOnLoadCode());
894  }
895  return $input;
896  }
897 
904  protected function getTransformedDateFormat(
905  DateFormat\DateFormat $origin,
906  array $mapping
907  ): string {
908  $ret = '';
909  foreach ($origin->toArray() as $element) {
910  if (array_key_exists($element, $mapping)) {
911  $ret .= $mapping[$element];
912  } else {
913  $ret .= $element;
914  }
915  }
916  return $ret;
917  }
918 
919  protected function renderFilePreview(
920  FI\File $file_input,
921  FormInput $metadata_input,
922  RendererInterface $default_renderer,
923  ?FileInfoResult $file_info,
924  Template $template
925  ): Template {
926  $template->setCurrentBlock('block_file_preview');
927  $template->setVariable('REMOVAL_GLYPH', $default_renderer->render(
928  $this->getUIFactory()->symbol()->glyph()->close()->withAction("#")
929  ));
930 
931  if (null !== $file_info) {
932  $template->setVariable('FILE_NAME', $file_info->getName());
933  $template->setVariable(
934  'FILE_SIZE',
935  (string) (new DataSize($file_info->getSize(), DataSize::Byte))
936  );
937  }
938 
939  // only render expansion toggles if the input
940  // contains actual (unhidden) inputs.
941  if ($file_input->hasMetadataInputs()) {
942  $template->setVariable('EXPAND_GLYPH', $default_renderer->render(
943  $this->getUIFactory()->symbol()->glyph()->expand()->withAction("#")
944  ));
945  $template->setVariable('COLLAPSE_GLYPH', $default_renderer->render(
946  $this->getUIFactory()->symbol()->glyph()->collapse()->withAction("#")
947  ));
948  }
949 
950  $template->setVariable('METADATA_INPUTS', $default_renderer->render($metadata_input));
951 
952  $template->parseCurrentBlock();
953 
954  return $template;
955  }
956 
957  protected function initClientsideFileInput(FI\File $input): FI\File
958  {
959  return $input->withAdditionalOnLoadCode(
960  function ($id) use ($input) {
961  $current_file_count = count($input->getGeneratedDynamicInputs());
962  $translations = json_encode($input->getTranslations());
963  $is_disabled = ($input->isDisabled()) ? 'true' : 'false';
964  $php_upload_limit = $this->getUploadLimitResolver()->getPhpUploadLimitInBytes();
965  $should_upload_be_chunked = ($input->getMaxFileSize() > $php_upload_limit) ? 'true' : 'false';
966  $chunk_size = (int) floor($php_upload_limit * self::FILE_UPLOAD_CHUNK_SIZE_FACTOR);
967  return "
968  $(document).ready(function () {
969  il.UI.Input.File.init(
970  '$id',
971  '{$input->getUploadHandler()->getUploadURL()}',
972  '{$input->getUploadHandler()->getFileRemovalURL()}',
973  '{$input->getUploadHandler()->getFileIdentifierParameterName()}',
974  $current_file_count,
975  {$input->getMaxFiles()},
976  {$input->getMaxFileSize()},
977  '{$this->prepareDropzoneJsMimeTypes($input->getAcceptedMimeTypes())}',
978  $is_disabled,
979  $translations,
980  $should_upload_be_chunked,
981  $chunk_size
982  );
983  });
984  ";
985  }
986  );
987  }
988 
994  protected function prepareDropzoneJsMimeTypes(array $mime_types): string
995  {
996  $mime_type_string = '';
997  foreach ($mime_types as $index => $mime_type) {
998  $mime_type_string .= (isset($mime_types[$index + 1])) ? "$mime_type," : $mime_type;
999  }
1000 
1001  return $mime_type_string;
1002  }
1003 
1004  protected function renderColorSelectField(F\ColorSelect $component, RendererInterface $default_renderer): string
1005  {
1006  $tpl = $this->getTemplate("tpl.color_select.html", true, true);
1007  $this->applyName($component, $tpl);
1008  $tpl->setVariable('VALUE', $component->getValue());
1009 
1010  $label_id = $this->createId();
1011  $tpl->setVariable('ID', $label_id);
1012  return $this->wrapInFormContext($component, $component->getLabel(), $tpl->get(), $label_id);
1013  }
1014 
1015  protected function renderRatingField(F\Rating $component, RendererInterface $default_renderer): string
1016  {
1017  $tpl = $this->getTemplate("tpl.rating.html", true, true);
1018  $id = $this->createId();
1019  $aria_description_id = $id . '_desc';
1020  $tpl->setVariable('DESCRIPTION_SRC_ID', $aria_description_id);
1021 
1022  $option_count = count(FiveStarRatingScale::cases()) - 1;
1023 
1024  foreach (range($option_count, 1, -1) as $option) {
1025  $tpl->setCurrentBlock('scaleoption');
1026  $tpl->setVariable('ARIALABEL', $this->txt($option . 'stars'));
1027  $tpl->setVariable('OPT_VALUE', (string) $option);
1028  $tpl->setVariable('OPT_ID', $id . '-' . $option);
1029  $tpl->setVariable('NAME', $component->getName());
1030  $tpl->setVariable('DESCRIPTION_ID', $aria_description_id);
1031 
1032  if ($component->getValue() === FiveStarRatingScale::from((int) $option)) {
1033  $tpl->setVariable("SELECTED", ' checked="checked"');
1034  }
1035  if ($component->isDisabled()) {
1036  $tpl->setVariable("DISABLED", 'disabled="disabled"');
1037  }
1038  $tpl->parseCurrentBlock();
1039  }
1040 
1041  if (!$component->isRequired()) {
1042  $tpl->setVariable('NEUTRAL_ID', $id . '-0');
1043  $tpl->setVariable('NEUTRAL_NAME', $component->getName());
1044  $tpl->setVariable('NEUTRAL_LABEL', $this->txt('reset_stars'));
1045  $tpl->setVariable('NEUTRAL_DESCRIPTION_ID', $aria_description_id);
1046 
1047  if ($component->getValue() === FiveStarRatingScale::NONE || is_null($component->getValue())) {
1048  $tpl->setVariable('NEUTRAL_SELECTED', ' checked="checked"');
1049  }
1050  }
1051 
1052  if ($txt = $component->getAdditionalText()) {
1053  $tpl->setVariable('TEXT', $txt);
1054  }
1055 
1056  if ($component->isDisabled()) {
1057  $tpl->touchBlock('disabled');
1058  }
1059  if ($average = $component->getCurrentAverage()) {
1060  $average_title = sprintf($this->txt('rating_average'), $average);
1061  $tpl->setVariable('AVERAGE_VALUE', $average_title);
1062  $tpl->setVariable('AVERAGE_VALUE_PERCENT', $average / $option_count * self::CENTUM);
1063  }
1064 
1065  return $this->wrapInFormContext($component, $component->getLabel(), $tpl->get());
1066  }
1067 
1068  protected function renderTreeMultiSelectField(F\TreeMultiSelect $component, RendererInterface $default_renderer): string
1069  {
1070  $template = $this->prepareTreeSelectTemplate($component, $default_renderer);
1071 
1072  if ($component->canSelectChildNodes()) {
1073  $select_child_nodes = 'true';
1074  } else {
1075  $select_child_nodes = 'false';
1076  }
1077 
1078  $enriched_component = $component->withAdditionalOnLoadCode(
1079  static fn($id) => "il.UI.Input.treeSelect.initTreeMultiSelect('$id', $select_child_nodes);"
1080  );
1081 
1082  $id = $this->bindJSandApplyId($enriched_component, $template);
1083 
1084  return $this->wrapInFormContext($component, $component->getLabel(), $template->get(), $id);
1085  }
1086 
1087  protected function renderTreeSelectField(F\TreeSelect $component, RendererInterface $default_renderer): string
1088  {
1089  $template = $this->prepareTreeSelectTemplate($component, $default_renderer);
1090 
1091  $enriched_component = $component->withAdditionalOnLoadCode(
1092  static fn($id) => "il.UI.Input.treeSelect.initTreeSelect('$id');"
1093  );
1094 
1095  $id = $this->bindJSandApplyId($enriched_component, $template);
1096 
1097  return $this->wrapInFormContext($component, $component->getLabel(), $template->get(), $id);
1098  }
1099 
1100  protected function prepareTreeSelectTemplate(
1101  TreeSelect|TreeMultiSelect $component,
1102  RendererInterface $default_renderer,
1103  ): Template {
1104  $template = $this->getTemplate('tpl.tree_select.html', true, true);
1105 
1106  if ($component->isDisabled()) {
1107  $template->setVariable('DISABLED', 'disabled');
1108  }
1109 
1110  $template->setVariable('SELECT_LABEL', $this->txt('select'));
1111  $template->setVariable('CLOSE_LABEL', $this->txt('close'));
1112  $template->setVariable('LABEL', $component->getLabel());
1113 
1114  $template->setVariable('INPUT_TEMPLATE', $default_renderer->render(
1115  $component->getTemplateForDynamicInputs()
1116  ));
1117  $template->setVariable('BREADCRUMB_TEMPLATE', $default_renderer->render(
1118  $this->getUIFactory()->breadcrumbs([$this->getUIFactory()->link()->standard('label', '#')])
1119  ));
1120  $template->setVariable('BREADCRUMBS', $default_renderer->render(
1121  $this->getUIFactory()->breadcrumbs([])
1122  ));
1123 
1124  $node_factory = $this->getUIFactory()->input()->field()->node();
1125  $node_generator = $component->getNodeRetrieval()->getNodes(
1126  $node_factory,
1127  $this->getUIFactory()->symbol()->icon(),
1128  );
1129 
1130  $nodes = [];
1131  foreach ($node_generator as $node) {
1132  // check against public interface, will be delegated to rendering chain.
1133  $this->checkArgInstanceOf('node', $node, Component\Input\Field\Node\Node::class);
1134  $nodes[] = $node;
1135  }
1136 
1137  $template->setVariable('DRILLDOWN', $default_renderer->render(
1138  $this->getUIFactory()->menu()->drilldown($component->getLabel(), $nodes)
1139  ));
1140 
1142  $dynamic_inputs_generator = (static fn() => yield from $component->getGeneratedDynamicInputs())();
1143 
1144  $leaf_generator = $component->getNodeRetrieval()->getNodesAsLeaf(
1145  $this->getUIFactory()->input()->field()->node(),
1146  $this->getUIFactory()->symbol()->icon(),
1147  $component->getValue(),
1148  );
1149 
1150  $lockstep_iterator = $this->iterateGeneratorsInLockstep($leaf_generator, $dynamic_inputs_generator);
1151 
1152  foreach ($lockstep_iterator as [$leaf, $dynamic_input]) {
1153  // check against internal interface, will not be delegated to rendering chain.
1155  $this->checkArgInstanceOf('leaf', $leaf, Node\Leaf::class);
1156 
1157  $value_template = $this->getTemplate('tpl.tree_select.html', true, true);
1158  $value_template->setCurrentBlock('with_value_template');
1159  $value_template->setVariable('NODE_ID', (string) ($leaf->getId()));
1160  $value_template->setVariable('NODE_NAME', $leaf->getName());
1161  $value_template->setVariable('INPUT_TEMPLATE', $default_renderer->render($dynamic_input));
1162  $value_template->setVariable('UNSELECT_NODE_LABEL', sprintf($this->txt('unselect_node'), $leaf->getName()));
1163  $value_template->parseCurrentBlock();
1164 
1165  $template->setCurrentBlock('with_value');
1166  $template->setVariable('VALUE', $value_template->get('with_value_template'));
1167  $template->parseCurrentBlock();
1168  }
1169 
1170  $this->toJS('unselect_node');
1171  $this->toJS('select_node');
1172 
1173  return $template;
1174  }
1175 
1184  {
1185  while ($a->valid() && $b->valid()) {
1186  yield [$a->current(), $b->current()];
1187  $a->next();
1188  $b->next();
1189  }
1190  if ($a->valid() || $b->valid()) {
1191  throw new LogicException('Generators do not have equal lenghts.');
1192  }
1193  }
1194 
1195  private function setHelpBlockForFileField(Template $template, FI\File $input): void
1196  {
1197  $template->setCurrentBlock('HELP_BLOCK');
1198 
1199  $template->setCurrentBlock('MAX_FILE_SIZE');
1200  $template->setVariable('FILE_SIZE_LABEL', $this->txt('file_notice'));
1201  $template->setVariable('FILE_SIZE_VALUE', new DataSize($input->getMaxFileSize(), DataSize::Byte));
1202  $template->parseCurrentBlock();
1203 
1204  $template->setCurrentBlock('MAX_FILES');
1205  $template->setVariable('FILES_LABEL', $this->txt('ui_file_upload_max_nr'));
1206  $template->setVariable('FILES_VALUE', $input->getMaxFiles());
1207  $template->parseCurrentBlock();
1208 
1209  $template->parseCurrentBlock();
1210  }
1211 }
bindJSandApplyId(Component\JavaScriptBindable $component, Template $tpl)
Definition: Renderer.php:240
This implements commonalities between inputs.
Definition: Input.php:42
Registry for resources required by rendered output like Javascript or CSS.
applyName(FormInput $component, Template $tpl)
Definition: Renderer.php:233
This implements the radio input.
Definition: Radio.php:34
renderFilePreview(FI\File $file_input, FormInput $metadata_input, RendererInterface $default_renderer, ?FileInfoResult $file_info, Template $template)
Definition: Renderer.php:919
renderTreeSelectField(F\TreeSelect $component, RendererInterface $default_renderer)
Definition: Renderer.php:1087
FiveStarRatingScale
This is the scale for the Rating Input.
This implements the checkbox input.
Definition: Checkbox.php:35
getGeneratedDynamicInputs()
Returns serverside generated dynamic Inputs, which happens when providing values with.
standard()
description: > This is an example, of how the Notification Slate is generated by assigning Notificat...
Definition: standard.php:38
setHelpBlockForFileField(Template $template, FI\File $input)
Definition: Renderer.php:1195
This implements the textarea input.
Definition: Textarea.php:33
This class provides the data size with additional information to remove the work to calculate the siz...
Definition: DataSize.php:30
trait JavaScriptBindable
Trait for components implementing JavaScriptBindable providing standard implementation.
applyValue(FormInput $component, Template $tpl, ?callable $escape=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).
Definition: Renderer.php:255
txt(string $id)
Get a text from the language file.
renderTagField(F\Tag $component, RendererInterface $default_renderer)
Definition: Renderer.php:394
renderLinkField(F\Link $component, RendererInterface $default_renderer)
Definition: Renderer.php:286
renderRatingField(F\Rating $component, RendererInterface $default_renderer)
Definition: Renderer.php:1015
A password is used as part of credentials for authentication.
Definition: Password.php:30
registerResources(ResourceRegistry $registry)
Announce resources this renderer requires.
Definition: Renderer.php:854
toJS($key)
Add language var to client side (il.Language)
getByline()
Get the byline of the input.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Definition: Checkbox.php:21
This implements the text input.
Definition: Text.php:32
setCurrentBlock(string $name)
Set the block to work on.
renderNumericField(F\Numeric $component, RendererInterface $default_renderer)
Definition: Renderer.php:312
renderImageField(F\Image $input, RendererInterface $default_renderer)
Definition: Renderer.php:782
setVariable(string $name, $value)
Set a variable in the current block.
while($session_entry=$r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) return null
renderUrlField(F\Url $component, RendererInterface $default_renderer)
Definition: Renderer.php:771
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Definition: Checkbox.php:21
renderRadioField(F\Radio $component, RendererInterface $default_renderer)
Definition: Renderer.php:611
A Date Format provides a format definition akin to PHP&#39;s date formatting options, but stores the sing...
Definition: DateFormat.php:26
This implements the duration input group.
Definition: Duration.php:37
renderMultiSelectField(F\MultiSelect $component, RendererInterface $default_renderer)
Definition: Renderer.php:643
getTemplate(string $name, bool $purge_unfilled_vars, bool $purge_unused_blocks)
Get template of component this renderer is made for.
This implements the multi-select input.
Definition: MultiSelect.php:31
getOnLoadCode()
Get the currently bound on load code.
renderFileField(F\File $input, RendererInterface $default_renderer)
Definition: Renderer.php:787
renderCheckboxField(F\Checkbox $component, RendererInterface $default_renderer)
Definition: Renderer.php:325
renderDateTimeField(F\DateTime $component, RendererInterface $default_renderer)
Definition: Renderer.php:669
renderSwitchableGroup(F\SwitchableGroup $component, RendererInterface $default_renderer)
Definition: Renderer.php:357
renderColorSelectField(F\ColorSelect $component, RendererInterface $default_renderer)
Definition: Renderer.php:1004
renderPasswordField(F\Password $component, RendererInterface $default_renderer)
Definition: Renderer.php:429
getValue()
Get the value that is displayed in the input client side.
renderSection(F\Section $section, RendererInterface $default_renderer)
Definition: Renderer.php:754
getTemplateForDynamicInputs()
Returns an Input Field which will be used to generate similar inputs on both server and client...
renderTreeMultiSelectField(F\TreeMultiSelect $component, RendererInterface $default_renderer)
Definition: Renderer.php:1068
getTransformedDateFormat(DateFormat\DateFormat $origin, array $mapping)
Return the datetime format in a form fit for the JS-component of this input.
Definition: Renderer.php:904
renderDurationField(F\Duration $component, RendererInterface $default_renderer)
Definition: Renderer.php:731
This implements the numeric input.
Definition: Numeric.php:33
wrapInFormContext(FormInput $component, string $label, string $input_html, ?string $id_for_label=null, ?string $dependant_group_html=null)
Definition: Renderer.php:173
cannotHandleComponent(Component $component)
This method MUST be called by derived component renderers, if.
$txt
Definition: error.php:31
renderSelectField(F\Select $component, RendererInterface $default_renderer)
Definition: Renderer.php:469
This implements the URL input.
Definition: Url.php:34
register(string $name)
Add a dependency.
internalRenderDateTimeField(F\DateTime $component, RendererInterface $default_renderer)
Definition: Renderer.php:680
parseCurrentBlock()
Parse the block that is currently worked on.
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
Definition: plugin.php:23
iterateGeneratorsInLockstep(\Generator $a, \Generator $b)
Iterates over two Generators in lockstep, yielding their current values as paired arrays which can be...
Definition: Renderer.php:1183
ilErrorHandling $error
Definition: class.ilias.php:69
link(string $caption, string $href, bool $new_viewport=false)
$a
thx to https://mlocati.github.io/php-cs-fixer-configurator for the examples
This describes inputs that can be used in forms.
Definition: FormInput.php:32
renderOptionalGroup(F\OptionalGroup $component, RendererInterface $default_renderer)
Definition: Renderer.php:339
render(Component\Component $component, RendererInterface $default_renderer)
Definition: Renderer.php:88
prepareDropzoneJsMimeTypes(array $mime_types)
Appends all given mime-types to a comma-separated string.
Definition: Renderer.php:994
bindJavaScript(JavaScriptBindable $component)
Bind the component to JavaScript.