Skip to content

Commit

Permalink
Fix #218: Add ability to set page size (#219)
Browse files Browse the repository at this point in the history
* Fix #218: Add ability to set page size

* Apply fixes from StyleCI

* Implement constraint check

* Apply fixes from StyleCI

* Add tests, fix constraint

* Apply fixes from StyleCI

* Add draft page selector

* Apply fixes from StyleCI

* Apply Rector changes (CI)

* Fix types

* process page size URL parameter

* ui

* Apply fixes from StyleCI

* improve

* improve

* rename

* improve

---------

Co-authored-by: StyleCI Bot <[email protected]>
Co-authored-by: samdark <[email protected]>
Co-authored-by: Sergei Predvoditelev <[email protected]>
  • Loading branch information
4 people authored Nov 22, 2024
1 parent 3394978 commit c11a1d2
Show file tree
Hide file tree
Showing 11 changed files with 516 additions and 8 deletions.
21 changes: 21 additions & 0 deletions config/widgets-themes.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,25 @@
use Yiisoft\Yii\DataView\GridView;
use Yiisoft\Yii\DataView\KeysetPagination;
use Yiisoft\Yii\DataView\OffsetPagination;
use Yiisoft\Yii\DataView\PageSize\InputPageSize;
use Yiisoft\Yii\DataView\PageSize\SelectPageSize;

return [
'bootstrap5' => [
GridView::class => [
'layout()' => [
<<<HTML
{header}
{toolbar}
{items}
{summary}
<div class="row">
<div class="col-md-8">{pager}</div>
<div class="col-md-4 text-end">{pageSize}</div>
</div>
HTML
],
'pageSizeTag()' => [null],
'summaryTag()' => ['p'],
'summaryAttributes()' => [['class' => 'text-secondary']],
'tableClass()' => ['table table-bordered'],
Expand Down Expand Up @@ -54,5 +69,11 @@
'currentItemClass()' => ['active'],
'disabledItemClass()' => ['disabled'],
],
InputPageSize::class => [
'attributes()' => [['class' => 'form-control d-inline text-center mx-2', 'style' => 'width:60px']],
],
SelectPageSize::class => [
'attributes()' => [['class' => 'form-select w-auto d-inline mx-2']],
],
],
];
264 changes: 257 additions & 7 deletions src/BaseListView.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,25 @@
use Yiisoft\Widget\Widget;
use Yiisoft\Data\Reader\OrderHelper;
use Yiisoft\Yii\DataView\Exception\DataReaderNotSetException;
use Yiisoft\Yii\DataView\PageSize\InputPageSize;
use Yiisoft\Yii\DataView\PageSize\PageSizeContext;
use Yiisoft\Yii\DataView\PageSize\PageSizeControlInterface;
use Yiisoft\Yii\DataView\PageSize\SelectPageSize;

use function array_key_exists;
use function array_slice;
use function call_user_func_array;
use function extension_loaded;
use function in_array;
use function is_array;
use function is_int;
use function is_string;

/**
* @psalm-type UrlArguments = array<string,scalar|Stringable|null>
* @psalm-type UrlCreator = callable(UrlArguments,array):string
* @psalm-type PageNotFoundExceptionCallback = callable(PageNotFoundException):void
* @psalm-type PageSizeConstraint = list<positive-int>|positive-int|bool
* @psalm-import-type TOrder from Sort
*/
abstract class BaseListView extends Widget
Expand All @@ -53,8 +62,34 @@ abstract class BaseListView extends Widget
protected $urlCreator = null;
protected UrlConfig $urlConfig;

/**
* @var int Page size that is used in case it is not set explicitly.
* @psalm-var positive-int
*/
protected int $defaultPageSize = PaginatorInterface::DEFAULT_PAGE_SIZE;

/**
* @var array|bool|int Page size constraint.
* - `true` - default only.
* - `false` - no constraint.
* - int - maximum page size.
* - [int, int, ...] - a list of page sizes to choose from.
*
* @see PageSizeContext::FIXED_VALUE
* @see PageSizeContext::ANY_VALUE
*
* @psalm-var PageSizeConstraint
*/
protected bool|int|array $pageSizeConstraint = true;

/**
* @psalm-var non-empty-string|null
*/
private ?string $pageSizeTag = 'div';
private array $pageSizeAttributes = [];
private ?string $pageSizeTemplate = 'Results per page {control}';
private PageSizeControlInterface|null $pageSizeControl = null;

