From 626c608fd0cc25578912ef288f259e1c9f8741c0 Mon Sep 17 00:00:00 2001 From: Marcel Werk Date: Tue, 3 Jun 2025 16:39:54 +0200 Subject: [PATCH 1/2] Document grid views Closes #496 --- docs/php/api/grid_views.md | 350 +++++++++++++++++++++++++++++++++++++ mkdocs.yml | 1 + 2 files changed, 351 insertions(+) create mode 100644 docs/php/api/grid_views.md diff --git a/docs/php/api/grid_views.md b/docs/php/api/grid_views.md new file mode 100644 index 000000000..2d7ae72f4 --- /dev/null +++ b/docs/php/api/grid_views.md @@ -0,0 +1,350 @@ +# Grid Views + +Grid views are a generic solution for the creation of listings, as they occur again and again in the software. +In addition to rendering, the grid view also take care of sorting, filtering and pagination and ensure that a lot of boilerplating becomes obsolete. + +The implementation essentially offers the following advantages: +1. A uniform appearance and usability for the user. +2. An easy way for developers to create their own grid views. +3. An easy way for developers to extend existing grid views using plugins. + +## Usage + +### AbstractGridView + +Grid views obtain their data from a database object list and display it using a defined column configuration. + +Example: + +```php +addColumns([ + GridViewColumn::for('id') + ->label('wcf.global.objectID') + ->renderer(new ObjectIdColumnRenderer()) + ->sortable(), + GridViewColumn::for('title') + ->label('wcf.global.title') + ->sortable() + ->titleColumn() + ]); + } + + #[\Override] + public function isAccessible(): bool + { + return true; + } + + #[\Override] + protected function createObjectList(): DatabaseObjectList + { + return new ExampleDatabaseObjectList(); + } +} +``` + +### AbstractGridViewPage + +A grid view can be displayed on a page by inheriting from `AbstractGridViewPage`. + +Example: + +```php + + {unsafe:$gridView->render()} + + +{include file='footer'} +``` + +## Columns + +Columns can be created using the `GridViewColumn::for` method. This expects a unique string as a parameter, which is equivalent to the corresponding key in the data source. + +The `label` method can be used to give the column a human readable label. + +Example: + +```php +final class FooGridView extends AbstractGridView +{ + public function __construct() + { + $this->addColumns([ + GridViewColumn::for('id') + ->label('wcf.global.objectID'), + GridViewColumn::for('name') + ->label('wcf.global.name'), + ]); + } +} +``` + +### Renderer + +Renderers can be applied to columns to format the output. A column can have multiple renderers. The renderers are applied in the order in which they were set. + +```php +GridViewColumn::for('foo') + ->renderer([ + new FooColumnRenderer(), + new BarColumnRenderer(), + ]) +``` + +#### CategoryColumnRenderer + +`CategoryColumnRenderer` can be set to columns that contain the ID of categories. This results in the name of the category being output. + +#### CurrencyColumnRenderer + +`CurrencyColumnRenderer` formats the content of a column as a currency. Expects the content of the column to be a decimal. + +Example: + +```php +GridViewColumn::for('foo') + ->renderer(new CurrencyColumnRenderer('EUR')) +``` + +#### DefaultColumnRenderer + +The `DefaultColumnRenderer` is automatically applied to all columns if no other renderers have been set. + +#### EmailColumnRenderer + +`EmailColumnRenderer` formats the content of the column as an email address. + +#### FilesizeColumnRenderer + +`FilesizeColumnRenderer` formats the content of the column as a file size. + +#### IpAddressColumnRenderer + +`IpAddressColumnRenderer` renders ipv6 embedded ipv4 address into ipv4 or returns input if true ipv6. + +#### LinkColumnRenderer + +`LinkColumnRenderer` allows the setting of a link to a column. + +Example: + +```php +GridViewColumn::for('foo') + ->renderer(new LinkColumnRenderer(FooEditForm::class)) +``` + +#### NumberColumnRenderer + +`NumberColumnRenderer` formats the content of a column as a number using `StringUtil::formatNumeric()`. + +#### ObjectIdColumnRenderer + +`ObjectIdColumnRenderer` formats the content of a column as an object id. + +#### PhraseColumnRenderer + +`PhraseColumnRenderer` formats the content of a column as a phrase. + +#### TimeColumnRenderer + +`TimeColumnRenderer` renders a unix timestamp into a human readable format. + +#### TruncatedTextColumnRenderer + +`TruncatedTextColumnRenderer` truncates the content of a column to a length of 80 characters (default value). + +#### UserColumnRenderer + +`UserColumnRenderer` formats the content of a column as a user. + +#### UserLinkColumnRenderer + +`UserLinkColumnRenderer` is a combination of the `UserColumnRenderer` and the `LinkColumnRenderer`. + +Example: + +```php +GridViewColumn::for('foo') + ->renderer(new UserLinkColumnRenderer(FooEditForm::class)) +``` + +### Custom Renderer + +If necessary, you can define your own renderers: + +```php +GridViewColumn::for('id') + ->renderer([ + new class extends DefaultColumnRenderer { + public function render(mixed $value, DatabaseObject $row): string + { + return 'foo: ' . $value; + } + }, + ]), +``` + +### Row Link + +A row link applies a link to every column in the grid. + +```php +final class FooGridView extends AbstractGridView +{ + public function __construct() + { + $this->addRowLink(new GridViewRowLink(FooEditForm::class)); + } +} +``` + +The constructor supports 3 optional parameters: +1. `string $controllerClass`: The controller to which the link should refer. +2. `array $parameters`: Additional parameters for the controller. +3. `string $cssClass`: CSS class for the link. + +### Sorting + +Columns can be marked as sortable so that the user has the option of sorting according to the content of the column. + +```php +GridViewColumn::for('foo') + ->sortable() +``` + +By default, sorting is based on the `id` of the column. +Optionally, you can specify the name of an alternative database column to be used for sorting instead: + +```php +GridViewColumn::for('foo') + ->sortable(sortByDatabaseColumn: "another_table.bar") +``` + +The default sorting can be defined after the column configuration has been defined: + +```php +final class FooGridView extends AbstractGridView +{ + public function __construct() + { + GridViewColumn::for('title') + ->sortable(); + + $this->setSortField('title'); + $this->setSortOrder('ASC'); + } +} +``` + +### Filtering + +Filters can be defined for columns so that the user has the option to filter by the content of a column. + +```php +GridViewColumn::for('foo') + ->filter(new FooFilter()) +``` + +#### BooleanFilter + +`BooleanFilter` is a filter for columns that contain boolean values (`1` or `0`). + +#### CategoryFilter + +`CategoryFilter` is a filter for columns that contain category ids. + +```php +GridViewColumn::for('categoryID') + ->filter(new CategoryFilter((new CategoryNodeTree('identifier'))->getIterator())) +``` + +#### I18nTextFilter + +`I18nTextFilter` is a filter for text columns that are using i18n phrases. + +#### IpAddressFilter + +`IpAddressFilter` is a filter for columns that contain ipv6 addresses, allowing the user to enter addresses in the ipv4 format. + +#### NumericFilter + +`NumericFilter` is a filter for columns that contain numeric values. + +#### ObjectIdFilter + +`ObjectIdFilter` is a filter for columns that contain object ids. + +#### SelectFilter + +`SelectFilter` allows a column to be filtered on the basis of a select dropdown. + +```php +GridViewColumn::for('foo') + ->filter(new SelectFilter([ + 1 => 'value 1', + 0 => 'value 0', + ])); +``` + +#### TextFilter + +`TextFilter` is a filter for text columns. + +#### TimeFilter + +`TimeFilter` is a filter for columns that contain unix timestamps. + +#### UserFilter + +`UserFilter` is a filter for columns that contain user ids. + +### Events + +Existing grid views can be modified using events. + +Example of adding an additional column: + +```php +$eventHandler->register( + \wcf\event\gridView\UserRankGridViewInitialized::class, + static function (\wcf\event\gridView\UserRankGridViewInitialized $event) { + $event->gridView->addColumnBefore(GridViewColumn::for('hideTitle') + ->label('hideTitle') + ->renderer(new NumberColumnRenderer()) + ->sortable(), 'requiredPoints'); + } +); +``` diff --git a/mkdocs.yml b/mkdocs.yml index 9b8deae6e..f77b2c553 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -39,6 +39,7 @@ nav: - 'Fields': 'php/api/form_builder/form_fields.md' - 'Validation and Data': 'php/api/form_builder/validation_data.md' - 'Dependencies': 'php/api/form_builder/dependencies.md' + - 'Grid Views': 'php/api/grid_views.md' - 'Package Installation Plugins': 'php/api/package_installation_plugins.md' - 'RPC API': 'php/api/rpc_api.md' - 'User Activity Events': 'php/api/user_activity_events.md' From f6e37555ba0e4f5d0d7b25f3a6d41cc3ad677e57 Mon Sep 17 00:00:00 2001 From: Marcel Werk Date: Mon, 9 Jun 2025 15:57:46 +0200 Subject: [PATCH 2/2] Apply suggestions from code review Co-authored-by: Alexander Ebert --- docs/php/api/grid_views.md | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/docs/php/api/grid_views.md b/docs/php/api/grid_views.md index 2d7ae72f4..883dc826e 100644 --- a/docs/php/api/grid_views.md +++ b/docs/php/api/grid_views.md @@ -1,7 +1,7 @@ # Grid Views -Grid views are a generic solution for the creation of listings, as they occur again and again in the software. -In addition to rendering, the grid view also take care of sorting, filtering and pagination and ensure that a lot of boilerplating becomes obsolete. +Grid views are a generic solution for the creation of listings that are ubiquitous in the software. +In addition to rendering, the grid view also take care of sorting, filtering and pagination, and ensure that a lot of boilerplating becomes obsolete. The implementation essentially offers the following advantages: 1. A uniform appearance and usability for the user. @@ -141,7 +141,7 @@ GridViewColumn::for('foo') #### DefaultColumnRenderer -The `DefaultColumnRenderer` is automatically applied to all columns if no other renderers have been set. +The `DefaultColumnRenderer` is implicitly applied to all columns unless one ore more renderers have been explicitly set. #### EmailColumnRenderer @@ -153,7 +153,7 @@ The `DefaultColumnRenderer` is automatically applied to all columns if no other #### IpAddressColumnRenderer -`IpAddressColumnRenderer` renders ipv6 embedded ipv4 address into ipv4 or returns input if true ipv6. +`IpAddressColumnRenderer` outputs the value by attempting to interpret it as IPv4 if possible, otherwise shows the IPv6 address. #### LinkColumnRenderer @@ -176,7 +176,7 @@ GridViewColumn::for('foo') #### PhraseColumnRenderer -`PhraseColumnRenderer` formats the content of a column as a phrase. +`PhraseColumnRenderer` attempts to evaluate the value as a phrase and outputs it as plain text otherwise. #### TimeColumnRenderer @@ -250,7 +250,7 @@ Optionally, you can specify the name of an alternative database column to be use ```php GridViewColumn::for('foo') - ->sortable(sortByDatabaseColumn: "another_table.bar") + ->sortable(sortByDatabaseColumn: "table_alias.columnName") ``` The default sorting can be defined after the column configuration has been defined: @@ -297,7 +297,7 @@ GridViewColumn::for('categoryID') #### IpAddressFilter -`IpAddressFilter` is a filter for columns that contain ipv6 addresses, allowing the user to enter addresses in the ipv4 format. +`IpAddressFilter` is a filter for columns that contain IPv6 addresses, allowing the user to enter addresses in the IPv4 format too. #### NumericFilter @@ -341,10 +341,13 @@ Example of adding an additional column: $eventHandler->register( \wcf\event\gridView\UserRankGridViewInitialized::class, static function (\wcf\event\gridView\UserRankGridViewInitialized $event) { - $event->gridView->addColumnBefore(GridViewColumn::for('hideTitle') - ->label('hideTitle') - ->renderer(new NumberColumnRenderer()) - ->sortable(), 'requiredPoints'); + $event->gridView->addColumnBefore( + GridViewColumn::for('hideTitle') + ->label('hideTitle') + ->renderer(new NumberColumnRenderer()) + ->sortable(), + 'requiredPoints' + ); } ); ```