Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

URL-oriented improvements #117

Merged
merged 3 commits into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 0 additions & 23 deletions assets/controllers/persistence.js

This file was deleted.

41 changes: 41 additions & 0 deletions assets/controllers/state.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Controller } from '@hotwired/stimulus'

export default class extends Controller {
static values = {
urlQueryParameters: Object,
}

connect() {
this.#appendUrlQueryParameters();
}

#appendUrlQueryParameters() {
const url = new URL(window.location.href);

const parameters = this.#flattenParameters(this.urlQueryParametersValue);

for (const [key, value] of Object.entries(parameters)) {
if (!url.searchParams.has(key)) {
url.searchParams.set(key, String(value));
}
}

window.history.replaceState(null, null, url);
}

#flattenParameters(input, keyName) {
let result = {};

for (const key in input) {
const newKey = keyName ? `${keyName}[${key}]` : key;

if (typeof input[key] === "object" && !Array.isArray(input[key])) {
result = {...result, ...this.#flattenParameters(input[key], newKey)}
} else {
result[newKey] = input[key];
}
}

return result;
}
}
4 changes: 2 additions & 2 deletions assets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
"fetch": "eager",
"enabled": true
},
"persistence": {
"main": "controllers/persistence.js",
"state": {
"main": "controllers/state.js",
"fetch": "eager",
"enabled": true
}
Expand Down
4 changes: 2 additions & 2 deletions docs/src/docs/features/filtering.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,14 +180,14 @@ class ProductController extends AbstractController

By default, the filters loaded from the persistence are not visible in the URL.

It is recommended to make sure the **persistence** controller is enabled in your `assets/controllers.json`,
It is recommended to make sure the **state** controller is enabled in your `assets/controllers.json`,
which will automatically append the filters to the URL, even if multiple data tables are visible on the same page.

```json
{
"controllers": {
"@kreyu/data-table-bundle": {
"persistence": {
"state": {
"enabled": true
}
}
Expand Down
19 changes: 19 additions & 0 deletions docs/src/docs/features/pagination.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,25 @@ class ProductController extends AbstractController
```
:::

### Adding pagination loaded from persistence to URL

By default, the pagination loaded from the persistence is not visible in the URL.

It is recommended to make sure the **state** controller is enabled in your `assets/controllers.json`,
which will automatically append the pagination parameters to the URL, even if multiple data tables are visible on the same page.

```json
{
"controllers": {
"@kreyu/data-table-bundle": {
"state": {
"enabled": true
}
}
}
}
```

## Default pagination

The default pagination data can be overridden using the data table builder's `setDefaultPaginationData()` method:
Expand Down
19 changes: 19 additions & 0 deletions docs/src/docs/features/sorting.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,25 @@ class ProductController extends AbstractController
```
:::

### Adding sorting loaded from persistence to URL

By default, the sorting loaded from the persistence is not visible in the URL.

It is recommended to make sure the **state** controller is enabled in your `assets/controllers.json`,
which will automatically append the sorting parameters to the URL, even if multiple data tables are visible on the same page.

```json
{
"controllers": {
"@kreyu/data-table-bundle": {
"state": {
"enabled": true
}
}
}
}
```

## Default sorting

The default sorting data can be overridden using the data table builder's `setDefaultSortingData()` method:
Expand Down
2 changes: 1 addition & 1 deletion docs/src/docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ Now, add `@kreyu/data-table-bundle` controllers to your `assets/controllers.json
"personalization": {
"enabled": true
},
"persistence": {
"state": {
"enabled": true
},
"batch": {
Expand Down
12 changes: 11 additions & 1 deletion src/Column/ColumnSortUrlGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Kreyu\Bundle\DataTableBundle\Column;

use Kreyu\Bundle\DataTableBundle\DataTableView;
use Kreyu\Bundle\DataTableBundle\Exception\LogicException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
Expand All @@ -17,7 +18,7 @@ public function __construct(
) {
}

public function generate(ColumnHeaderView ...$columnHeaderViews): string
public function generate(DataTableView $dataTableView, ColumnHeaderView ...$columnHeaderViews): string
{
$request = $this->getRequest();

Expand All @@ -27,10 +28,19 @@ public function generate(ColumnHeaderView ...$columnHeaderViews): string

$parameters = [...$routeParams, ...$queryParams];

// Recursively replace/merge with the URL query parameters defined in the data table view.
// This allows the user to define custom query parameters that should be preserved when sorting columns.
$parameters = array_replace_recursive($parameters, $dataTableView->vars['url_query_parameters'] ?? []);

foreach ($columnHeaderViews as $columnHeaderView) {
$parameters = array_replace_recursive($parameters, $this->getColumnSortQueryParameters($columnHeaderView));
}

// Clearing the filters should reset the pagination to the first page.
if ($dataTableView->vars['pagination_enabled']) {
$parameters[$dataTableView->vars['page_parameter_name']] = 1;
}

return $this->urlGenerator->generate($route, $parameters);
}

Expand Down
4 changes: 3 additions & 1 deletion src/Column/ColumnSortUrlGeneratorInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

namespace Kreyu\Bundle\DataTableBundle\Column;

use Kreyu\Bundle\DataTableBundle\DataTableView;

interface ColumnSortUrlGeneratorInterface
{
public function generate(ColumnHeaderView $columnHeaderView): string;
public function generate(DataTableView $dataTableView, ColumnHeaderView ...$columnHeaderView): string;
}
1 change: 1 addition & 0 deletions src/DependencyInjection/KreyuDataTableExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public function load(array $configs, ContainerBuilder $container): void
$loader->load('actions.php');
$loader->load('exporter.php');
$loader->load('filtration.php');
$loader->load('pagination.php');
$loader->load('personalization.php');
$loader->load('twig.php');

Expand Down
23 changes: 18 additions & 5 deletions src/Filter/FilterClearUrlGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Kreyu\Bundle\DataTableBundle\Filter;

use Kreyu\Bundle\DataTableBundle\DataTableView;
use Kreyu\Bundle\DataTableBundle\Exception\LogicException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
Expand All @@ -17,7 +18,7 @@ public function __construct(
) {
}

public function generate(FilterView ...$filterViews): string
public function generate(DataTableView $dataTableView, FilterView ...$filterViews): string
{
$request = $this->getRequest();

Expand All @@ -27,23 +28,35 @@ public function generate(FilterView ...$filterViews): string

$parameters = [...$routeParams, ...$queryParams];

// Recursively replace/merge with the URL query parameters defined in the data table view.
// This allows the user to define custom query parameters that should be preserved when clearing filters.
$parameters = array_replace_recursive($parameters, $dataTableView->vars['url_query_parameters'] ?? []);

foreach ($filterViews as $filterView) {
$parameters = array_replace_recursive($parameters, $this->getFilterClearQueryParameters($filterView));
}

// Clearing the filters should reset the pagination to the first page.
if ($dataTableView->vars['pagination_enabled']) {
$parameters[$dataTableView->vars['page_parameter_name']] = 1;
}

return $this->urlGenerator->generate($route, $parameters);
}

private function getFilterClearQueryParameters(FilterView $filterView): array
{
$parameters = ['value' => ''];

if ($filterView->vars['operator_selectable']) {
$parameters['operator'] = null;
}

$dataTableView = $filterView->parent;

return [
$dataTableView->vars['filtration_parameter_name'] => [
$filterView->vars['name'] => [
'value' => '',
'operator' => null,
],
$filterView->vars['name'] => $parameters,
],
];
}
Expand Down
4 changes: 3 additions & 1 deletion src/Filter/FilterClearUrlGeneratorInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

namespace Kreyu\Bundle\DataTableBundle\Filter;

use Kreyu\Bundle\DataTableBundle\DataTableView;

interface FilterClearUrlGeneratorInterface
{
public function generate(FilterView ...$filterViews): string;
public function generate(DataTableView $dataTableView, FilterView ...$filterViews): string;
}
48 changes: 48 additions & 0 deletions src/Pagination/PaginationUrlGenerator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

declare(strict_types=1);

namespace Kreyu\Bundle\DataTableBundle\Pagination;

use Kreyu\Bundle\DataTableBundle\DataTableView;
use Kreyu\Bundle\DataTableBundle\Exception\LogicException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

class PaginationUrlGenerator implements PaginationUrlGeneratorInterface
{
public function __construct(
private readonly RequestStack $requestStack,
private readonly UrlGeneratorInterface $urlGenerator,
) {
}

public function generate(DataTableView $dataTableView, int $page): string
{
$request = $this->getRequest();

$route = $request->attributes->get('_route');
$routeParams = $request->attributes->get('_route_params', []);
$queryParams = $request->query->all();

$parameters = [...$routeParams, ...$queryParams];

// Recursively replace/merge with the URL query parameters defined in the data table view.
// This allows the user to define custom query parameters that should be preserved when changing pages.
$parameters = array_replace_recursive($parameters, $dataTableView->vars['url_query_parameters'] ?? []);

$parameters[$dataTableView->vars['page_parameter_name']] = $page;

return $this->urlGenerator->generate($route, $parameters);
}

private function getRequest(): Request
{
if (null === $request = $this->requestStack->getCurrentRequest()) {
throw new LogicException('Unable to retrieve current request.');
}

return $request;
}
}
12 changes: 12 additions & 0 deletions src/Pagination/PaginationUrlGeneratorInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace Kreyu\Bundle\DataTableBundle\Pagination;

use Kreyu\Bundle\DataTableBundle\DataTableView;

interface PaginationUrlGeneratorInterface
{
public function generate(DataTableView $dataTableView, int $page): string;
}
1 change: 1 addition & 0 deletions src/Pagination/PaginationView.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public function __construct(
PaginationInterface $pagination,
) {
$this->vars = [
'data_table' => $this->parent,
'page_parameter_name' => $this->parent->vars['page_parameter_name'],
'current_page_number' => $pagination->getCurrentPageNumber(),
'current_page_item_count' => $pagination->getCurrentPageItemCount(),
Expand Down
23 changes: 23 additions & 0 deletions src/Resources/config/pagination.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

use Kreyu\Bundle\DataTableBundle\Pagination\PaginationUrlGenerator;
use Kreyu\Bundle\DataTableBundle\Pagination\PaginationUrlGeneratorInterface;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

use function Symfony\Component\DependencyInjection\Loader\Configurator\service;

return static function (ContainerConfigurator $configurator) {
$services = $configurator->services();

$services
->set('kreyu_data_table.pagination.url_generator', PaginationUrlGenerator::class)
->args([
service('request_stack'),
service(UrlGeneratorInterface::class),
])
->alias(PaginationUrlGeneratorInterface::class, 'kreyu_data_table.pagination.url_generator')
;
};
1 change: 1 addition & 0 deletions src/Resources/config/twig.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
->args([
service('kreyu_data_table.column.column_sort_url_generator'),
service('kreyu_data_table.filter.filter_clear_url_generator'),
service('kreyu_data_table.pagination.url_generator'),
])
;
};
Loading
Loading