/**
* A name for {@see CategorySource} used with translator ({@see TranslatorInterface}) by default.
* @psalm-suppress MissingClassConstType
Expand Down Expand Up @@ -84,7 +119,7 @@ abstract class BaseListView extends Widget
private array $emptyTextAttributes = [];
private string $header = '';
private array $headerAttributes = [];
private string $layout = "{header}\n{toolbar}\n{items}\n{summary}\n{pager}";
private string $layout = "{header}\n{toolbar}\n{items}\n{summary}\n{pager}\n{pageSize}";

private array $offsetPaginationConfig = [];
private array $keysetPaginationConfig = [];
Expand Down Expand Up @@ -372,9 +407,9 @@ private function prepareDataReaderByParams(
}

if ($dataReader->isPaginationRequired()) {

Check warning on line 409 in src/BaseListView.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.2-ubuntu-latest

Escaped Mutant for Mutator "IfNegation": --- Original +++ New @@ @@ return $dataReader; } } - if ($dataReader->isPaginationRequired()) { + if (!$dataReader->isPaginationRequired()) { $dataReader = $dataReader->withPageSize($this->preparePageSize($pageSize) ?? $this->getDefaultPageSize()); if ($page !== null) { $dataReader = $dataReader->withToken(PageToken::next($page));
if ($pageSize !== null) {
$dataReader = $dataReader->withPageSize((int) $pageSize);
}
$dataReader = $dataReader->withPageSize(
$this->preparePageSize($pageSize) ?? $this->getDefaultPageSize()
);

if ($page !== null) {
$dataReader = $dataReader->withToken(PageToken::next($page));
Expand Down Expand Up @@ -468,6 +503,53 @@ public function layout(string $value): static
return $new;
}

final public function pageSizeTag(?string $tag): static
{
if ($tag === '') {
throw new InvalidArgumentException('Tag name cannot be empty.');
}

$new = clone $this;
$new->pageSizeTag = $tag;
return $new;
}

/**
* Returns a new instance with the HTML attributes for page size wrapper tag.
*
* @param array $values Attribute values indexed by attribute names.
*/
final public function pageSizeAttributes(array $values): static
{
$new = clone $this;
$new->pageSizeAttributes = $values;
return $new;
}

/**
* Returns a new instance with the page size template.
*
* @param string|null $template The HTML content to be displayed as the page size control. If you don't want to show
* control, you may set it with an empty string or null.
*
* The following tokens will be replaced with the corresponding values:
*
* - `{control}` — page size control.
*/
final public function pageSizeTemplate(?string $template): static
{
$new = clone $this;
$new->pageSizeTemplate = $template;
return $new;
}

final public function pageSizeControl(?PageSizeControlInterface $widget): static
{
$new = clone $this;
$new->pageSizeControl = $widget;
return $new;
}

public function pagination(string|KeysetPagination|OffsetPagination|null $pagination): static
{
$new = clone $this;
Expand Down Expand Up @@ -627,6 +709,7 @@ public function render(): string
'{items}' => $this->renderItems($items, $filterValidationResult),
'{summary}' => $this->renderSummary(),
'{pager}' => $this->renderPagination(),
'{pageSize}' => $this->renderPageSize(),
],
)
);
Expand All @@ -638,14 +721,48 @@ public function render(): string
->render();
}

