List Views
List views are a generic solution for the creation of listings that are ubiquitous in the software.
In contrast to grid views, list views do not specify a particular layout.
The developer must specify a custom template that takes care of the rendering of the entries.
A list view takes 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 list views.
3. An easy way for developers to extend existing list views using plugins.
Usage
AbstractListView
List views obtain their data from a database object list and display it using custom template.
Example:
<?php
namespace wcf\system\listView\user;
use wcf\data\DatabaseObjectList;
use wcf\system\listView\AbstractListView;
use wcf\system\WCF;
/**
* @extends AbstractListView<Example, ExampleList>
*/
class ExampleListView extends AbstractListView
{
#[\Override]
protected function createObjectList(): DatabaseObjectList
{
return new ExampleList();
}
#[\Override]
public function isAccessible(): bool
{
return true;
}
#[\Override]
public function renderItems(): string
{
return WCF::getTPL()->render('wcf', 'exampleListItems', ['view' => $this]);
}
}Example exampleListItems.tpl:
{foreach from=$view->getItems() item='item'}
<div class="listView__item" data-object-id="{$item->getObjectID()}">
<h2>{$item->getTitle()}</h2>
</div>
{/foreach}AbstractListViewPage
A list view can be displayed on a page by inheriting from AbstractListViewPage.
Example:
<?php
namespace wcf\page;
/**
* @extends AbstractListViewPage<ExampleListView>
*/
class ExampleListPage extends AbstractListViewPage
{
#[\Override]
protected function createListView(): ExampleListView
{
return new ExampleListView();
}
}{include file='header'}
<div class="section">
{unsafe:$listView->render()}
</div>
{include file='footer'}Sorting
The addAvailableSortFields method allows you to define columns that the user can use to sort the list.
The columns must exist in the linked database object list.
class ExampleListView extends AbstractListView
{
public function __construct() {
$this->addAvailableSortFields([
new ListViewSortField('time', 'wcf.global.date'),
new ListViewSortField('title', 'wcf.global.title'),
]);
}
}By default, sorting is based on the id (first parameter) of the specified sort field.
Optionally, you can specify the name of an alternative database column to be used for sorting instead:
new ListViewSortField('title', 'wcf.global.title', 'table_alias.columnName'),The default sorting can be defined after the configuration of the sort fields has been defined:
class ExampleListView extends AbstractListView
{
public function __construct()
{
$this->addAvailableSortFields([
new ListViewSortField('time', 'wcf.global.date'),
new ListViewSortField('title', 'wcf.global.title'),
]);
$this->setDefaultSortField('title');
$this->setDefaultSortOrder('ASC');
}
}Filtering
Filters can be defined for columns so that the user has the option to filter by the content of a column.
class ExampleListView extends AbstractListView
{
public function __construct()
{
$this->addAvailableFilters([
new TextFilter('title', 'wcf.global.title'),
]);
}
}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.
class ExampleListView extends AbstractListView
{
public function __construct()
{
$this->addAvailableFilters([
new CategoryFilter((new CategoryNodeTree('identifier'))->getIterator()), 'categoryID'),
]);
}
}DateFilter
DateFilter is a filter for columns that contain unix timestamps.
FloatFilter
FloatFilter is a filter for columns that contain float values.
FormOptionFilter
FormOptionFilter is a filter for columns that are based on IFormOption.
I18nTextFilter
I18nTextFilter is a filter for text columns that are using i18n phrases.
IntegerFilter
IntegerFilter is a filter for columns that contain integer values.
IpAddressFilter
IpAddressFilter is a filter for columns that contain IPv6 addresses, allowing the user to enter addresses in the IPv4 format too.
LabelFilter
LabelFilter allows to filter a list view by labels.
class ExampleListView extends AbstractListView
{
public function __construct()
{
$objectTypeID = ObjectTypeCache::getInstance()->getObjectTypeIDByName(
'com.woltlab.wcf.label.object',
'example.identifier'
);
foreach (ExampleCategory::getAccessibleLabelGroups('canViewLabel') as $groupID => $categoryIDs) {
$this->addAvailableFilters([
new LabelFilter(
LabelHandler::getInstance()->getLabelGroup($groupID),
$objectTypeID,
'labelIDs' . $groupID
)
]);
}
}
}MultipleSelectFilter
MultipleSelectFilter allows a column to be filtered on the basis of a multi-select.
class ExampleListView extends AbstractListView
{
public function __construct()
{
$this->addAvailableFilters([
new MultipleSelectFilter([
1 => 'value 1',
0 => 'value 0',
], 'id', 'language.item'),
]);
}
}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.
class ExampleListView extends AbstractListView
{
public function __construct()
{
$this->addAvailableFilters([
new SelectFilter([
1 => 'value 1',
0 => 'value 0',
], 'id', 'language.item'),
]);
}
}TextFilter
TextFilter is a filter for text columns.
TimeFilter
TimeFilter is a filter for columns that contain unix timestamps.
In contrast to DateFilter, this filter also allows filtering by a specific time.
UserFilter
UserFilter is a filter for columns that contain user ids.
Customization
Number of Items
By default, list views use a pagination that shows 20 items per page. You can set a custom number of items per page:
class ExampleListView extends AbstractListView
{
public function __construct()
{
$this->setItemsPerPage(50);
}
}There are some cases where only a list with a fixed number of items is required, for example, showcasing the 10 latests items.
class ExampleListView extends AbstractListView
{
public function __construct()
{
$this->fixedNumberOfItems(10);
}
}CSS Class Names
Optionally, a CSS class can be set on the surrounding HTML element:
class ExampleListView extends AbstractListView
{
public function __construct()
{
$this->setCssClassName('exampleList');
}
}Additional Parameters
A list view can be provided with additional parameters, e.g. to filter them by a specific category:
class ExampleListView extends AbstractListView
{
public function __construct(public readonly int $categoryID)
{
parent::__construct();
}
#[\Override]
protected function createObjectList(): DatabaseObjectList
{
$list = new ExampleList();
$list->getConditionBuilder()->add('categoryID = ?', [$this->categoryID]);
return $list;
}
#[\Override]
public function getParameters(): array
{
return ['categoryID' => $this->categoryID];
}
}class ExampleListPage extends AbstractListViewPage
{
public int $categoryID = 0;
#[\Override]
public function readParameters()
{
parent::readParameters();
if (isset($_REQUEST['categoryID'])) {
$this->categoryID = \intval($_REQUEST['categoryID']);
}
}
#[\Override]
protected function createListView(): AbstractListView
{
return new ExampleListView($this->categoryID);
}
#[\Override]
protected function getBaseUrlParameters(): array
{
return [
'categoryID' => $this->categoryID,
];
}
}Events
Existing list views can be modified using events.
Example of adding an additional sort field:
$eventHandler->register(
\wcf\event\listView\user\ArticleListViewInitialized::class,
static function (\wcf\event\listView\user\ArticleListViewInitialized $event) {
$event->listView->addAvailableSortField(
new ListViewSortField('example', 'wcf.global.example'),
);
}
);Interactions
Interaction providers can be specified using the methods setInteractionProvider() and setBulkInteractionProvider() (for bulk interactions).
Example:
final class ExampleGridView extends AbstractListView
{
public function __construct()
{
...
$this->setInteractionProvider(new ExampleInteractions());
$this->setBulkInteractionProvider(new ExampleBulkInteractions());
}
}The following template code must be included in the template for rendering of the items so that the buttons for the interactions are displayed.
{if $view->hasBulkInteractions()}
<label class="listView__selectItem__label jsTooltip" title="{lang}wcf.clipboard.item.mark{/lang}">
<input type="checkbox" class="listView__selectItem" aria-label="{lang}wcf.clipboard.item.mark{/lang}">
</label>
{/if}
{unsafe:$view->renderInteractionContextMenuButton($article)}Mark as Read
If your objects can be marked as read, list views provide an abstract implementation for this.
The setMarkAsReadEndpoints(string $endpoint): void method allows you to configure the corresponding RPC endpoint.
final class ExampleGridView extends AbstractListView
{
public function __construct()
{
...
$this->setMarkAsReadEndpoints('core/example/%s/mark-as-read');
}
}In the template code, the corresponding button can be created for each object using the renderMarkAsReadButton(DatabaseObject $object): string method.
{foreach from=$view->getItems() item='item'}
...
{if $item->isNew()}
{unsafe:$view->renderMarkAsReadButton($item)}
{/if}
...
{/foreach}