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 
29 use ILIAS\Data\URI;
33 
35 {
39  public function render(Component\Component $component, RendererInterface $default_renderer): string
40  {
41  if ($component instanceof Component\Table\Presentation) {
42  return $this->renderPresentationTable($component, $default_renderer);
43  }
44  if ($component instanceof Component\Table\PresentationRow) {
45  return $this->renderPresentationRow($component, $default_renderer);
46  }
47  if ($component instanceof Component\Table\Data) {
48  return $this->renderDataTable($component, $default_renderer);
49  }
50  if ($component instanceof Component\Table\DataRow
51  && !$component instanceof Component\Table\OrderingRow
52  ) {
53  return $this->renderDataRow($component, $default_renderer);
54  }
55  if ($component instanceof Component\Table\Ordering) {
56  return $this->renderOrderingTable($component, $default_renderer);
57  }
58  if ($component instanceof Component\Table\OrderingRow) {
59  return $this->renderOrderingRow($component, $default_renderer);
60  }
61  $this->cannotHandleComponent($component);
62  }
63 
64  protected function renderPresentationTable(
65  Component\Table\Presentation $component,
66  RendererInterface $default_renderer
67  ): string {
68  $tpl = $this->getTemplate("tpl.presentationtable.html", true, true);
69  $tpl->setVariable("TITLE", $component->getTitle());
70  $expcollapsebtns = [];
71  if ($sig_ta = $component->getExpandCollapseAllSignal()) {
72  $sig_ta_expand = clone $sig_ta;
73  $sig_ta_expand->addOption('expand', true);
74  $expcollapsebtns[] = $this->getUIFactory()->button()
75  ->standard($this->txt('presentation_table_expand'), '')
76  ->withOnClick($sig_ta_expand);
77  $sig_ta_collapse = clone $sig_ta;
78  $sig_ta_collapse->addOption('expand', false);
79  $expcollapsebtns[] = $this->getUIFactory()->button()
80  ->standard($this->txt('presentation_table_collapse'), '')
81  ->withOnClick($sig_ta_collapse);
82  $component = $component->withAdditionalOnLoadCode(
83  static fn($id) => "
84  il.UI.table.presentation.init('{$id}');
85  $(document).on('$sig_ta', function(event, signal_data) { il.UI.table.presentation.get('$id').expandAll(signal_data); return false; });
86  "
87  );
88  }
89 
90  $tpl->setVariable("EXPANDCOLLAPSEALL", $default_renderer->render($expcollapsebtns));
91 
92  $vcs = $component->getViewControls();
93  if ($vcs) {
94  $tpl->setVariable("VC", $default_renderer->render($vcs));
95  }
96 
97  $id = $this->bindJavaScript($component);
98  $tpl->setVariable("ID", $id);
99 
100  $row_mapping = $component->getRowMapping();
101  $data = $component->getData();
102  $component_id = $id;
103 
104  if (empty($data)) {
105  $this->renderEmptyPresentationRow($tpl, $default_renderer, $this->txt('ui_table_no_records'));
106  return $tpl->get();
107  }
108 
109  foreach ($data as $record) {
110  $row = $row_mapping(
111  new PresentationRow($component->getSignalGenerator(), $component_id),
112  $record,
113  $this->getUIFactory(),
114  $component->getEnvironment()
115  );
116 
117  $tpl->setCurrentBlock("row");
118  $tpl->setVariable("ROW", $default_renderer->render($row));
119  $tpl->parseCurrentBlock();
120  }
121 
122  return $tpl->get();
123  }
124 
125  protected function renderPresentationRow(
126  Component\Table\PresentationRow $component,
127  RendererInterface $default_renderer
128  ): string {
129  $f = $this->getUIFactory();
130  $tpl = $this->getTemplate("tpl.presentationrow.html", true, true);
131 
132  $component = $this->registerSignals($component->withResetSignals());
133  $sig_show = $component->getShowSignal();
134  $sig_hide = $component->getCloseSignal();
135  $sig_toggle = $component->getToggleSignal();
136  $id = $this->bindJavaScript($component);
137 
138  $expander = $f->symbol()->glyph()->expand("#")
139  ->withOnClick($sig_show);
140  $collapser = $f->symbol()->glyph()->collapse("#")
141  ->withOnClick($sig_hide);
142  $shy_expander = $f->button()->shy($this->txt("presentation_table_more"), "#")
143  ->withOnClick($sig_show);
144 
145  $tpl->setVariable("ID", $id);
146  $tpl->setVariable("EXPANDER", $default_renderer->render($expander));
147  $tpl->setVariable("COLLAPSER", $default_renderer->render($collapser));
148  $tpl->setVariable("SHY_EXPANDER", $default_renderer->render($shy_expander));
149 
150  if ($symbol = $component->getLeadingSymbol()) {
151  $tpl->setVariable("SYMBOL", $default_renderer->render($symbol));
152  }
153  $tpl->setVariable("HEADLINE", $component->getHeadline());
154  $tpl->setVariable("TOGGLE_SIGNAL", $sig_toggle);
155  $subheadline = $component->getSubheadline();
156  if ($subheadline) {
157  $tpl->setVariable("SUBHEADLINE", $subheadline);
158  }
159 
160  foreach ($component->getImportantFields() as $label => $value) {
161  $tpl->setCurrentBlock("important_field");
162  if (is_string($label)) {
163  $tpl->setVariable("IMPORTANT_FIELD_LABEL", $label);
164  }
165  $tpl->setVariable("IMPORTANT_FIELD_VALUE", $value);
166  $tpl->parseCurrentBlock();
167  }
168 
169  $tpl->setVariable("DESCLIST", $default_renderer->render($component->getContent()));
170 
171  $further_fields_headline = $component->getFurtherFieldsHeadline();
172  $further_fields = $component->getFurtherFields();
173 
174  if (count($further_fields) > 0) {
175  $tpl->touchBlock("has_further_fields");
176 
177  if ($further_fields_headline) {
178  $tpl->setVariable("FURTHER_FIELDS_HEADLINE", $further_fields_headline);
179  }
180 
181  foreach ($further_fields as $label => $value) {
182  $tpl->setCurrentBlock("further_field");
183  if (is_string($label)) {
184  $tpl->setVariable("FIELD_LABEL", $label);
185  }
186  $tpl->setVariable("FIELD_VALUE", $value);
187  $tpl->parseCurrentBlock();
188  }
189  }
190 
191  $action = $component->getAction();
192  if (!is_null($action)) {
193  $tpl->setCurrentBlock("button");
194  $tpl->setVariable("BUTTON", $default_renderer->render($action));
195  $tpl->parseCurrentBlock();
196  }
197 
198  return $tpl->get();
199  }
200 
201  public function renderDataTable(Component\Table\Data $component, RendererInterface $default_renderer): string
202  {
203  $tpl = $this->getTemplate("tpl.datatable.html", true, true);
204  $component = $this->registerActions($component);
205 
206  [$component, $view_controls] = $component->applyViewControls(
207  $component->getFilter() ?? [],
208  $component->getAdditionalParameters()
209  );
210 
211  $rows = $component->getDataRetrieval()->getRows(
212  $component->getRowBuilder(),
213  array_keys($component->getVisibleColumns()),
214  $component->getRange(),
215  $component->getOrder(),
216  $component->getFilter(),
217  $component->getAdditionalParameters()
218  );
219 
220  // these column counts and index numbers are meant for aria attributes in the html tpl
221  $compensate_col_index = 1; // aria-colindex expects counting to start with 1, not 0
222  if ($component->hasMultiActions()) { // adds column with checkbox before first data column which is numbered 1.
223  $compensate_col_index += 1;
224  }
225  $compensate_col_count = 0; // count starts with 1, only needs to be compensated for special columns
226  if ($component->hasMultiActions()) {
227  $compensate_col_count += 1;
228  }
229  if ($component->hasSingleActions()) { // adds column with action dropdown at the very end
230  $compensate_col_count += 1;
231  }
232 
233  $id = $this->bindJavaScript($component);
234  $tpl->setVariable('ID', $id);
235  $tpl->setVariable('TITLE', $component->getTitle());
236  $tpl->setVariable('COL_COUNT', (string) $component->getColumnCount() + $compensate_col_count);
237  $tpl->setVariable('VIEW_CONTROLS', $default_renderer->render($view_controls));
238 
239  $sortation_signal = null;
240  // if the generator is empty, and thus invalid, we render an empty row.
241  if (!$rows->valid()) {
242  $this->renderFullWidthDataCell($component, $tpl, $this->txt('ui_table_no_records'));
243  } else {
244  $this->renderActionsHeader($default_renderer, $component, $tpl, $compensate_col_count);
245  $this->appendTableRows($tpl, $rows, $default_renderer);
246 
247  if ($component->hasMultiActions()) {
248  $multi_actions = $component->getMultiActions();
249  $modal = $this->buildMultiActionsAllObjectsModal($multi_actions, $id);
250  $multi_actions_dropdown = $this->buildMultiActionsDropdown(
251  $multi_actions,
252  $component->getMultiActionSignal(),
253  $modal->getShowSignal()
254  );
255  $multi_action_col_span = count($component->getVisibleColumns()) + $compensate_col_count;
256  $tpl->setVariable('MULTI_ACTION_SPAN', (string) $multi_action_col_span);
257  $tpl->setVariable('MULTI_ACTION_TRIGGERER', $default_renderer->render($multi_actions_dropdown));
258  $tpl->setVariable('MULTI_ACTION_ALL_MODAL', $default_renderer->render($modal));
259  $tpl->setVariable('MULTI_ACTION_WARNING', $default_renderer->render($this->getUrlTooLongWarning()));
260  $tpl->setVariable('MULTI_ACTION_WARNING_BUTTON_CLOSE_LABEL', $this->txt('datatable_close_warning'));
261  }
262 
263  $sortation_view_control = array_filter(
264  $view_controls->getInputs(),
265  static fn($i): bool => $i instanceof Component\Input\ViewControl\Sortation
266  );
267  if ($sortation_view_control) {
268  $sortation_signal = array_shift($sortation_view_control)->getInternalSignal();
269  $sortation_signal->addOption('parent_container', $id);
270  }
271  }
272 
273  $this->renderTableHeader($default_renderer, $component, $tpl, $sortation_signal, $compensate_col_index);
274  return $tpl->get();
275  }
276 
277  protected function renderTableHeader(
278  RendererInterface $default_renderer,
279  Component\Table\Data $component,
280  Template $tpl,
281  ?Component\Signal $sortation_signal,
282  int $compensate_col_index,
283  ): void {
284  $order = $component->getOrder();
285  $glyph_factory = $this->getUIFactory()->symbol()->glyph();
286  $sort_col = key($order->get());
287  $sort_direction = current($order->get());
288  $columns = $component->getVisibleColumns();
289 
290  foreach ($columns as $col_id => $col) {
291  $param_sort_direction = Order::ASC;
292  $col_title = $col->getTitle();
293  if ($col_id === $sort_col) {
294  if ($sort_direction === Order::ASC) {
295  $sortation = "ascending"; // aria-sort should not be translated and always be in English
296  $sortation_glyph = $glyph_factory->sortAscending("#");
297  $param_sort_direction = Order::DESC;
298  }
299  if ($sort_direction === Order::DESC) {
300  $sortation = "descending"; // aria-sort should not be translated and always be in English
301  $sortation_glyph = $glyph_factory->sortDescending("#");
302  }
303  }
304 
305  $tpl->setCurrentBlock('header_cell');
306 
307  $tpl->setVariable('COL_INDEX', (string) $col->getIndex() + $compensate_col_index);
308 
309  if ($col->isSortable() && !is_null($sortation_signal)) {
310  $sort_signal = clone $sortation_signal;
311  $sort_signal->addOption('value', "$col_id:$param_sort_direction");
312  $col_title = $default_renderer->render(
313  $this->getUIFactory()->button()->shy($col_title, $sort_signal)
314  );
315 
316  if ($col_id === $sort_col) {
317  $sortation_glyph = $default_renderer->render($sortation_glyph->withOnClick($sort_signal));
318  $tpl->setVariable('COL_SORTATION', $sortation);
319  $tpl->setVariable('COL_SORTATION_GLYPH', $sortation_glyph);
320  }
321  }
322 
323  $tpl->setVariable('COL_TITLE', $col_title);
324  $tpl->setVariable('COL_TYPE', strtolower($col->getType()));
325  $tpl->parseCurrentBlock();
326  }
327  }
328 
329  protected function renderActionsHeader(
330  RendererInterface $default_renderer,
331  Component\Table\Table $component,
332  Template $tpl,
333  int $compensate_col_count,
334  ): void {
335  if ($component->hasSingleActions()) {
336  $tpl->setVariable('COL_INDEX_ACTION', (string) $component->getColumnCount() + $compensate_col_count);
337  $tpl->setVariable('COL_TITLE_ACTION', $this->txt('actions'));
338  }
339 
340  if ($component->hasMultiActions()) {
341  $glyph_factory = $this->getUIFactory()->symbol()->glyph();
342  $signal = $component->getSelectionSignal();
343  $sig_all = clone $signal;
344  $sig_all->addOption('select', true);
345  $select_all = $glyph_factory->add()->withOnClick($sig_all);
346  $signal->addOption('select', false);
347  $select_none = $glyph_factory->close()->withOnClick($signal);
348  $tpl->setVariable('SELECTION_CONTROL_SELECT', $default_renderer->render($select_all));
349  $tpl->setVariable('SELECTION_CONTROL_DESELECT', $default_renderer->render($select_none));
350  }
351 
352  if ($component instanceof Component\Table\Ordering) {
353  if (!$component->isOrderingDisabled() && !$component->hasMultiActions()) {
354  $tpl->touchBlock('header_rowselection_cell');
355  }
356  }
357  }
358 
363  protected function renderFullWidthDataCell(Component\Table\Data $component, Template $tpl, string $content): void
364  {
365  $cell_tpl = $this->getTemplate('tpl.datacell.html', true, true);
366  $cell_tpl->setCurrentBlock('cell');
367  $cell_tpl->setVariable('CELL_CONTENT', $content);
368  $cell_tpl->setVariable('COL_SPAN', count($component->getVisibleColumns()));
369  $cell_tpl->setVariable('COL_TYPE', 'full-width');
370  $cell_tpl->setVariable('COL_INDEX', '1');
371  $cell_tpl->parseCurrentBlock();
372 
373  $tpl->setCurrentBlock('row');
374  $tpl->setVariable('ALTERNATION', 'even');
375  $tpl->setVariable('CELLS', $cell_tpl->get());
376  $tpl->parseCurrentBlock();
377  }
378 
379  protected function registerActions(Component\Table\Table $component): Component\Table\Table
380  {
381  $opt_action_id = Action::OPT_ACTIONID;
382  $opt_row_id = Action::OPT_ROWID;
383 
384  $component = $component
385  ->withAdditionalOnLoadCode(
386  static fn($id): string =>
387  "il.UI.table.data.init('{$id}','{$opt_action_id}','{$opt_row_id}');"
388  )
389  ->withAdditionalOnLoadCode($this->getAsyncActionHandler($component->getAsyncActionSignal()))
390  ->withAdditionalOnLoadCode($this->getMultiActionHandler($component->getMultiActionSignal()))
391  ->withAdditionalOnLoadCode($this->getSelectionHandler($component->getSelectionSignal()));
392 
393  $actions = [];
394  foreach ($component->getAllActions() as $action_id => $action) {
395  $component = $component->withAdditionalOnLoadCode($this->getActionRegistration((string) $action_id, $action));
396  if ($action->isAsync()) {
397  $signal = clone $component->getAsyncActionSignal();
398  $signal->addOption(Action::OPT_ACTIONID, $action_id);
399  $action = $action->withSignalTarget($signal);
400  }
401  $actions[$action_id] = $action;
402  }
403  $component = $component->withActions($actions);
404 
405  if ($component->hasMultiActions()) {
406  $component = $component->withAdditionalOnLoadCode(
407  static fn($id): string => "il.UI.table.data.get('{$id}').selectAll(false);"
408  );
409  }
410 
411  return $component;
412  }
413 
414  protected function appendTableRows(
415  Template $tpl,
416  \Generator|array $rows,
417  RendererInterface $default_renderer
418  ): void {
419  $alternate = 'even';
420  foreach ($rows as $row) {
421  $row_contents = $default_renderer->render($row);
422  $alternate = ($alternate === 'odd') ? 'even' : 'odd';
423  $tpl->setCurrentBlock('row');
424  $tpl->setVariable('ALTERNATION', $alternate);
425  $tpl->setVariable('CELLS', $row_contents);
426  $tpl->parseCurrentBlock();
427  }
428  }
429 
430  protected function renderEmptyPresentationRow(Template $tpl, RendererInterface $default_renderer, string $content): void
431  {
432  $row_tpl = $this->getTemplate('tpl.presentationrow_empty.html', true, true);
433  $row_tpl->setVariable('CONTENT', $content);
434  $tpl->setVariable('ROW', $row_tpl->get());
435  }
436 
441  array $actions,
442  string $table_id
443  ): \ILIAS\UI\Component\Modal\RoundTrip {
444  $f = $this->getUIFactory();
445 
446  $msg = $f->messageBox()->confirmation($this->txt('datatable_multiactionmodal_msg'));
447 
448  $select = $f->input()->field()->select(
449  $this->txt('datatable_multiactionmodal_actionlabel'),
450  array_map(
451  static fn($action): string => $action->getLabel(),
452  $actions
453  ),
454  ""
455  )->withRequired(true);
456  $submit = $f->button()->primary($this->txt('datatable_multiactionmodal_apply'), '')
457  ->withOnLoadCode(
458  static fn($id): string => "$('#{$id}').click(function() { il.UI.table.data.get('{$table_id}').doActionForAll(this); return false; });"
459  );
460  $modal = $f->modal()
461  ->roundtrip($this->txt('datatable_multiactionmodal_title'), [$msg, $select])
462  ->withActionButtons([$submit]);
463  return $modal;
464  }
465 
466  protected function getUrlTooLongWarning(): \ILIAS\UI\Component\MessageBox\MessageBox
467  {
468  $f = $this->getUIFactory();
469  return $f->messageBox()->failure($this->txt('warning_url_too_long_msg'));
470  }
471 
475  protected function buildMultiActionsDropdown(
476  array $actions,
477  Component\Signal $action_signal,
478  Component\Signal $modal_signal,
480  if ($actions === []) {
481  return null;
482  }
483  $f = $this->getUIFactory();
484  $glyph = $f->symbol()->glyph()->bulletlist();
485  $buttons = [];
486  $all_obj_buttons = [];
487  foreach ($actions as $action_id => $act) {
488  $signal = clone $action_signal;
489  $signal->addOption(Action::OPT_ACTIONID, $action_id);
490  $buttons[] = $f->button()->shy($act->getLabel(), $signal);
491  }
492 
493  $buttons[] = $f->divider()->horizontal();
494  $buttons[] = $f->button()->shy($this->txt('datatable_multiactionmodal_listentry'), '#')->withOnClick($modal_signal);
495 
496  return $f->dropdown()->standard($buttons)->withLabel($this->txt('datatable_multiaction_label'));
497  }
498 
499  protected function getAsyncActionHandler(Component\Signal $action_signal): \Closure
500  {
501  return static function ($id) use ($action_signal): string {
502  return "
503  $(document).on('{$action_signal}', function(event, signal_data) {
504  il.UI.table.data.get('{$id}').doSingleAction(signal_data);
505  return false;
506  });";
507  };
508  }
509  protected function getMultiActionHandler(Component\Signal $action_signal): \Closure
510  {
511  return static function ($id) use ($action_signal): string {
512  return "
513  $(document).on('{$action_signal}', function(event, signal_data) {
514  il.UI.table.data.get('{$id}').doMultiAction(signal_data);
515  return false;
516  });";
517  };
518  }
519 
520  protected function getSelectionHandler(Component\Signal $selection_signal): \Closure
521  {
522  return static function ($id) use ($selection_signal): string {
523  return "
524  $(document).on('{$selection_signal}', function(event, signal_data) {
525  il.UI.table.data.get('{$id}').selectAll(signal_data.options.select);
526  return false;
527  });
528  ";
529  };
530  }
531 
532  protected function getActionRegistration(
533  string $action_id,
534  Action $action
535  ): \Closure {
536  $async = $action->isAsync() ? 'true' : 'false';
537  $url_builder_js = $action->getURLBuilderJS();
538  $tokens_js = $action->getURLBuilderTokensJS();
539 
540  return static function ($id) use ($action_id, $async, $url_builder_js, $tokens_js): string {
541  return "
542  il.UI.table.data.get('{$id}').registerAction('{$action_id}', {$async}, {$url_builder_js}, {$tokens_js});
543  ";
544  };
545  }
546 
547  public function renderDataRow(Component\Table\DataRow $component, RendererInterface $default_renderer): string
548  {
549  $cell_tpl = $this->getTemplate("tpl.datacell.html", true, true);
550  $this->fillCells($component, $cell_tpl, $default_renderer);
551 
552 
553  return $cell_tpl->get();
554  }
555 
556  public function renderOrderingRow(Component\Table\OrderingRow $component, RendererInterface $default_renderer): string
557  {
558  $cell_tpl = $this->getTemplate("tpl.orderingcell.html", true, true);
559  $this->fillCells($component, $cell_tpl, $default_renderer);
560 
561 
562  if ($component->isOrderingDisabled()) {
563  return $cell_tpl->get();
564  }
565 
566  $namesource = new class () implements NameSource {
567  public function getNewName(): string
568  {
569  return '';
570  }
571  public function getNewDedicatedName(string $dedicated_name): string
572  {
573  return $dedicated_name;
574  }
575  };
576 
577  $numeric_label = $this->txt("ui_table_order");
578  $input = $this->getUIFactory()->input()->field()->numeric($numeric_label)
579  ->withDedicatedName($component->getId())
580  ->withNameFrom($namesource)
581  ->withValue($component->getPosition() * 10);
582  $cell_tpl->setVariable('ORDER_INPUT', $default_renderer->render($input));
583 
584  return $cell_tpl->get();
585  }
586 
587 
588  protected function fillCells(
589  Component\Table\DataRow $row,
590  Template $cell_tpl,
591  RendererInterface $default_renderer
592  ) {
593  $cols = $row->getColumns();
594  foreach ($cols as $col_id => $column) {
595  if ($column->isHighlighted()) {
596  $cell_tpl->touchBlock('highlighted');
597  }
598  $cell_tpl->setCurrentBlock('cell');
599  $cell_tpl->setVariable('COL_TYPE', strtolower($column->getType()));
600  $cell_content = $row->getCellContent($col_id);
601  if ($cell_content instanceof Component\Component) {
602  $cell_content = $default_renderer->render($cell_content);
603  }
604  $cell_tpl->setVariable('CELL_CONTENT', $cell_content);
605  $cell_tpl->setVariable('CELL_COL_TITLE', $row->getColumns()[$col_id]->getTitle());
606  $cell_tpl->parseCurrentBlock();
607  }
608 
609  if ($row->tableHasMultiActions()) {
610  $cell_tpl->setVariable('ROW_ID', $row->getId());
611  }
612  if ($row->tableHasSingleActions()) {
613  $row_actions_dropdown = $this->getSingleActionsForRow(
614  $row->getId(),
615  $row->getActions()
616  );
617  $cell_tpl->setVariable('ACTION_CONTENT', $default_renderer->render($row_actions_dropdown));
618  }
619 
620  if ($row instanceof Component\Table\OrderingRow) {
621  if (!$row->isOrderingDisabled()) {
622  $drag_handle = $this->getUIFactory()->symbol()->glyph()->dragHandle();
623  $drag_handle = $default_renderer->render($drag_handle);
624  $cell_tpl->setVariable('DRAG_HANDLE', $drag_handle);
625  }
626  }
627  }
628 
632  protected function getSingleActionsForRow(string $row_id, array $actions): \ILIAS\UI\Component\Dropdown\Standard
633  {
634  $f = $this->getUIFactory();
635  $buttons = [];
636  foreach ($actions as $act) {
637  $act = $act->withRowId($row_id);
638  $target = $act->getTarget();
639  if ($target instanceof URI) {
640  $target = (string) $target;
641  }
642  $buttons[] = $f->button()->shy($act->getLabel(), $target);
643  }
644  return $f->dropdown()->standard($buttons);
645  }
646 
647 
648  public function renderOrderingTable(Component\Table\Ordering $component, RendererInterface $default_renderer): string
649  {
650  $tpl = $this->getTemplate("tpl.orderingtable.html", true, true);
651  $component = $this->registerActions($component);
652 
653  [$component, $view_controls] = $component->applyViewControls();
654 
655  $rows = $component->getDataBinding()->getRows(
656  $component->getRowBuilder(),
657  array_keys($component->getVisibleColumns()),
658  );
659 
660 
661  if (!$component->isOrderingDisabled()) {
662  $component = $component->withAdditionalOnLoadCode(
663  static fn($id): string => "il.UI.table.ordering.init('{$id}');"
664  );
665  }
666 
667  // these column counts and index numbers are meant for aria attributes in the html tpl
668  $compensate_col_index = 1; // aria-colindex expects counting to start with 1, not 0
669  // for column with checkbox and/or drag handle before first data column which is numbered 1.
670  if ($component->hasMultiActions() || !$component->isOrderingDisabled()) {
671  $compensate_col_index += 1; // checkbox & drag handle
672  }
673  if (!$component->isOrderingDisabled()) {
674  $compensate_col_index += 1; // Position input
675  }
676 
677  $compensate_col_count = 0; // count starts with 1, only needs to be compensated for special columns
678  if ($component->hasMultiActions() || !$component->isOrderingDisabled()) {
679  $compensate_col_count += 1; // checkbox & drag handle
680  }
681  if (!$component->isOrderingDisabled()) {
682  $compensate_col_count += 1; // Position input
683  }
684  if ($component->hasSingleActions()) { // adds column with action dropdown at the very end
685  $compensate_col_count += 1;
686  }
687 
688  $tableid = $this->bindJavaScript($component) ?? $this->createId();
689 
690  if (!$component->isOrderingDisabled()) {
691  $submit = $this->getUIFactory()->button()->standard($this->txt('sorting_save'), "")
692  ->withOnLoadCode(static fn($id) => "document.getElementById('$id').addEventListener('click',
693  function() {document.querySelector('#$tableid form.c-table-ordering__form').submit();return false;});");
694 
695  $tpl->setVariable('FORM_BUTTONS', $default_renderer->render($submit));
696  $tpl->setVariable('POS_INPUT_TITLE', $this->txt('table_posinput_col_title'));
697 
698  }
699 
700  $tpl->setVariable('ID', $tableid);
701  $tpl->setVariable('TARGET_URL', $component->getTargetURL() ? $component->getTargetURL()->__toString() : '#');
702  $tpl->setVariable('TITLE', $component->getTitle());
703  $tpl->setVariable('COL_COUNT', (string) $component->getColumnCount() + $compensate_col_count);
704  $tpl->setVariable('VIEW_CONTROLS', $default_renderer->render($view_controls));
705 
706  $columns = $component->getVisibleColumns();
707  foreach ($columns as $col_id => $col) {
708  $col_title = $col->getTitle();
709  $tpl->setCurrentBlock('header_cell');
710  $tpl->setVariable('COL_INDEX', (string) $col->getIndex() + $compensate_col_index);
711  $tpl->setVariable('COL_TITLE', $col_title);
712  $tpl->setVariable('COL_TYPE', strtolower($col->getType()));
713  $tpl->parseCurrentBlock();
714  }
715 
716  $rows = iterator_to_array($rows);
717  $r = [];
718  foreach ($rows as $idx => $row) {
719  $r[] = $row
720  ->withPosition($idx + 1)
721  ->withOrderingDisabled($component->isOrderingDisabled());
722  }
723 
724  $this->renderActionsHeader($default_renderer, $component, $tpl, $compensate_col_count);
725  $this->appendTableRows($tpl, $r, $default_renderer);
726 
727  if ($component->hasMultiActions()) {
728  $multi_actions = $component->getMultiActions();
729  $modal = $this->buildMultiActionsAllObjectsModal($multi_actions, $tableid);
730  $multi_actions_dropdown = $this->buildMultiActionsDropdown(
731  $multi_actions,
732  $component->getMultiActionSignal(),
733  $modal->getShowSignal()
734  );
735 
736  $tpl->setVariable('MULTI_ACTION_TRIGGERER', $default_renderer->render($multi_actions_dropdown));
737  $tpl->setVariable('MULTI_ACTION_ALL_MODAL', $default_renderer->render($modal));
738  $tpl->setVariable('MULTI_ACTION_WARNING', $default_renderer->render($this->getUrlTooLongWarning()));
739  $tpl->setVariable('MULTI_ACTION_WARNING_BUTTON_CLOSE_LABEL', $this->txt('datatable_close_warning'));
740  }
741  if ($component->hasMultiActions() || !$component->isOrderingDisabled()) {
742  $multi_action_col_span = count($component->getVisibleColumns()) + $compensate_col_count;
743  $tpl->setVariable('MULTI_ACTION_SPAN', (string) $multi_action_col_span);
744  }
745 
746  return $tpl->get();
747  }
748 
752  public function registerResources(ResourceRegistry $registry): void
753  {
754  parent::registerResources($registry);
755  $registry->register('assets/js/table.min.js');
756  $registry->register('assets/js/modal.min.js');
757  }
758 
760  {
761  $show = $component->getShowSignal();
762  $close = $component->getCloseSignal();
763  $toggle = $component->getToggleSignal();
764  $table_id = $component->getTableId();
765  return $component->withAdditionalOnLoadCode(
766  static fn($id): string =>
767  "$(document).on('$show', function() { il.UI.table.presentation.get('$table_id').expandRow('$id'); return false; });" .
768  "$(document).on('$close', function() { il.UI.table.presentation.get('$table_id').collapseRow('$id'); return false; });" .
769  "$(document).on('$toggle', function() { il.UI.table.presentation.get('$table_id').toggleRow('$id'); return false; });"
770  );
771  }
772 }
Registry for resources required by rendered output like Javascript or CSS.
button(string $caption, string $cmd)
registerSignals(Component\Table\PresentationRow $component)
Definition: Renderer.php:759
renderPresentationTable(Component\Table\Presentation $component, RendererInterface $default_renderer)
Definition: Renderer.php:64
renderTableHeader(RendererInterface $default_renderer, Component\Table\Data $component, Template $tpl, ?Component\Signal $sortation_signal, int $compensate_col_index,)
Definition: Renderer.php:277
Interface Observer Contains several chained tasks and infos about them.
getActionRegistration(string $action_id, Action $action)
Definition: Renderer.php:532
txt(string $id)
Get a text from the language file.
registerResources(ResourceRegistry $registry)
Announce resources this renderer requires.
Definition: Renderer.php:752
setCurrentBlock(string $name)
Set the block to work on.
This describes commonalities between all types of Dropdowns.
Definition: Dropdown.php:34
buildMultiActionsAllObjectsModal(array $actions, string $table_id)
Definition: Renderer.php:440
setVariable(string $name, $value)
Set a variable in the current block.
render(Component\Component $component, RendererInterface $default_renderer)
Definition: Renderer.php:39
while($session_entry=$r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) return null
renderPresentationRow(Component\Table\PresentationRow $component, RendererInterface $default_renderer)
Definition: Renderer.php:125
getSingleActionsForRow(string $row_id, array $actions)
Definition: Renderer.php:632
getAsyncActionHandler(Component\Signal $action_signal)
Definition: Renderer.php:499
registerActions(Component\Table\Table $component)
Definition: Renderer.php:379
renderDataTable(Component\Table\Data $component, RendererInterface $default_renderer)
Definition: Renderer.php:201
renderFullWidthDataCell(Component\Table\Data $component, Template $tpl, string $content)
Renders a full-width cell with a single message within, indication there is no data to display...
Definition: Renderer.php:363
getTemplate(string $name, bool $purge_unfilled_vars, bool $purge_unused_blocks)
Get template of component this renderer is made for.
renderDataRow(Component\Table\DataRow $component, RendererInterface $default_renderer)
Definition: Renderer.php:547
fillCells(Component\Table\DataRow $row, Template $cell_tpl, RendererInterface $default_renderer)
Definition: Renderer.php:588
cannotHandleComponent(Component $component)
This method MUST be called by derived component renderers, if.
register(string $name)
Add a dependency.
getSelectionHandler(Component\Signal $selection_signal)
Definition: Renderer.php:520
renderOrderingTable(Component\Table\Ordering $component, RendererInterface $default_renderer)
Definition: Renderer.php:648
parseCurrentBlock()
Parse the block that is currently worked on.
appendTableRows(Template $tpl, \Generator|array $rows, RendererInterface $default_renderer)
Definition: Renderer.php:414
withNameFrom(NameSource $source, ?string $parent_name=null)
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
Definition: plugin.php:23
touchBlock(string $name)
Touch a block without working further on it.
renderActionsHeader(RendererInterface $default_renderer, Component\Table\Table $component, Template $tpl, int $compensate_col_count,)
Definition: Renderer.php:329
Describes a source for input names.
Definition: NameSource.php:26
const DESC
Definition: Order.php:31
renderEmptyPresentationRow(Template $tpl, RendererInterface $default_renderer, string $content)
Definition: Renderer.php:430
getMultiActionHandler(Component\Signal $action_signal)
Definition: Renderer.php:509
renderOrderingRow(Component\Table\OrderingRow $component, RendererInterface $default_renderer)
Definition: Renderer.php:556
buildMultiActionsDropdown(array $actions, Component\Signal $action_signal, Component\Signal $modal_signal,)
Definition: Renderer.php:475
$r
bindJavaScript(JavaScriptBindable $component)
Bind the component to JavaScript.