/**
* @psalm-return positive-int
*/
protected function getDefaultPageSize(): int
{
$dataReader = $this->getDataReader();
if ($dataReader instanceof PaginatorInterface) {
return $dataReader->getPageSize();
$pageSize = $dataReader instanceof PaginatorInterface
? $dataReader->getPageSize()
: $this->defaultPageSize;

if (is_int($this->pageSizeConstraint)) {
return $pageSize <= $this->pageSizeConstraint
? $pageSize
: $this->pageSizeConstraint;
}

if (is_array($this->pageSizeConstraint)) {
return in_array($pageSize, $this->pageSizeConstraint, true)
? $pageSize
: $this->pageSizeConstraint[0];
}

return $this->defaultPageSize;
return $pageSize;
}

/**
* Get a new instance with a page size constraint set.
*
* @param array|bool|int $pageSizeConstraint Page size constraint.
* `true` - default only.
* `false` - no constraint.
* int - maximum page size.
* [int, int, ...] - a list of page sizes to choose from.
* @return static New instance.
*
* @psalm-param PageSizeConstraint $pageSizeConstraint
*/
public function pageSizeConstraint(array|int|bool $pageSizeConstraint): static
{
$new = clone $this;
$new->pageSizeConstraint = $pageSizeConstraint;
return $new;
}

/**
Expand Down Expand Up @@ -705,6 +822,69 @@ private function renderPagination(): string
->render();
}

private function renderPageSize(): string
{
if (empty($this->pageSizeTemplate)) {
return '';
}

$dataReader = $this->preparedDataReader;
if (!$dataReader instanceof PaginatorInterface) {
return '';
}

if ($this->pageSizeControl === null) {
if ($this->pageSizeConstraint === false || is_int($this->pageSizeConstraint)) {
$widget = InputPageSize::widget();
} elseif (is_array($this->pageSizeConstraint)) {
$widget = SelectPageSize::widget();
} else {
return '';
}
} else {
$widget = $this->pageSizeControl;
}

if ($this->urlCreator === null) {
$urlPattern = '#pagesize=' . PageSizeContext::URL_PLACEHOLDER;
$defaultUrl = '#';
} else {
$sort = $this->getSortValueForUrl($dataReader);
$urlPattern = call_user_func_array(
$this->urlCreator,
UrlParametersFactory::create(
null,
PageSizeContext::URL_PLACEHOLDER,
$sort,
$this->urlConfig
),
);
$defaultUrl = call_user_func_array(
$this->urlCreator,
UrlParametersFactory::create(null, null, $sort, $this->urlConfig),
);
}

$context = new PageSizeContext(
$dataReader->getPageSize(),
$this->getDefaultPageSize(),
$this->pageSizeConstraint,
$urlPattern,
$defaultUrl,
);
$control = $widget->withContext($context)->render();

$content = $this->translator->translate(
$this->pageSizeTemplate,
['control' => $control],
$this->translationCategory,
);

return $this->pageSizeTag === null
? $content
: Html::tag($this->pageSizeTag, $content, $this->pageSizeAttributes)->encode(false)->render();
}

private function renderSummary(): string
{
if (empty($this->summaryTemplate)) {
Expand Down Expand Up @@ -767,6 +947,39 @@ private function renderHeader(): string
};
}

/**
* @psalm-return positive-int|null
*/
private function preparePageSize(?string $rawPageSize): ?int
{
if ($this->pageSizeConstraint === true) {
return null;
}

if ($rawPageSize === null) {
return null;
}

$pageSize = (int) $rawPageSize;
if ($pageSize < 1) {
return null;
}

if ($this->pageSizeConstraint === false) {
return $pageSize;
}

if (is_int($this->pageSizeConstraint) && $pageSize <= $this->pageSizeConstraint) {
return $pageSize;
}

if (is_array($this->pageSizeConstraint) && in_array($pageSize, $this->pageSizeConstraint, true)) {
return $pageSize;
}

return null;
}

/**
* Creates default translator to use if {@see $translator} wasn't set explicitly in the constructor. Depending on
* "intl" extension availability, either {@see IntlMessageFormatter} or {@see SimpleMessageFormatter} is used as
Expand All @@ -786,4 +999,41 @@ private function createDefaultTranslator(): Translator

return $translator;
}

private function getSortValueForUrl(PaginatorInterface $paginator): ?string
{
$sort = $this->getSort($paginator);
if ($sort === null) {
return null;
}

$originalSort = $this->getSort($this->dataReader);
if ($originalSort?->getOrderAsString() === $sort->getOrderAsString()) {
return null;
}

$order = [];
$overrideOrderFields = array_flip($this->getOverrideOrderFields());
foreach ($sort->getOrder() as $name => $value) {
$key = array_key_exists($name, $overrideOrderFields)
? $overrideOrderFields[$name]
: $name;
$order[$key] = $value;
}

return OrderHelper::arrayToString($order);
}

private function getSort(?ReadableDataInterface $dataReader): ?Sort
{
if ($dataReader instanceof PaginatorInterface && $dataReader->isSortable()) {
return $dataReader->getSort();
}

if ($dataReader instanceof SortableDataInterface) {
return $dataReader->getSort();
}

return null;
}
}
3 changes: 3 additions & 0 deletions src/BasePagination.php
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,9 @@ public function urlConfig(UrlConfig $config): static
/**
* Creates the URL suitable for pagination with the specified page number. This method is mainly called by pagers
* when creating URLs used to perform pagination.
*
* @param PageToken $pageToken Token for the page.
* @return string Created URL.
*/
protected function createUrl(PageToken $pageToken): string
{
Expand Down
Loading

0 comments on commit c11a1d2

Please sign in to comment.