From e49f1157101ec229dd52d4fc66ac476dabd2eee1 Mon Sep 17 00:00:00 2001 From: Nils Haagen Date: Thu, 26 Oct 2023 15:36:16 +0200 Subject: [PATCH] UI/DataTable: View Controls for the Table (#6222) * UI: minor format fix in roadmap * UI/DataTable: integrate ViewControls --------- Co-authored-by: Richard Klees Co-authored-by: Timon Amstutz --- .../classes/Dependencies/InitUIFramework.php | 2 + src/Data/Range.php | 20 +++ src/UI/Component/Table/Data.php | 4 +- .../Input/Container/ViewControl/Renderer.php | 20 ++- .../Input/ViewControl/Pagination.php | 2 +- .../Component/Input/ViewControl/Renderer.php | 13 +- .../Implementation/Component/Table/Data.php | 124 +++++++++++--- .../Component/Table/Factory.php | 6 + .../Component/Table/Renderer.php | 108 +++++-------- src/UI/ROADMAP.md | 17 +- src/UI/examples/Table/Action/Multi/base.php | 3 +- src/UI/examples/Table/Action/Single/base.php | 3 +- .../examples/Table/Action/Standard/base.php | 3 +- src/UI/examples/Table/Column/Boolean/base.php | 3 +- src/UI/examples/Table/Column/Date/base.php | 3 +- src/UI/examples/Table/Column/EMail/base.php | 3 +- src/UI/examples/Table/Column/Number/base.php | 3 +- src/UI/examples/Table/Column/Status/base.php | 3 +- .../examples/Table/Column/StatusIcon/base.php | 3 +- src/UI/examples/Table/Column/Text/base.php | 3 +- .../examples/Table/Column/TimeSpan/base.php | 3 +- src/UI/examples/Table/Data/base.php | 34 ++-- .../Table/Data/repo_implementation.php | 12 +- src/UI/examples/Table/Data/without_data.php | 11 +- tests/UI/Component/Table/DataRendererTest.php | 79 +++++---- tests/UI/Component/Table/DataTest.php | 45 ++---- .../Component/Table/DataViewControlsTest.php | 153 ++++++++++++++++++ tests/UI/Component/Table/PresentationTest.php | 25 +-- tests/UI/Component/Table/TableTestBase.php | 86 ++++++++++ 29 files changed, 585 insertions(+), 209 deletions(-) create mode 100644 tests/UI/Component/Table/DataViewControlsTest.php create mode 100644 tests/UI/Component/Table/TableTestBase.php diff --git a/Services/Init/classes/Dependencies/InitUIFramework.php b/Services/Init/classes/Dependencies/InitUIFramework.php index 764dfa032369..a0f4c44b463b 100644 --- a/Services/Init/classes/Dependencies/InitUIFramework.php +++ b/Services/Init/classes/Dependencies/InitUIFramework.php @@ -140,6 +140,8 @@ public function init(\ILIAS\DI\Container $c): void $row_builder = new ILIAS\UI\Implementation\Component\Table\DataRowBuilder(); return new ILIAS\UI\Implementation\Component\Table\Factory( $c["ui.signal_generator"], + $c['ui.factory.input.viewcontrol'], + $c['ui.factory.input.container.viewcontrol'], $c["ui.data_factory"], $c["ui.factory.table.column"], $c["ui.factory.table.action"], diff --git a/src/Data/Range.php b/src/Data/Range.php index e108bef0b1a0..74a98e8f8405 100644 --- a/src/Data/Range.php +++ b/src/Data/Range.php @@ -1,5 +1,21 @@ length === PHP_INT_MAX) { + return PHP_INT_MAX; + } + return $this->start + $this->length; } diff --git a/src/UI/Component/Table/Data.php b/src/UI/Component/Table/Data.php index e83c74c6bb81..da6de61030a6 100644 --- a/src/UI/Component/Table/Data.php +++ b/src/UI/Component/Table/Data.php @@ -56,8 +56,8 @@ public function withNumberOfRows(int $number_of_rows): self; */ public function withSelectedOptionalColumns(array $selected_optional_column_ids): self; - public function withOrder(Order $order): self; - public function withRange(Range $range): self; + public function withOrder(?Order $order): self; + public function withRange(?Range $range): self; public function withFilter(?array $filter): self; public function withAdditionalParameters(?array $additional_parameters): self; } diff --git a/src/UI/Implementation/Component/Input/Container/ViewControl/Renderer.php b/src/UI/Implementation/Component/Input/Container/ViewControl/Renderer.php index 99336dff7a73..2b55033fc7e8 100644 --- a/src/UI/Implementation/Component/Input/Container/ViewControl/Renderer.php +++ b/src/UI/Implementation/Component/Input/Container/ViewControl/Renderer.php @@ -84,11 +84,23 @@ function(event, signalData) { fn($k) => ! in_array($k, $input_names), ARRAY_FILTER_USE_KEY ); + // The remaining parameters for the view controls need to be stuffed into + // hidden fields, so the browser passes them as query parameters once the + // form is submitted. foreach ($query_params as $k => $v) { - $tpl->setCurrentBlock('param'); - $tpl->setVariable("PARAM_NAME", $k); - $tpl->setVariable("VALUE", $v); - $tpl->parseCurrentBlock(); + if (is_array($v)) { + foreach (array_values($v) as $arrv) { + $tpl->setCurrentBlock('param'); + $tpl->setVariable("PARAM_NAME", $k . '[]'); + $tpl->setVariable("VALUE", $arrv); + $tpl->parseCurrentBlock(); + } + } else { + $tpl->setCurrentBlock('param'); + $tpl->setVariable("PARAM_NAME", $k); + $tpl->setVariable("VALUE", $v); + $tpl->parseCurrentBlock(); + } } $inputs = array_map( diff --git a/src/UI/Implementation/Component/Input/ViewControl/Pagination.php b/src/UI/Implementation/Component/Input/ViewControl/Pagination.php index 6b237ef3b6b6..de38f7f0162c 100644 --- a/src/UI/Implementation/Component/Input/ViewControl/Pagination.php +++ b/src/UI/Implementation/Component/Input/ViewControl/Pagination.php @@ -36,7 +36,7 @@ class Pagination extends ViewControlInput implements VCInterface\Pagination, Has use ComponentHelper; use GroupDecorator; - protected const DEFAULT_LIMITS = [5, 10, 25, 50, 100, 250, 500, \PHP_INT_MAX]; + public const DEFAULT_LIMITS = [5, 10, 25, 50, 100, 250, 500, \PHP_INT_MAX]; protected const NUMBER_OF_VISIBLE_SECTIONS = 7; protected Signal $internal_selection_signal; diff --git a/src/UI/Implementation/Component/Input/ViewControl/Renderer.php b/src/UI/Implementation/Component/Input/ViewControl/Renderer.php index 7cc1ae9e9a6a..7026a9dfee11 100644 --- a/src/UI/Implementation/Component/Input/ViewControl/Renderer.php +++ b/src/UI/Implementation/Component/Input/ViewControl/Renderer.php @@ -153,9 +153,16 @@ protected function renderSortation(Sortation $component, RendererInterface $defa $component = $component->withAdditionalOnLoadCode( fn($id) => "$(document).on('{$internal_signal}', function(event, signal_data) { - let inputs = event.target - .closest('.il-viewcontrol-sortation') - .querySelectorAll('.il-viewcontrol-value > input'); + let container; + if(signal_data.options.parent_container) { + container = document.querySelector( + '#' + signal_data.options.parent_container + + ' .il-viewcontrol-sortation' + ); + } else { + container = event.target.closest('.il-viewcontrol-sortation'); + } + let inputs = container.querySelectorAll('.il-viewcontrol-value > input'); let val = signal_data.options.value.split(':'); inputs[0].value = val[0]; inputs[1].value = val[1]; diff --git a/src/UI/Implementation/Component/Table/Data.php b/src/UI/Implementation/Component/Table/Data.php index b96b88591570..cf5d5e2916e5 100644 --- a/src/UI/Implementation/Component/Table/Data.php +++ b/src/UI/Implementation/Component/Table/Data.php @@ -23,7 +23,6 @@ use ILIAS\UI\Component\Table as T; use ILIAS\UI\Component\Table\Column\Column; use ILIAS\UI\Component\Table\Action\Action; -use ILIAS\UI\Component\Input\ViewControl\ViewControl; use Psr\Http\Message\ServerRequestInterface; use ILIAS\UI\Implementation\Component\SignalGeneratorInterface; use ILIAS\UI\Component\Signal; @@ -32,11 +31,18 @@ use ILIAS\Data\Factory as DataFactory; use ILIAS\Data\Order; use ILIAS\Data\Range; +use ILIAS\UI\Component\Input\ViewControl; +use ILIAS\UI\Component\Input\Container\ViewControl as ViewControlContainer; class Data extends Table implements T\Data, JSBindable { use JavaScriptBindable; + public const VIEWCONTROL_KEY_PAGINATION = 'range'; + public const VIEWCONTROL_KEY_ORDERING = 'order'; + public const VIEWCONTROL_KEY_FIELDSELECTION = 'selected_optional'; + + /** * @var array */ @@ -66,8 +72,8 @@ class Data extends Table implements T\Data, JSBindable * @var string[] */ protected array $selected_optional_column_ids = []; - protected Range $range; - protected Order $order; + protected ?Range $range = null; + protected ?Order $order = null; protected ?array $filter = null; protected ?array $additional_parameters = null; @@ -76,6 +82,8 @@ class Data extends Table implements T\Data, JSBindable */ public function __construct( SignalGeneratorInterface $signal_generator, + protected ViewControl\Factory $view_control_factory, + protected ViewControlContainer\Factory $view_control_container_factory, protected DataFactory $data_factory, protected DataRowBuilder $data_row_builder, string $title, @@ -94,8 +102,6 @@ public function __construct( $this->columns = $this->enumerateColumns($columns); $this->selected_optional_column_ids = $this->filterVisibleColumnIds($columns); - $this->order = $this->data_factory->order($this->initialOrder(), Order::ASC); - $this->range = $data_factory->range(0, $this->number_of_rows); } /** @@ -182,6 +188,7 @@ public function withRequest(ServerRequestInterface $request): self $clone->request = $request; return $clone; } + public function getRequest(): ?ServerRequestInterface { return $this->request; @@ -193,31 +200,34 @@ public function withNumberOfRows(int $number_of_rows): self $clone->number_of_rows = $number_of_rows; return $clone; } + public function getNumberOfRows(): int { return $this->number_of_rows; } - public function withOrder(Order $order): self + public function withOrder(?Order $order): self { $clone = clone $this; $clone->order = $order; return $clone; } + public function getOrder(): Order { - return $this->order; + return $this->order ?? $this->data_factory->order($this->initialOrder(), Order::ASC); } - public function withRange(Range $range): self + public function withRange(?Range $range): self { $clone = clone $this; $clone->range = $range; return $clone; } + public function getRange(): Range { - return $this->range; + return $this->range ?? $this->data_factory->range(0, $this->number_of_rows); } public function withFilter(?array $filter): self @@ -226,6 +236,7 @@ public function withFilter(?array $filter): self $clone->filter = $filter; return $clone; } + public function getFilter(): ?array { return $this->filter; @@ -237,20 +248,12 @@ public function withAdditionalParameters(?array $additional_parameters): self $clone->additional_parameters = $additional_parameters; return $clone; } + public function getAdditionalParameters(): ?array { return $this->additional_parameters; } - /** - * @return ViewControl[] - */ - public function getViewControls(): array - { - //NYI - return []; - } - public function getMultiActionSignal(): Signal { return $this->multi_action_signal; @@ -342,4 +345,89 @@ public function getRowBuilder(): DataRowBuilder ->withSingleActions($this->getSingleActions()) ->withVisibleColumns($this->getVisibleColumns()); } + + /** + * @return array + */ + public function applyViewControls( + array $filter_data, + array $additional_parameters + ): array { + $table = $this; + $total_count = $this->getDataRetrieval()->getTotalRowCount($filter_data, $additional_parameters); + $view_controls = $this->getViewControls($total_count); + + if ($request = $this->getRequest()) { + $view_controls = $view_controls->withRequest($request); + $data = $view_controls->getData(); + $table = $table + ->withRange(($data[self::VIEWCONTROL_KEY_PAGINATION] ?? null)?->croppedTo($total_count ?? PHP_INT_MAX)) + ->withOrder($data[self::VIEWCONTROL_KEY_ORDERING] ?? null) + ->withSelectedOptionalColumns($data[self::VIEWCONTROL_KEY_FIELDSELECTION] ?? []); + } + + return [ + $table + ->withFilter($filter_data) + ->withAdditionalParameters($additional_parameters), + $view_controls + ]; + } + + protected function getViewControls(?int $total_count = null): ViewControlContainer\ViewControl + { + $view_controls = [ + self::VIEWCONTROL_KEY_PAGINATION => $this->getViewControlPagination($total_count), + self::VIEWCONTROL_KEY_ORDERING => $this->getViewControlOrdering(), + self::VIEWCONTROL_KEY_FIELDSELECTION => $this->getViewControlFieldSelection(), + ]; + $view_controls = array_filter($view_controls); + return $this->view_control_container_factory->standard($view_controls); + } + + protected function getViewControlPagination(?int $total_count = null): ?ViewControl\Pagination + { + $smallest_option = current(\ILIAS\UI\Implementation\Component\Input\ViewControl\Pagination::DEFAULT_LIMITS); + if (is_null($total_count) || $total_count >= $smallest_option) { + return $this->view_control_factory->pagination()->withTotalCount($total_count); + } + return null; + } + + protected function getViewControlOrdering(): ?ViewControl\Sortation + { + $sortable_visible_cols = array_filter( + $this->getVisibleColumns(), + static fn($c): bool => $c->isSortable() + ); + $sort_options = []; + foreach ($sortable_visible_cols as $id => $col) { + $sort_options[$col->getTitle() . ', ' . 'ascending'] = $this->data_factory->order($id, 'ASC'); + $sort_options[$col->getTitle() . ', ' . 'decending'] = $this->data_factory->order($id, 'DESC'); + } + + if ($sort_options !== []) { + return $this->view_control_factory->sortation($sort_options); + } + return null; + } + + protected function getViewControlFieldSelection(): ?ViewControl\FieldSelection + { + $optional_cols = array_filter( + $this->getColumns(), + static fn($c): bool => $c->isOptional() + ); + if ($optional_cols === []) { + return null; + } + + $aspect_options = []; + foreach ($optional_cols as $id => $col) { + $aspect_options[$id] = $col->getTitle(); + } + + return $this->view_control_factory + ->fieldSelection($aspect_options); + } } diff --git a/src/UI/Implementation/Component/Table/Factory.php b/src/UI/Implementation/Component/Table/Factory.php index 29c88d7dcb15..9d5bc3a543bf 100644 --- a/src/UI/Implementation/Component/Table/Factory.php +++ b/src/UI/Implementation/Component/Table/Factory.php @@ -22,6 +22,8 @@ use ILIAS\UI\Component\Table as T; use ILIAS\UI\Implementation\Component\SignalGeneratorInterface; +use ILIAS\UI\Component\Input\ViewControl\Factory as ViewControlFactory; +use ILIAS\UI\Component\Input\Container\ViewControl\Factory as ViewControlContainerFactory; use ILIAS\Data\Factory as DataFactory; use Closure; @@ -32,6 +34,8 @@ class Factory implements T\Factory { public function __construct( protected SignalGeneratorInterface $signal_generator, + protected ViewControlFactory $view_control_factory, + protected ViewControlContainerFactory $view_control_container_factory, protected DataFactory $data_factory, protected T\Column\Factory $column_factory, protected T\Action\Factory $action_factory, @@ -57,6 +61,8 @@ public function data( ): T\Data { return new Data( $this->signal_generator, + $this->view_control_factory, + $this->view_control_container_factory, $this->data_factory, $this->data_row_builder, $title, diff --git a/src/UI/Implementation/Component/Table/Renderer.php b/src/UI/Implementation/Component/Table/Renderer.php index 810037e3bb34..b41f7896924a 100644 --- a/src/UI/Implementation/Component/Table/Renderer.php +++ b/src/UI/Implementation/Component/Table/Renderer.php @@ -28,17 +28,10 @@ use ILIAS\Data\Order; use ILIAS\Data\URI; use ILIAS\UI\Implementation\Component\Table\Action\Action; +use ILIAS\UI\Implementation\Component\Input\ViewControl\Pagination; class Renderer extends AbstractComponentRenderer { - /** - * The parameters for ordering will be removed as soon as ViewControls - * are available for the Table. DO NOT USE THEM. - * This is purely to demonstrate column-ordering already. - */ - private const TABLE_DEMO_ORDER_FIELD = 'tsort_f'; - private const TABLE_DEMO_ORDER_DIRECTION = 'tsort_d'; - /** * @inheritdoc */ @@ -201,8 +194,6 @@ public function renderDataTable(Component\Table\Data $component, RendererInterfa { $tpl = $this->getTemplate("tpl.datatable.html", true, true); - $component = $this->applyViewControls($component); - $opt_action_id = Action::OPT_ACTIONID; $opt_row_id = Action::OPT_ROWID; $component = $component @@ -232,6 +223,16 @@ public function renderDataTable(Component\Table\Data $component, RendererInterfa ); } + //TODO: Filter + $filter_data = []; + $additional_parameters = []; + [$component, $view_controls] = $component->applyViewControls( + $filter_data = [], + $additional_parameters = [] + ); + + $tpl->setVariable('VIEW_CONTROLS', $default_renderer->render($view_controls)); + $rows = $component->getDataRetrieval()->getRows( $component->getRowBuilder(), array_keys($component->getVisibleColumns()), @@ -241,71 +242,48 @@ public function renderDataTable(Component\Table\Data $component, RendererInterfa $component->getAdditionalParameters() ); - $id = $this->bindJavaScript($component); $tpl->setVariable('ID', $id); $tpl->setVariable('TITLE', $component->getTitle()); $tpl->setVariable('COL_COUNT', (string) $component->getColumnCount()); - $tpl->setVariable('VIEW_CONTROLS', $default_renderer->render($component->getViewControls())); - - $this->renderTableHeader($default_renderer, $component, $tpl); + $sortation_signal = null; // if the generator is empty, and thus invalid, we render an empty row. - if ($rows->valid()) { - $this->appendTableRows($tpl, $rows, $default_renderer); - } else { + if (!$rows->valid()) { $this->renderFullWidthDataCell($component, $tpl, $this->txt('ui_table_no_records')); - } + } else { + $this->appendTableRows($tpl, $rows, $default_renderer); - if ($component->hasMultiActions()) { - $multi_actions = $component->getMultiActions(); - $modal = $this->buildMultiActionsAllObjectsModal($multi_actions, $id); - $multi_actions_dropdown = $this->buildMultiActionsDropdown( - $multi_actions, - $component->getMultiActionSignal(), - $modal->getShowSignal() + if ($component->hasMultiActions()) { + $multi_actions = $component->getMultiActions(); + $modal = $this->buildMultiActionsAllObjectsModal($multi_actions, $id); + $multi_actions_dropdown = $this->buildMultiActionsDropdown( + $multi_actions, + $component->getMultiActionSignal(), + $modal->getShowSignal() + ); + $tpl->setVariable('MULTI_ACTION_TRIGGERER', $default_renderer->render($multi_actions_dropdown)); + $tpl->setVariable('MULTI_ACTION_ALL_MODAL', $default_renderer->render($modal)); + } + + //TODO: move this? + $sortation_view_control = array_filter( + $view_controls->getInputs(), + static fn($i): bool => $i instanceof Component\Input\ViewControl\Sortation ); - $tpl->setVariable('MULTI_ACTION_TRIGGERER', $default_renderer->render($multi_actions_dropdown)); - $tpl->setVariable('MULTI_ACTION_ALL_MODAL', $default_renderer->render($modal)); + $sortation_signal = array_shift($sortation_view_control)->getInternalSignal(); + $sortation_signal->addOption('parent_container', $id); } + $this->renderTableHeader($default_renderer, $component, $tpl, $sortation_signal); return $tpl->get(); } - - protected function applyViewControls(Component\Table\Data $component): Component\Table\Data - { - //TODO: Viewcontrols, Filter - $df = $this->getDataFactory(); - $range = $component->getRange(); - $order = $component->getOrder(); - $selected_optional = $component->getSelectedOptionalColumns(); - $filter_data = null; - $additional_parameters = null; - - if ($request = $component->getRequest()) { - $params = []; - parse_str($request->getUri()->getQuery(), $params); - if (array_key_exists(self::TABLE_DEMO_ORDER_FIELD, $params) && array_key_exists(self::TABLE_DEMO_ORDER_DIRECTION, $params) - && array_key_exists($params[self::TABLE_DEMO_ORDER_FIELD], $component->getVisibleColumns()) - ) { - $order = $df->order($params[self::TABLE_DEMO_ORDER_FIELD], $params[self::TABLE_DEMO_ORDER_DIRECTION]); - } - } - //END TODO: Viewcontrols, Filter - - return $component - ->withSelectedOptionalColumns($selected_optional) - ->withRange($range) - ->withOrder($order) - ->withFilter($filter_data) - ->withAdditionalParameters($additional_parameters); - } - protected function renderTableHeader( RendererInterface $default_renderer, Component\Table\Data $component, - Template $tpl + Template $tpl, + ?Component\Signal $sortation_signal ): void { $order = $component->getOrder(); $glyph_factory = $this->getUIFactory()->symbol()->glyph(); @@ -313,8 +291,6 @@ protected function renderTableHeader( $sort_direction = current($order->get()); $columns = $component->getVisibleColumns(); - $request = $component->getRequest(); - foreach ($columns as $col_id => $col) { $param_sort_direction = Order::ASC; $col_title = $col->getTitle(); @@ -333,17 +309,15 @@ protected function renderTableHeader( $tpl->setCurrentBlock('header_cell'); $tpl->setVariable('COL_INDEX', (string) $col->getIndex()); - if ($col->isSortable() && ! is_null($request)) { - $uri = (string)$this->getDataFactory()->uri($request->getUri()->__toString()) - ->withParameter(self::TABLE_DEMO_ORDER_FIELD, $col_id) - ->withParameter(self::TABLE_DEMO_ORDER_DIRECTION, $param_sort_direction); - + if ($col->isSortable() && ! is_null($sortation_signal)) { + $sort_signal = clone $sortation_signal; + $sort_signal->addOption('value', "$col_id:$param_sort_direction"); $col_title = $default_renderer->render( - $this->getUIFactory()->button()->shy($col_title, $uri) + $this->getUIFactory()->button()->shy($col_title, $sort_signal) ); if ($col_id === $sort_col) { - $sortation_glyph = $default_renderer->render($sortation_glyph->withAction($uri)); + $sortation_glyph = $default_renderer->render($sortation_glyph->withOnClick($sort_signal)); $tpl->setVariable('COL_SORTATION', $sortation); $tpl->setVariable('COL_SORTATION_GLYPH', $sortation_glyph); } diff --git a/src/UI/ROADMAP.md b/src/UI/ROADMAP.md index d22e6a97c4ae..434fa6d46c95 100644 --- a/src/UI/ROADMAP.md +++ b/src/UI/ROADMAP.md @@ -11,7 +11,7 @@ are explained in [Usage](#usage). ## Short Term -#### Outer Content (advanced, variable) +### Outer Content (advanced, variable) This Component is basically what could be hooked into the [Standard Layout](Component/Layout/Page/Factory.php) as content (currently provided as array of Legacy Components). Most Probably it should be able to hold the title section (not yet part of the UI Components, see below), the Tabs (not yet Part of the UI Components, see below) and the @@ -19,7 +19,7 @@ Inner Content holding the workspace for the current context (not yet Part of the Note; One important aspect here, will be to clarify at some point the relation to the [Global Screen](../GlobalScreen). -#### Title Section (advanced, variable) +### Title Section (advanced, variable) This Component will probably hold the Icon, title, description and the actions (maybe along with the used glyphs) of the current context. Note that a major part of the work for this components will be to setup a comprehensive set of rules on when to provide an Icon, restrictions of the Title (lengths, nouns vs verbs etc.), restrictions of the description @@ -29,7 +29,7 @@ However, this has not been decided yet and is thus most certainly up for discuss Note; One important aspect here, will be to clarify at some point the relation to the [Global Screen](../GlobalScreen). -#### Tabs and Sub Tabs (advanced, variable) +### Tabs and Sub Tabs (advanced, variable) Note that a major part of the work for this Components will be to setup a comprehensive set of rules on the naming of Tabs and Sub Tabs (noun vs verbs, length, amount of words etc.) and rules for the usage of Tabs vs Sub Tabs vs Sections in Forms shown in Tabs. Also, one would have to look into the issue that currently "<-- Back" actions are mixed into @@ -37,13 +37,13 @@ the Tabs. We will need to decide, whether we will still use this concept in the Note; One important aspect here, will be to clarify at some point the relation to the [Global Screen](../GlobalScreen). -#### Inner Content +### Inner Content This will most probably mainly contain an array of Components used in the Content Section. An interesting point here will be the question, whether this Component should also offer something like withToolbar, to make sure only one or no Toolbar can be provided and whether there would be different types of Inner Content Components (such as one with a Sidebar). -#### Toolbar +### Toolbar The Toolbar is currently in discussion (link to paper) and it is very plausibel that we find altnerntive places for all elements currently in it. Therefore we are not sure, if there will ever be something like the toolbar in the UI Components. @@ -219,6 +219,13 @@ In addition, the `UploadHandler` interface contains methods which are not requir and can be safely removed (along with their implementations): `getExistingFileInfoURL()`, `getInfoForExistingFiles()`, and `getInfoResult()`. +### Move JS of Input/Container/ViewControls to proper modules [beginner, 8h] + +Currently the JS-code of Input/Container/ViewControls is located in the according +renderer and untested. The code should be moved out of the renderer and be located +in properly structured JS-modules. And of course it should be tested by automated +tests as well. + ### Add Information of anticipated datatypes to Table/Column When applying records to a row, the cells (Columns) expect an input of a certain type; however, this cannot be (or currently is not) PHP-typehinted. diff --git a/src/UI/examples/Table/Action/Multi/base.php b/src/UI/examples/Table/Action/Multi/base.php index c5b9f667b21f..09243d18a716 100644 --- a/src/UI/examples/Table/Action/Multi/base.php +++ b/src/UI/examples/Table/Action/Multi/base.php @@ -42,7 +42,8 @@ function base() ]; $table = getExampleTable($f) - ->withActions($actions); + ->withActions($actions) + ->withRequest($DIC->http()->request()); //render table and results diff --git a/src/UI/examples/Table/Action/Single/base.php b/src/UI/examples/Table/Action/Single/base.php index 2d453f14f260..77af23f6dc36 100644 --- a/src/UI/examples/Table/Action/Single/base.php +++ b/src/UI/examples/Table/Action/Single/base.php @@ -42,7 +42,8 @@ function base() ]; $table = getExampleTable($f) - ->withActions($actions); + ->withActions($actions) + ->withRequest($DIC->http()->request()); //render table and results diff --git a/src/UI/examples/Table/Action/Standard/base.php b/src/UI/examples/Table/Action/Standard/base.php index e485f01000a6..e5fd3c3e3108 100644 --- a/src/UI/examples/Table/Action/Standard/base.php +++ b/src/UI/examples/Table/Action/Standard/base.php @@ -46,7 +46,8 @@ function base() ]; $table = getExampleTable($f) - ->withActions($actions); + ->withActions($actions) + ->withRequest($DIC->http()->request()); //render table and results diff --git a/src/UI/examples/Table/Column/Boolean/base.php b/src/UI/examples/Table/Column/Boolean/base.php index 24a141e7feaa..f61b0fb2433a 100644 --- a/src/UI/examples/Table/Column/Boolean/base.php +++ b/src/UI/examples/Table/Column/Boolean/base.php @@ -61,6 +61,7 @@ public function getTotalRowCount( } }; - $table = $f->table()->data('Boolean Columns', $columns, $data_retrieval); + $table = $f->table()->data('Boolean Columns', $columns, $data_retrieval) + ->withRequest($DIC->http()->request()); return $r->render($table); } diff --git a/src/UI/examples/Table/Column/Date/base.php b/src/UI/examples/Table/Column/Date/base.php index f9ac4fbbb3b0..e9701237f22b 100644 --- a/src/UI/examples/Table/Column/Date/base.php +++ b/src/UI/examples/Table/Column/Date/base.php @@ -47,6 +47,7 @@ public function getTotalRowCount( } }; - $table = $f->table()->data('Date Columns', $columns, $data_retrieval); + $table = $f->table()->data('Date Columns', $columns, $data_retrieval) + ->withRequest($DIC->http()->request()); return $r->render($table); } diff --git a/src/UI/examples/Table/Column/EMail/base.php b/src/UI/examples/Table/Column/EMail/base.php index a2366e68f360..dde8bcd73db5 100644 --- a/src/UI/examples/Table/Column/EMail/base.php +++ b/src/UI/examples/Table/Column/EMail/base.php @@ -47,6 +47,7 @@ public function getTotalRowCount( } }; - $table = $f->table()->data('eMail Columns', $columns, $data_retrieval); + $table = $f->table()->data('eMail Columns', $columns, $data_retrieval) + ->withRequest($DIC->http()->request()); return $r->render($table); } diff --git a/src/UI/examples/Table/Column/Number/base.php b/src/UI/examples/Table/Column/Number/base.php index 6ca9aa25ce79..ca691d122d34 100644 --- a/src/UI/examples/Table/Column/Number/base.php +++ b/src/UI/examples/Table/Column/Number/base.php @@ -61,6 +61,7 @@ public function getTotalRowCount( } }; - $table = $f->table()->data('Number Columns', $columns, $data_retrieval); + $table = $f->table()->data('Number Columns', $columns, $data_retrieval) + ->withRequest($DIC->http()->request()); return $r->render($table); } diff --git a/src/UI/examples/Table/Column/Status/base.php b/src/UI/examples/Table/Column/Status/base.php index 816d5de1e0e8..ce65303c8225 100644 --- a/src/UI/examples/Table/Column/Status/base.php +++ b/src/UI/examples/Table/Column/Status/base.php @@ -50,6 +50,7 @@ public function getTotalRowCount( } }; - $table = $f->table()->data('Status Columns', $columns, $data_retrieval); + $table = $f->table()->data('Status Columns', $columns, $data_retrieval) + ->withRequest($DIC->http()->request()); return $r->render($table); } diff --git a/src/UI/examples/Table/Column/StatusIcon/base.php b/src/UI/examples/Table/Column/StatusIcon/base.php index 9947a7795b6f..32bf44bb41f2 100644 --- a/src/UI/examples/Table/Column/StatusIcon/base.php +++ b/src/UI/examples/Table/Column/StatusIcon/base.php @@ -58,6 +58,7 @@ public function getTotalRowCount( } }; - $table = $f->table()->data('StatusIcons Columns', $columns, $data_retrieval); + $table = $f->table()->data('StatusIcons Columns', $columns, $data_retrieval) + ->withRequest($DIC->http()->request()); return $r->render($table); } diff --git a/src/UI/examples/Table/Column/Text/base.php b/src/UI/examples/Table/Column/Text/base.php index 26df82dd29d7..24c6b84a9c4b 100644 --- a/src/UI/examples/Table/Column/Text/base.php +++ b/src/UI/examples/Table/Column/Text/base.php @@ -54,6 +54,7 @@ public function getTotalRowCount( } }; - $table = $f->table()->data('Text Columns', $columns, $data_retrieval); + $table = $f->table()->data('Text Columns', $columns, $data_retrieval) + ->withRequest($DIC->http()->request()); return $r->render($table); } diff --git a/src/UI/examples/Table/Column/TimeSpan/base.php b/src/UI/examples/Table/Column/TimeSpan/base.php index 98ed3e2b13e0..d8047f20744a 100644 --- a/src/UI/examples/Table/Column/TimeSpan/base.php +++ b/src/UI/examples/Table/Column/TimeSpan/base.php @@ -48,6 +48,7 @@ public function getTotalRowCount( } }; - $table = $f->table()->data('TimeSpan Columns', $columns, $data_retrieval); + $table = $f->table()->data('TimeSpan Columns', $columns, $data_retrieval) + ->withRequest($DIC->http()->request()); return $r->render($table); } diff --git a/src/UI/examples/Table/Data/base.php b/src/UI/examples/Table/Data/base.php index cdef1b8d7027..3cce6c0896a9 100644 --- a/src/UI/examples/Table/Data/base.php +++ b/src/UI/examples/Table/Data/base.php @@ -42,7 +42,7 @@ function base() 'fee' => $f->table()->column()->number("Fee") ->withDecimals(2) ->withUnit('£', I\Column\Number::UNIT_POSITION_FORE), - 'hidden' => $f->table()->column()->status("success") + 'failure_txt' => $f->table()->column()->status("failure") ->withIsSortable(false) ->withIsOptional(true) ->withIsInitiallyVisible(false), @@ -144,10 +144,11 @@ public function getRows( ?array $filter_data, ?array $additional_parameters ): \Generator { - $records = $this->getRecords($order); + $records = $this->getRecords($range, $order); foreach ($records as $idx => $record) { $row_id = (string)$record['usr_id']; $record['achieve_txt'] = $record['achieve'] > 80 ? 'passed' : 'failed'; + $record['failure_txt'] = "not " . $record["achieve_txt"]; $record['repeat'] = $record['achieve'] < 80; $record['achieve'] = $this->ui_renderer->render( $this->ui_factory->chart()->progressMeter()->mini(80, $record['achieve']) @@ -163,10 +164,10 @@ public function getTotalRowCount( ?array $filter_data, ?array $additional_parameters ): ?int { - return null; + return count($this->getRecords()); } - protected function getRecords(Order $order): array + protected function getRecords(Range $range = null, Order $order = null): array { $records = [ ['usr_id' => 123,'login' => 'superuser','email' => 'user@example.com', @@ -179,14 +180,27 @@ protected function getRecords(Order $order): array 'last' => new \DateTimeImmutable(),'achieve' => 66,'fee' => 36.789 ], ['usr_id' => 8748,'login' => 'student3_longname','email' => 'student3_long_email@example.com', - 'last' => new \DateTimeImmutable(),'achieve' => 66,'fee' => 36.789 + 'last' => new \DateTimeImmutable(),'achieve' => 8,'fee' => 36.789 + ], + ['usr_id' => 8749,'login' => 'studentAB','email' => 'studentAB@example.com', + 'last' => new \DateTimeImmutable(),'achieve' => 100,'fee' => 114 + ], + ['usr_id' => 8750,'login' => 'student5','email' => 'student5@example.com', + 'last' => new \DateTimeImmutable(),'achieve' => 76,'fee' => 3.789 + ], + ['usr_id' => 8751,'login' => 'student6','email' => 'student6@example.com', + 'last' => new \DateTimeImmutable(),'achieve' => 66,'fee' => 67 ] ]; - - list($order_field, $order_direction) = $order->join([], fn($ret, $key, $value) => [$key, $value]); - usort($records, fn($a, $b) => $a[$order_field] <=> $b[$order_field]); - if ($order_direction === 'DESC') { - $records = array_reverse($records); + if ($order) { + list($order_field, $order_direction) = $order->join([], fn($ret, $key, $value) => [$key, $value]); + usort($records, fn($a, $b) => $a[$order_field] <=> $b[$order_field]); + if ($order_direction === 'DESC') { + $records = array_reverse($records); + } + } + if ($range) { + $records = array_slice($records, max($range->getStart() - 1, 0), $range->getLength()); } return $records; diff --git a/src/UI/examples/Table/Data/repo_implementation.php b/src/UI/examples/Table/Data/repo_implementation.php index e5fc5d88d916..2699f69fc5ce 100644 --- a/src/UI/examples/Table/Data/repo_implementation.php +++ b/src/UI/examples/Table/Data/repo_implementation.php @@ -18,6 +18,9 @@ function repo_implementation() * * Here is an example, in which the DataRetrieval extends the repository in * which the UI-code becomes _very_ small for the actual representation. + * + * Please note that the pagination is missing due to an amount of records + * smaller than the lowest option "number of rows". */ global $DIC; @@ -64,6 +67,9 @@ public function getRows( ): \Generator { foreach ($this->doSelect($order, $range) as $idx => $record) { $row_id = (string)$record['usr_id']; + $record['achieve_txt'] = $record['achieve'] > 80 ? 'passed' : 'failed'; + $record['failure_txt'] = "not " . $record["achieve_txt"]; + $record['repeat'] = $record['achieve'] < 80; yield $row_builder->buildDataRow($row_id, $record); } } @@ -78,10 +84,10 @@ public function getTotalRowCount( //do the actual reading - note, that e.g. order and range are easily converted to SQL protected function doSelect(Order $order, Range $range): array { - $sql_order_part = $order->join('ORDER BY', fn (...$o) => implode(' ', $o)); + $sql_order_part = $order->join('ORDER BY', fn(...$o) => implode(' ', $o)); $sql_range_part = sprintf('LIMIT %2$s OFFSET %1$s', ...$range->unpack()); return array_map( - fn ($rec) => array_merge($rec, ['sql_order' => $sql_order_part, 'sql_range' => $sql_range_part]), + fn($rec) => array_merge($rec, ['sql_order' => $sql_order_part, 'sql_range' => $sql_range_part]), $this->dummyrecords() ); } @@ -107,7 +113,7 @@ protected function getColumsForRepresentation(): array 'fee' => $f->table()->column()->number("Fee") ->withDecimals(2) ->withUnit('£', I\Column\Number::UNIT_POSITION_FORE), - 'hidden' => $f->table()->column()->status("success") + 'failure_txt' => $f->table()->column()->status("failure") ->withIsSortable(false) ->withIsOptional(true) ->withIsInitiallyVisible(false), diff --git a/src/UI/examples/Table/Data/without_data.php b/src/UI/examples/Table/Data/without_data.php index 1c8d1c0282e2..47fbd4fb370f 100644 --- a/src/UI/examples/Table/Data/without_data.php +++ b/src/UI/examples/Table/Data/without_data.php @@ -20,6 +20,7 @@ function without_data(): string $factory = $DIC->ui()->factory(); $renderer = $DIC->ui()->renderer(); + $request = $DIC->http()->request(); $empty_retrieval = new class () implements DataRetrieval { public function getRows( @@ -35,18 +36,20 @@ public function getRows( public function getTotalRowCount(?array $filter_data, ?array $additional_parameters): ?int { - return null; + return 0; } }; $table = $factory->table()->data( 'Empty Data Table', [ - 'col1' => $factory->table()->column()->text('Column 1'), - 'col2' => $factory->table()->column()->number('Column 2'), + 'col1' => $factory->table()->column()->text('Column 1') + ->withIsSortable(false), + 'col2' => $factory->table()->column()->number('Column 2') + ->withIsSortable(false), ], $empty_retrieval ); - return $renderer->render($table); + return $renderer->render($table->withRequest($request)); } diff --git a/tests/UI/Component/Table/DataRendererTest.php b/tests/UI/Component/Table/DataRendererTest.php index 514cb92216ac..de06e7b47a5b 100644 --- a/tests/UI/Component/Table/DataRendererTest.php +++ b/tests/UI/Component/Table/DataRendererTest.php @@ -19,7 +19,7 @@ declare(strict_types=1); require_once("libs/composer/vendor/autoload.php"); -require_once(__DIR__ . "/../../Base.php"); +require_once(__DIR__ . "/TableTestBase.php"); use ILIAS\UI\Component; use ILIAS\UI\Implementation\Component as I; @@ -60,9 +60,9 @@ public function p_renderTableHeader( TestDefaultRenderer $default_renderer, I\Table\Data $component, $tpl, - \ILIAS\Data\Order $order + I\Signal $sortation_signal ) { - return $this->renderTableHeader($default_renderer, $component, $tpl, $order); + return $this->renderTableHeader($default_renderer, $component, $tpl, $sortation_signal); } } @@ -70,7 +70,7 @@ public function p_renderTableHeader( /** * Tests for the Renderer of DataTables. */ -class DataRendererTest extends ILIAS_UI_TestBase +class DataRendererTest extends TableTestBase { private function getRenderer() { @@ -97,6 +97,24 @@ private function getColumnFactory() return new I\Table\Column\Factory(); } + private function getDummyRequest() + { + $request = $this->createMock(ServerRequestInterface::class); + $request + ->method("getUri") + ->willReturn(new class () { + public function __toString() + { + return 'http://localhost:80'; + } + }); + + $request + ->method("getQueryParams") + ->willReturn([]); + return $request; + } + public function getDataFactory(): Data\Factory { return new Data\Factory(); @@ -104,16 +122,20 @@ public function getDataFactory(): Data\Factory public function getUIFactory(): NoUIFactory { - $factory = new class () extends NoUIFactory { - public function button(): \ILIAS\UI\Component\Button\Factory + $factory = new class ($this->getTableFactory()) extends NoUIFactory { + public function __construct( + protected Component\Table\Factory $table_factory + ) { + } + public function button(): Component\Button\Factory { return new I\Button\Factory(); } - public function dropdown(): ILIAS\UI\Component\Dropdown\Factory + public function dropdown(): Component\Dropdown\Factory { return new I\Dropdown\Factory(); } - public function symbol(): ILIAS\UI\Component\Symbol\Factory + public function symbol(): Component\Symbol\Factory { return new I\Symbol\Factory( new I\Symbol\Icon\Factory(), @@ -121,17 +143,11 @@ public function symbol(): ILIAS\UI\Component\Symbol\Factory new I\Symbol\Avatar\Factory() ); } - public function table(): ILIAS\UI\Component\Table\Factory + public function table(): Component\Table\Factory { - return new I\Table\Factory( - new I\SignalGenerator(), - new \ILIAS\Data\Factory(), - new I\Table\Column\Factory(), - new I\Table\Action\Factory(), - new I\Table\DataRowBuilder() - ); + return $this->table_factory; } - public function divider(): ILIAS\UI\Component\Divider\Factory + public function divider(): Component\Divider\Factory { return new I\Divider\Factory(); } @@ -240,19 +256,11 @@ public function getTotalRowCount( 'f2' => $f->text("Field 2")->withIndex(2), 'f3' => $f->number("Field 3")->withIndex(3) ]; - $request = $this->createMock(ServerRequestInterface::class); - $request->method("getUri") - ->willReturn(new class () { - public function __toString() - { - return 'http://localhost:80'; - } - }); - - $order = $data_factory->order('f1', \ILIAS\Data\Order::ASC); + $sortation_signal = new I\Signal('sort_header_signal_id'); + $sortation_signal->addOption('value', 'f1:ASC'); $table = $this->getUIFactory()->table()->data('', $columns, $data) - ->withRequest($request); - $renderer->p_renderTableHeader($this->getDefaultRenderer(), $table, $tpl, $order); + ->withRequest($this->getDummyRequest()); + $renderer->p_renderTableHeader($this->getDefaultRenderer(), $table, $tpl, $sortation_signal); $actual = $this->brutallyTrimHTML($tpl->get()); $expected = <<
- - + +
- +
- +
@@ -407,7 +415,7 @@ public function getRows( public function getTotalRowCount(?array $filter_data, ?array $additional_parameters): ?int { - return null; + return 0; } }; @@ -419,7 +427,8 @@ public function getTotalRowCount(?array $filter_data, ?array $additional_paramet 'f5' => $this->getUIFactory()->table()->column()->text('f5'), ]; - $table = $this->getUIFactory()->table()->data('', $columns, $data); + $table = $this->getTableFactory()->data('', $columns, $data) + ->withRequest($this->getDummyRequest()); $html = $this->getDefaultRenderer()->render($table); diff --git a/tests/UI/Component/Table/DataTest.php b/tests/UI/Component/Table/DataTest.php index ebe478cdd10b..eafa44dfdc1a 100644 --- a/tests/UI/Component/Table/DataTest.php +++ b/tests/UI/Component/Table/DataTest.php @@ -19,7 +19,7 @@ declare(strict_types=1); require_once("libs/composer/vendor/autoload.php"); -require_once(__DIR__ . "/../../Base.php"); +require_once(__DIR__ . "/TableTestBase.php"); use ILIAS\UI\Component; use ILIAS\UI\Implementation\Component as C; @@ -32,19 +32,8 @@ /** * Tests for the Data Table. */ -class DataTest extends ILIAS_UI_TestBase +class DataTest extends TableTestBase { - protected function getFactory() - { - return new C\Table\Factory( - new C\SignalGenerator(), - new \ILIAS\Data\Factory(), - new C\Table\Column\Factory(), - new C\Table\Action\Factory(), - new C\Table\DataRowBuilder() - ); - } - protected function getDataRetrieval(): I\Table\DataRetrieval { return new class () implements I\Table\DataRetrieval { @@ -70,8 +59,8 @@ public function getTotalRowCount( public function testDataTableBasicConstruction(): void { $data = $this->getDataRetrieval(); - $cols = ['f0' => $this->getFactory()->column()->text("col1")]; - $table = $this->getFactory()->data('title', $cols, $data); + $cols = ['f0' => $this->getTableFactory()->column()->text("col1")]; + $table = $this->getTableFactory()->data('title', $cols, $data); $this->assertEquals(800, $table->getNumberOfRows()); $this->assertInstanceOf(Order::class, $table->getOrder()); $this->assertInstanceOf(Range::class, $table->getRange()); @@ -88,7 +77,7 @@ public function testDataTableConstructionWithErrorColumns(): void $this->expectException(\InvalidArgumentException::class); $data = $this->getDataRetrieval(); $cols = ['f0' => "col1"]; - $table = $this->getFactory()->data('title', $cols, $data); + $table = $this->getTableFactory()->data('title', $cols, $data); } public function testDataTableConstructionWithoutColumns(): void @@ -96,17 +85,17 @@ public function testDataTableConstructionWithoutColumns(): void $this->expectException(\InvalidArgumentException::class); $data = $this->getDataRetrieval(); $cols = []; - $table = $this->getFactory()->data('title', $cols, $data); + $table = $this->getTableFactory()->data('title', $cols, $data); } public function testDataTableColumns(): void { - $f = $this->getFactory()->column(); + $f = $this->getTableFactory()->column(); $cols = [ 'f0' => $f->text("col1"), 'f1' => $f->text("col2") ]; - $table = $this->getFactory()->data('title', $cols, $this->getDataRetrieval()); + $table = $this->getTableFactory()->data('title', $cols, $this->getDataRetrieval()); $this->assertEquals(2, $table->getColumnCount()); $check = [ @@ -119,7 +108,7 @@ public function testDataTableColumns(): void public function testDataTableActions(): void { - $f = $this->getFactory()->action(); + $f = $this->getTableFactory()->action(); $df = new \ILIAS\Data\Factory(); $target = $df->uri('http://wwww.ilias.de?ref_id=1'); $url_builder = new URLBuilder($target); @@ -129,8 +118,8 @@ public function testDataTableActions(): void $f->multi('act2', $builder, $token), $f->standard('act0', $builder, $token) ]; - $cols = ['f0' => $this->getFactory()->column()->text("col1")]; - $table = $this->getFactory()->data('title', $cols, $this->getDataRetrieval()) + $cols = ['f0' => $this->getTableFactory()->column()->text("col1")]; + $table = $this->getTableFactory()->data('title', $cols, $this->getDataRetrieval()) ->withActions($actions); $this->assertEquals($actions, $table->getAllActions()); @@ -141,8 +130,8 @@ public function testDataTableActions(): void protected function getTable(): I\Table\Data { $data = $this->getDataRetrieval(); - $cols = ['f0' => $this->getFactory()->column()->text("col1")]; - $table = $this->getFactory()->data('title', $cols, $data); + $cols = ['f0' => $this->getTableFactory()->column()->text("col1")]; + $table = $this->getTableFactory()->data('title', $cols, $data); return $table; } @@ -196,13 +185,13 @@ public function testDataTableWithSelectedOptionalCols(): void { $data = $this->getDataRetrieval(); $cols = [ - 'f0' => $this->getFactory()->column()->text(''), - 'f1' => $this->getFactory()->column()->text('') + 'f0' => $this->getTableFactory()->column()->text(''), + 'f1' => $this->getTableFactory()->column()->text('') ->withIsOptional(true) ->withIsInitiallyVisible(false), - 'f2' => $this->getFactory()->column()->text('') + 'f2' => $this->getTableFactory()->column()->text('') ]; - $table = $this->getFactory()->data('title', $cols, $data); + $table = $this->getTableFactory()->data('title', $cols, $data); $this->assertEquals(3, $table->getColumnCount()); $this->assertEquals(['f0', 'f2'], array_keys($table->getVisibleColumns())); $this->assertEquals(0, $table->getVisibleColumns()['f0']->getIndex()); diff --git a/tests/UI/Component/Table/DataViewControlsTest.php b/tests/UI/Component/Table/DataViewControlsTest.php new file mode 100644 index 000000000000..fadabd2d2220 --- /dev/null +++ b/tests/UI/Component/Table/DataViewControlsTest.php @@ -0,0 +1,153 @@ +buildStandardRow('', []); + } + public function getTotalRowCount( + ?array $filter_data, + ?array $additional_parameters + ): ?int { + return $this->total_count; + } + }; + } + + protected function getTable(int $total_count, array $columns): array + { + $factory = $this->getTableFactory(); + $table = $factory->data('Table', $columns, $this->getDataRetrieval($total_count)); + return $table->applyViewControls([], []); + } + + public function testDataTableHasViewControls(): void + { + $factory = $this->getTableFactory(); + $columns = [ + 'f1' => $factory->column()->text('f1'), + 'f2' => $factory->column()->text('f2')->withIsOptional(true), + ]; + $total_count = 12; + list($table, $view_controls) = $this->getTable($total_count, $columns); + + $this->assertInstanceOf(I\Input\Container\ViewControl\ViewControl::class, $view_controls); + $this->assertEquals( + [ + C\Table\Data::VIEWCONTROL_KEY_PAGINATION, + C\Table\Data::VIEWCONTROL_KEY_ORDERING, + C\Table\Data::VIEWCONTROL_KEY_FIELDSELECTION, + ], + array_keys($view_controls->getInputs()) + ); + foreach (array_values($view_controls->getInputs()) as $vc) { + $this->assertInstanceOf(I\Input\Container\ViewControl\ViewControlInput::class, $vc); + } + } + + public function testDataTableHasNoPaginationViewControl(): void + { + $factory = $this->getTableFactory(); + $columns = [ + 'f1' => $factory->column()->text('f1'), + 'f2' => $factory->column()->text('f2')->withIsOptional(true), + ]; + $total_count = current(C\Input\ViewControl\Pagination::DEFAULT_LIMITS) - 1; + list($table, $view_controls) = $this->getTable($total_count, $columns); + + $this->assertEquals( + [ + C\Table\Data::VIEWCONTROL_KEY_ORDERING, + C\Table\Data::VIEWCONTROL_KEY_FIELDSELECTION, + ], + array_keys($view_controls->getInputs()) + ); + } + + public function testDataTableHasNoOrderingViewControl(): void + { + $factory = $this->getTableFactory(); + $columns = [ + 'f1' => $factory->column()->text('f1') + ->withIsSortable(false), + 'f2' => $factory->column()->text('f2') + ->withIsSortable(false) + ->withIsOptional(true), + ]; + $total_count = 200; + list($table, $view_controls) = $this->getTable($total_count, $columns); + + $this->assertEquals( + [ + C\Table\Data::VIEWCONTROL_KEY_PAGINATION, + C\Table\Data::VIEWCONTROL_KEY_FIELDSELECTION, + ], + array_keys($view_controls->getInputs()) + ); + } + + public function testDataTableHasNoFieldSelectionViewControl(): void + { + $factory = $this->getTableFactory(); + $columns = [ + 'f1' => $factory->column()->text('f1'), + 'f2' => $factory->column()->text('f2'), + ]; + $total_count = 200; + list($table, $view_controls) = $this->getTable($total_count, $columns); + + $this->assertEquals( + [ + C\Table\Data::VIEWCONTROL_KEY_PAGINATION, + C\Table\Data::VIEWCONTROL_KEY_ORDERING, + ], + array_keys($view_controls->getInputs()) + ); + } +} diff --git a/tests/UI/Component/Table/PresentationTest.php b/tests/UI/Component/Table/PresentationTest.php index 0d6d82debaa2..cde148a1ffad 100644 --- a/tests/UI/Component/Table/PresentationTest.php +++ b/tests/UI/Component/Table/PresentationTest.php @@ -19,7 +19,7 @@ declare(strict_types=1); require_once("libs/composer/vendor/autoload.php"); -require_once(__DIR__ . "/../../Base.php"); +require_once(__DIR__ . "/TableTestBase.php"); use ILIAS\UI\Implementation as I; use ILIAS\UI\Component as C; @@ -28,22 +28,11 @@ /** * Tests for Presentation Table. */ -class PresentationTest extends ILIAS_UI_TestBase +class PresentationTest extends TableTestBase { - private function getFactory(): I\Component\Table\Factory - { - return new I\Component\Table\Factory( - new I\Component\SignalGenerator(), - new \ILIAS\Data\Factory(), - new I\Component\Table\Column\Factory(), - new I\Component\Table\Action\Factory(), - new I\Component\Table\DataRowBuilder() - ); - } - public function testTableConstruction(): void { - $f = $this->getFactory(); + $f = $this->getTableFactory(); $this->assertInstanceOf("ILIAS\\UI\\Component\\Table\\Factory", $f); $pt = $f->presentation('title', [], function (): void { @@ -63,7 +52,7 @@ public function testTableConstruction(): void public function testRowConstruction(): void { - $f = $this->getFactory(); + $f = $this->getTableFactory(); $pt = $f->presentation('title', [], function (): void { }); $row = new PresentationRow($pt->getSignalGenerator(), 'table_id'); @@ -223,7 +212,7 @@ public function testFullRendering(): void EXP; $r = $this->getDefaultRenderer(); - $f = $this->getFactory(); + $f = $this->getTableFactory(); $pt = $f->presentation('title', [], $mapping); $actual = $r->render($pt->withData($this->getDummyData())); $this->assertEquals( @@ -299,7 +288,7 @@ public function testMinimalRendering(): void EXP; $r = $this->getDefaultRenderer(); - $f = $this->getFactory(); + $f = $this->getTableFactory(); $pt = $f->presentation('title', [], $mapping); $actual = $r->render($pt->withData($this->getDummyData())); $this->assertEquals( @@ -312,7 +301,7 @@ public function testRenderEmptyTableEntry(): void { $mapping = fn(PresentationRow $row, mixed $record, \ILIAS\UI\Factory $ui_factory, mixed $environment) => $row; - $table = $this->getFactory()->presentation('', [], $mapping); + $table = $this->getTableFactory()->presentation('', [], $mapping); $html = $this->getDefaultRenderer()->render($table); diff --git a/tests/UI/Component/Table/TableTestBase.php b/tests/UI/Component/Table/TableTestBase.php new file mode 100644 index 000000000000..023781bcf033 --- /dev/null +++ b/tests/UI/Component/Table/TableTestBase.php @@ -0,0 +1,86 @@ +createMock(UploadLimitResolver::class), + new C\SignalGenerator(), + new \ILIAS\Data\Factory(), + $this->buildRefinery(), + $this->getLanguage() + ); + } + + protected function buildRefinery(): Refinery + { + return new Refinery( + new \ILIAS\Data\Factory(), + $this->createMock(ilLanguage::class) + ); + } + + protected function getViewControlFactory(): ViewControl\Factory + { + return new ViewControl\Factory( + $this->buildFieldFactory(), + new \ILIAS\Data\Factory(), + $this->buildRefinery(), + new C\SignalGenerator(), + $this->getLanguage(), + ); + } + + protected function getViewControlContainerFactory(): ViewControlContainer\Factory + { + return new ViewControlContainer\Factory( + new C\SignalGenerator(), + $this->getViewControlFactory(), + ); + } + + protected function getTableFactory(): C\Table\Factory + { + return new C\Table\Factory( + new C\SignalGenerator(), + $this->getViewControlFactory(), + $this->getViewControlContainerFactory(), + new \ILIAS\Data\Factory(), + new C\Table\Column\Factory(), + new C\Table\Action\Factory(), + new C\Table\DataRowBuilder() + ); + } +}