From 916b681b593ef6752321f2e81ba2608f334daeb9 Mon Sep 17 00:00:00 2001 From: Alexandre Castelain Date: Fri, 22 Aug 2025 14:24:09 +0200 Subject: [PATCH 1/5] Add column visibility group management in the datatable --- src/Column/Type/ColumnType.php | 6 ++ .../ColumnVisibilityGroup.php | 52 ++++++++++++ .../ColumnVisibilityGroupBuilder.php | 33 +++++++ .../ColumnVisibilityGroupBuilderInterface.php | 10 +++ .../ColumnVisibilityGroupInterface.php | 12 +++ src/DataTable.php | 14 +++ src/DataTableBuilder.php | 85 +++++++++++++++++++ src/DataTableBuilderInterface.php | 9 ++ src/DataTableConfigBuilder.php | 27 ++++++ src/DataTableConfigBuilderInterface.php | 3 + src/DataTableConfigInterface.php | 3 + src/DataTableInterface.php | 2 + src/DependencyInjection/Configuration.php | 3 + .../KreyuDataTableExtension.php | 1 + src/Request/HttpFoundationRequestHandler.php | 10 +++ src/Resources/config/columns.php | 7 ++ src/Type/DataTableType.php | 4 + 17 files changed, 281 insertions(+) create mode 100644 src/ColumnVisibilityGroup/ColumnVisibilityGroup.php create mode 100644 src/ColumnVisibilityGroup/ColumnVisibilityGroupBuilder.php create mode 100644 src/ColumnVisibilityGroup/ColumnVisibilityGroupBuilderInterface.php create mode 100644 src/ColumnVisibilityGroup/ColumnVisibilityGroupInterface.php diff --git a/src/Column/Type/ColumnType.php b/src/Column/Type/ColumnType.php index 119f8bdc..eaf9fedd 100755 --- a/src/Column/Type/ColumnType.php +++ b/src/Column/Type/ColumnType.php @@ -330,6 +330,12 @@ public function configureOptions(OptionsResolver $resolver): void ->allowedTypes('bool') ->info('Defines whether the column can be personalized by the user in personalization feature.') ; + + $resolver->define('column_visibility_groups') + ->default(null) + ->allowedTypes('null', 'string', 'array') + ->info('Defines the visibility groups of a column.') + ; } public function getBlockPrefix(): string diff --git a/src/ColumnVisibilityGroup/ColumnVisibilityGroup.php b/src/ColumnVisibilityGroup/ColumnVisibilityGroup.php new file mode 100644 index 00000000..de10916a --- /dev/null +++ b/src/ColumnVisibilityGroup/ColumnVisibilityGroup.php @@ -0,0 +1,52 @@ +name; + } + + public function setName(string $name): self + { + $this->name = $name; + + return $this; + } + + public function getLabel(): string + { + return $this->label; + } + + public function setLabel(string $label): self + { + $this->label = $label; + + return $this; + } + + public function isDefault(): bool + { + return $this->isDefault; + } + + public function setIsDefault(bool $isDefault): self + { + $this->isDefault = $isDefault; + + return $this; + } +} diff --git a/src/ColumnVisibilityGroup/ColumnVisibilityGroupBuilder.php b/src/ColumnVisibilityGroup/ColumnVisibilityGroupBuilder.php new file mode 100644 index 00000000..0252e15c --- /dev/null +++ b/src/ColumnVisibilityGroup/ColumnVisibilityGroupBuilder.php @@ -0,0 +1,33 @@ +configureOptions($optionResolver); + + $resolvedOptions = $optionResolver->resolve($options); + + $columnVisibilityGroup = new ColumnVisibilityGroup(); + $columnVisibilityGroup->setName($name); + $columnVisibilityGroup->setLabel($resolvedOptions['label'] ?? $name); + $columnVisibilityGroup->setIsDefault($resolvedOptions['is_default']); + + return $columnVisibilityGroup; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'label' => null, + 'is_default' => false, + ]); + } +} diff --git a/src/ColumnVisibilityGroup/ColumnVisibilityGroupBuilderInterface.php b/src/ColumnVisibilityGroup/ColumnVisibilityGroupBuilderInterface.php new file mode 100644 index 00000000..c3e44839 --- /dev/null +++ b/src/ColumnVisibilityGroup/ColumnVisibilityGroupBuilderInterface.php @@ -0,0 +1,10 @@ +personalizationData?->getColumn($column)?->isVisible() ?? $visible; } + if ($visible && $column->getConfig()->getOption('column_visibility_groups')) { + return + null === $this->columnVisibilityGroup + || in_array($this->columnVisibilityGroup, (array) $column->getConfig()->getOption('column_visibility_groups'), true); + } + return $visible; }); } @@ -880,6 +887,13 @@ public function isRequestFromTurboFrame(): bool return null !== $this->turboFrameId && 'kreyu_data_table_'.$this->getName() === $this->turboFrameId; } + public function setColumnVisibilityGroup(?string $columnVisibilityGroup): self + { + $this->columnVisibilityGroup = $columnVisibilityGroup; + + return $this; + } + private function dispatch(string $eventName, DataTableEvent $event): void { $dispatcher = $this->config->getEventDispatcher(); diff --git a/src/DataTableBuilder.php b/src/DataTableBuilder.php index 8f49c975..854b648c 100755 --- a/src/DataTableBuilder.php +++ b/src/DataTableBuilder.php @@ -13,6 +13,8 @@ use Kreyu\Bundle\DataTableBundle\Column\Type\CheckboxColumnType; use Kreyu\Bundle\DataTableBundle\Column\Type\ColumnTypeInterface; use Kreyu\Bundle\DataTableBundle\Column\Type\TextColumnType; +use Kreyu\Bundle\DataTableBundle\ColumnVisibilityGroup\ColumnVisibilityGroupBuilderInterface; +use Kreyu\Bundle\DataTableBundle\ColumnVisibilityGroup\ColumnVisibilityGroupInterface; use Kreyu\Bundle\DataTableBundle\Exception\BadMethodCallException; use Kreyu\Bundle\DataTableBundle\Exception\InvalidArgumentException; use Kreyu\Bundle\DataTableBundle\Exporter\ExporterBuilderInterface; @@ -136,6 +138,20 @@ class DataTableBuilder extends DataTableConfigBuilder implements DataTableBuilde */ private array $unresolvedExporters = []; + /** + * The column visibility groups defined for the data table. + * + * @var array + */ + private array $columnVisibilityGroups = []; + + /** + * The data of column visibility groups that haven't been converted to column visibility group yet. + * + * @var array + */ + private array $unresolvedColumnVisibilityGroups = []; + public function __construct( string $name, ResolvedDataTableTypeInterface $type, @@ -715,6 +731,55 @@ public function removeExporter(string $name): static return $this; } + public function addColumnVisibilityGroup(ColumnVisibilityGroupInterface|string $columnVisibilityGroup, array $options = []): static + { + if ($this->locked) { + throw $this->createBuilderLockedException(); + } + + if ($columnVisibilityGroup instanceof ColumnVisibilityGroupInterface) { + $this->columns[$columnVisibilityGroup->getName()] = $columnVisibilityGroup; + + unset($this->unresolvedColumns[$columnVisibilityGroup->getName()]); + + return $this; + } + + $this->columnVisibilityGroups[$columnVisibilityGroup] = null; + $this->unresolvedColumnVisibilityGroups[$columnVisibilityGroup] = $options; + + return $this; + } + + public function hasColumnVisibilityGroup(string $name): bool + { + if ($this->locked) { + throw $this->createBuilderLockedException(); + } + + return isset($this->columnVisibilityGroups[$name]) || isset($this->unresolvedColumnVisibilityGroups[$name]); + } + + public function removeColumnVisibilityGroup(string $name): static + { + if ($this->locked) { + throw $this->createBuilderLockedException(); + } + + unset($this->unresolvedColumnVisibilityGroups[$name], $this->columnVisibilityGroups[$name]); + + return $this; + } + + public function createColumnVisibilityGroup(string $name, array $options = []): ColumnVisibilityGroupInterface + { + if ($this->locked) { + throw $this->createBuilderLockedException(); + } + + return $this->getColumnVisibilityGroupBuilder()->getColumnVisibilityGroup($name, $options); + } + public function getDataTable(): DataTableInterface { if ($this->locked) { @@ -739,6 +804,8 @@ public function getDataTable(): DataTableInterface $this->addSearchFilter(); } + $this->resolveColumnVisibilityGroups(); + $this->resolveColumns(); foreach ($this->columns as $column) { @@ -883,6 +950,24 @@ private function resolveExporters(): void } } + private function resolveColumnVisibilityGroups(): void + { + foreach (array_keys($this->unresolvedColumnVisibilityGroups) as $columnVisibilityGroups) { + $this->resolveColumnVisibilityGroup($columnVisibilityGroups); + } + } + + private function resolveColumnVisibilityGroup(string $name): ColumnVisibilityGroupInterface + { + $options = $this->unresolvedColumnVisibilityGroups[$name]; + + unset($this->unresolvedColumnVisibilityGroups[$name]); + + $columnVisibilityGroup = $this->getColumnVisibilityGroupBuilder()->getColumnVisibilityGroup($name, $options); + + return $this->columnVisibilityGroups[$name] = $columnVisibilityGroup; + } + private function shouldPrependBatchCheckboxColumn(): bool { return $this->isAutoAddingBatchCheckboxColumn() diff --git a/src/DataTableBuilderInterface.php b/src/DataTableBuilderInterface.php index 803bde4f..ff9d1797 100755 --- a/src/DataTableBuilderInterface.php +++ b/src/DataTableBuilderInterface.php @@ -9,6 +9,7 @@ use Kreyu\Bundle\DataTableBundle\Column\ColumnBuilderInterface; use Kreyu\Bundle\DataTableBundle\Column\Type\ActionsColumnType; use Kreyu\Bundle\DataTableBundle\Column\Type\ColumnTypeInterface; +use Kreyu\Bundle\DataTableBundle\ColumnVisibilityGroup\ColumnVisibilityGroupInterface; use Kreyu\Bundle\DataTableBundle\Exception\InvalidArgumentException; use Kreyu\Bundle\DataTableBundle\Exporter\ExporterBuilderInterface; use Kreyu\Bundle\DataTableBundle\Exporter\Type\ExporterTypeInterface; @@ -196,4 +197,12 @@ public function getQuery(): ?ProxyQueryInterface; public function setQuery(?ProxyQueryInterface $query): static; public function getDataTable(): DataTableInterface; + + public function addColumnVisibilityGroup(ColumnVisibilityGroupInterface|string $columnVisibilityGroup, array $options = []): static; + + public function removeColumnVisibilityGroup(string $name): static; + + public function hasColumnVisibilityGroup(string $name): bool; + + public function createColumnVisibilityGroup(string $name, array $options = []): ColumnVisibilityGroupInterface; } diff --git a/src/DataTableConfigBuilder.php b/src/DataTableConfigBuilder.php index 4e65af54..d269ec7e 100755 --- a/src/DataTableConfigBuilder.php +++ b/src/DataTableConfigBuilder.php @@ -6,6 +6,7 @@ use Kreyu\Bundle\DataTableBundle\Action\ActionFactoryInterface; use Kreyu\Bundle\DataTableBundle\Column\ColumnFactoryInterface; +use Kreyu\Bundle\DataTableBundle\ColumnVisibilityGroup\ColumnVisibilityGroupBuilderInterface; use Kreyu\Bundle\DataTableBundle\Exception\BadMethodCallException; use Kreyu\Bundle\DataTableBundle\Exporter\ExportData; use Kreyu\Bundle\DataTableBundle\Exporter\ExporterFactoryInterface; @@ -60,6 +61,7 @@ class DataTableConfigBuilder implements DataTableConfigBuilderInterface private ?FilterFactoryInterface $filterFactory = null; private ?ActionFactoryInterface $actionFactory = null; private ?ExporterFactoryInterface $exporterFactory = null; + private ?ColumnVisibilityGroupBuilderInterface $columnVisibilityGroupBuilder = null; private ?RequestHandlerInterface $requestHandler = null; private bool $sortingClearable = false; @@ -191,6 +193,26 @@ public function setColumnFactory(ColumnFactoryInterface $columnFactory): static return $this; } + public function getColumnVisibilityGroupBuilder(): ColumnVisibilityGroupBuilderInterface + { + if (!isset($this->columnVisibilityGroupBuilder)) { + throw new BadMethodCallException(sprintf('The column factory is not set, use the "%s::setColumnVisibilityGroupBuilder()" method to set the column factory.', $this::class)); + } + + return $this->columnVisibilityGroupBuilder; + } + + public function setColumnVisibilityGroupBuilder(ColumnVisibilityGroupBuilderInterface $columnVisibilityGroupBuilder): static + { + if ($this->locked) { + throw $this->createBuilderLockedException(); + } + + $this->columnVisibilityGroupBuilder = $columnVisibilityGroupBuilder; + + return $this; + } + public function getFilterFactory(): FilterFactoryInterface { if (!isset($this->filterFactory)) { @@ -850,6 +872,11 @@ public function getPersonalizationParameterName(): string return $this->getParameterName(static::PERSONALIZATION_PARAMETER); } + public function getColumnVisibilityGroupParameterName(): string + { + return $this->getParameterName(static::COLUMN_VISIBILITY_GROUP_PARAMETER); + } + public function getExportParameterName(): string { return $this->getParameterName(static::EXPORT_PARAMETER); diff --git a/src/DataTableConfigBuilderInterface.php b/src/DataTableConfigBuilderInterface.php index 064499b1..66207850 100755 --- a/src/DataTableConfigBuilderInterface.php +++ b/src/DataTableConfigBuilderInterface.php @@ -6,6 +6,7 @@ use Kreyu\Bundle\DataTableBundle\Action\ActionFactoryInterface; use Kreyu\Bundle\DataTableBundle\Column\ColumnFactoryInterface; +use Kreyu\Bundle\DataTableBundle\ColumnVisibilityGroup\ColumnVisibilityGroupBuilderInterface; use Kreyu\Bundle\DataTableBundle\Exporter\ExportData; use Kreyu\Bundle\DataTableBundle\Exporter\ExporterFactoryInterface; use Kreyu\Bundle\DataTableBundle\Filter\FilterFactoryInterface; @@ -51,6 +52,8 @@ public function setActionFactory(ActionFactoryInterface $actionFactory): static; public function setExporterFactory(ExporterFactoryInterface $exporterFactory): static; + public function setColumnVisibilityGroupBuilder(ColumnVisibilityGroupBuilderInterface $columnVisibilityGroupBuilder): static; + public function setExportingEnabled(bool $exportingEnabled): static; public function setExportFormFactory(?FormFactoryInterface $exportFormFactory): static; diff --git a/src/DataTableConfigInterface.php b/src/DataTableConfigInterface.php index c78eba6a..374ed1da 100755 --- a/src/DataTableConfigInterface.php +++ b/src/DataTableConfigInterface.php @@ -27,6 +27,7 @@ interface DataTableConfigInterface public const SORT_PARAMETER = 'sort'; public const FILTRATION_PARAMETER = 'filter'; public const PERSONALIZATION_PARAMETER = 'personalization'; + public const COLUMN_VISIBILITY_GROUP_PARAMETER = 'column_visibility_group'; public const EXPORT_PARAMETER = 'export'; public function getEventDispatcher(): EventDispatcherInterface; @@ -133,5 +134,7 @@ public function getFiltrationParameterName(): string; public function getPersonalizationParameterName(): string; + public function getColumnVisibilityGroupParameterName(): string; + public function getExportParameterName(): string; } diff --git a/src/DataTableInterface.php b/src/DataTableInterface.php index 177bb90e..b9d4881e 100755 --- a/src/DataTableInterface.php +++ b/src/DataTableInterface.php @@ -219,4 +219,6 @@ public function createExportView(): DataTableView; public function setTurboFrameId(string $turboFrameId): static; public function isRequestFromTurboFrame(): bool; + + public function setColumnVisibilityGroup(?string $columnVisibilityGroup): self; } diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index e1ec2e20..571cda97 100755 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -48,6 +48,9 @@ public function getConfigTreeBuilder(): TreeBuilder ->scalarNode('action_factory') ->defaultValue('kreyu_data_table.action.factory') ->end() + ->scalarNode('column_visibility_group_builder') + ->defaultValue('kreyu_data_table.column_visibility_group.builder') + ->end() ->scalarNode('request_handler') ->defaultValue('kreyu_data_table.request_handler.http_foundation') ->end() diff --git a/src/DependencyInjection/KreyuDataTableExtension.php b/src/DependencyInjection/KreyuDataTableExtension.php index 57b9ddec..06b756d4 100755 --- a/src/DependencyInjection/KreyuDataTableExtension.php +++ b/src/DependencyInjection/KreyuDataTableExtension.php @@ -116,6 +116,7 @@ private function resolveConfiguration(array $configs, ContainerBuilder $containe 'action_factory', 'filter_factory', 'exporter_factory', + 'column_visibility_group_builder', 'request_handler', ]; diff --git a/src/Request/HttpFoundationRequestHandler.php b/src/Request/HttpFoundationRequestHandler.php index 29c9efbe..f12c76a2 100755 --- a/src/Request/HttpFoundationRequestHandler.php +++ b/src/Request/HttpFoundationRequestHandler.php @@ -38,6 +38,7 @@ public function handle(DataTableInterface $dataTable, mixed $request = null): vo $this->paginate($dataTable, $request); $this->export($dataTable, $request); $this->turbo($dataTable, $request); + $this->columnVisibilityGroup($dataTable, $request); } private function filter(DataTableInterface $dataTable, Request $request): void @@ -140,4 +141,13 @@ private function turbo(DataTableInterface $dataTable, Request $request): void { $dataTable->setTurboFrameId($request->headers->get('Turbo-Frame')); } + + private function columnVisibilityGroup(DataTableInterface $dataTable, Request $request): void + { + $parameterName = $dataTable->getConfig()->getColumnVisibilityGroupParameterName(); + + $columnVisibility = $this->extractQueryParameter($request, "[$parameterName]"); + + $dataTable->setColumnVisibilityGroup($columnVisibility); + } } diff --git a/src/Resources/config/columns.php b/src/Resources/config/columns.php index 46e9ffe5..c6d214cd 100755 --- a/src/Resources/config/columns.php +++ b/src/Resources/config/columns.php @@ -26,6 +26,8 @@ use Kreyu\Bundle\DataTableBundle\Column\Type\ResolvedColumnTypeFactoryInterface; use Kreyu\Bundle\DataTableBundle\Column\Type\TemplateColumnType; use Kreyu\Bundle\DataTableBundle\Column\Type\TextColumnType; +use Kreyu\Bundle\DataTableBundle\ColumnVisibilityGroup\ColumnVisibilityGroupBuilder; +use Kreyu\Bundle\DataTableBundle\ColumnVisibilityGroup\ColumnVisibilityGroupBuilderInterface; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; @@ -56,6 +58,11 @@ ->alias(ColumnFactoryInterface::class, 'kreyu_data_table.column.factory') ; + $services + ->set('kreyu_data_table.column_visibility_group.builder', ColumnVisibilityGroupBuilder::class) + ->alias(ColumnVisibilityGroupBuilderInterface::class, 'kreyu_data_table.column_visibility_group.builder') + ; + $services ->set('kreyu_data_table.column.type.column', ColumnType::class) ->args([service('translator')->nullOnInvalid()]) diff --git a/src/Type/DataTableType.php b/src/Type/DataTableType.php index e76e935a..112f2c3e 100755 --- a/src/Type/DataTableType.php +++ b/src/Type/DataTableType.php @@ -9,6 +9,7 @@ use Kreyu\Bundle\DataTableBundle\Action\ActionView; use Kreyu\Bundle\DataTableBundle\Column\ColumnFactoryInterface; use Kreyu\Bundle\DataTableBundle\Column\ColumnInterface; +use Kreyu\Bundle\DataTableBundle\ColumnVisibilityGroup\ColumnVisibilityGroupBuilderInterface; use Kreyu\Bundle\DataTableBundle\DataTableBuilderInterface; use Kreyu\Bundle\DataTableBundle\DataTableInterface; use Kreyu\Bundle\DataTableBundle\DataTableView; @@ -43,6 +44,7 @@ public function buildDataTable(DataTableBuilderInterface $builder, array $option $setters = [ 'themes' => $builder->setThemes(...), 'column_factory' => $builder->setColumnFactory(...), + 'column_visibility_group_builder' => $builder->setColumnVisibilityGroupBuilder(...), 'filter_factory' => $builder->setFilterFactory(...), 'action_factory' => $builder->setActionFactory(...), 'exporter_factory' => $builder->setExporterFactory(...), @@ -163,6 +165,7 @@ public function configureOptions(OptionsResolver $resolver): void 'filter_factory' => $this->defaults['filtration']['filter_factory'] ?? null, 'action_factory' => $this->defaults['action_factory'] ?? null, 'exporter_factory' => $this->defaults['exporting']['exporter_factory'] ?? null, + 'column_visibility_group_builder' => $this->defaults['column_visibility_group_builder'] ?? null, 'request_handler' => $this->defaults['request_handler'] ?? null, 'sorting_enabled' => $this->defaults['sorting']['enabled'] ?? true, 'sorting_clearable' => $this->defaults['sorting']['clearable'] ?? true, @@ -195,6 +198,7 @@ public function configureOptions(OptionsResolver $resolver): void ->setAllowedTypes('filter_factory', ['null', FilterFactoryInterface::class]) ->setAllowedTypes('action_factory', ['null', ActionFactoryInterface::class]) ->setAllowedTypes('exporter_factory', ['null', ExporterFactoryInterface::class]) + ->setAllowedTypes('column_visibility_group_builder', ['null', ColumnVisibilityGroupBuilderInterface::class]) ->setAllowedTypes('request_handler', ['null', RequestHandlerInterface::class]) ->setAllowedTypes('sorting_enabled', 'bool') ->setAllowedTypes('sorting_clearable', 'bool') From 1a5203bf16f4c52868d975a1423774cbd2ecc22b Mon Sep 17 00:00:00 2001 From: Alexandre Castelain Date: Fri, 22 Aug 2025 15:32:21 +0200 Subject: [PATCH 2/5] Enhance column visibility group management with translation support and UI integration --- .../ColumnVisibilityGroupBuilder.php | 8 +++++- .../ColumnVisibilityGroupInterface.php | 1 + src/DataTable.php | 28 +++++++++++++++---- src/DataTableBuilder.php | 10 +++++-- src/DataTableInterface.php | 7 ++++- src/DataTableView.php | 6 ++++ src/Request/HttpFoundationRequestHandler.php | 12 ++++++-- src/Resources/config/columns.php | 1 + src/Resources/views/themes/base.html.twig | 25 ++++++++++++++++- .../views/themes/bootstrap_5.html.twig | 8 ++++++ src/Type/DataTableType.php | 2 ++ 11 files changed, 94 insertions(+), 14 deletions(-) diff --git a/src/ColumnVisibilityGroup/ColumnVisibilityGroupBuilder.php b/src/ColumnVisibilityGroup/ColumnVisibilityGroupBuilder.php index 0252e15c..d6cfd9f6 100644 --- a/src/ColumnVisibilityGroup/ColumnVisibilityGroupBuilder.php +++ b/src/ColumnVisibilityGroup/ColumnVisibilityGroupBuilder.php @@ -5,9 +5,15 @@ namespace Kreyu\Bundle\DataTableBundle\ColumnVisibilityGroup; use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Contracts\Translation\TranslatorInterface; class ColumnVisibilityGroupBuilder implements ColumnVisibilityGroupBuilderInterface { + public function __construct( + private TranslatorInterface $translator, + ) { + } + public function getColumnVisibilityGroup(string $name, array $options = []): ColumnVisibilityGroupInterface { $optionResolver = new OptionsResolver(); @@ -17,7 +23,7 @@ public function getColumnVisibilityGroup(string $name, array $options = []): Col $columnVisibilityGroup = new ColumnVisibilityGroup(); $columnVisibilityGroup->setName($name); - $columnVisibilityGroup->setLabel($resolvedOptions['label'] ?? $name); + $columnVisibilityGroup->setLabel($this->translator->trans($resolvedOptions['label'] ?? $name)); $columnVisibilityGroup->setIsDefault($resolvedOptions['is_default']); return $columnVisibilityGroup; diff --git a/src/ColumnVisibilityGroup/ColumnVisibilityGroupInterface.php b/src/ColumnVisibilityGroup/ColumnVisibilityGroupInterface.php index 9a55fe04..da76d67e 100644 --- a/src/ColumnVisibilityGroup/ColumnVisibilityGroupInterface.php +++ b/src/ColumnVisibilityGroup/ColumnVisibilityGroupInterface.php @@ -9,4 +9,5 @@ interface ColumnVisibilityGroupInterface public function getName(): string; public function getLabel(): string; public function isDefault(): bool; + public function setIsDefault(bool $isDefault): self; } diff --git a/src/DataTable.php b/src/DataTable.php index 19fca31b..9ec57aab 100755 --- a/src/DataTable.php +++ b/src/DataTable.php @@ -9,6 +9,7 @@ use Kreyu\Bundle\DataTableBundle\Action\Type\ActionType; use Kreyu\Bundle\DataTableBundle\Column\ColumnInterface; use Kreyu\Bundle\DataTableBundle\Column\Type\ColumnType; +use Kreyu\Bundle\DataTableBundle\ColumnVisibilityGroup\ColumnVisibilityGroupInterface; use Kreyu\Bundle\DataTableBundle\Event\DataTableEvent; use Kreyu\Bundle\DataTableBundle\Event\DataTableEvents; use Kreyu\Bundle\DataTableBundle\Event\DataTableExportEvent; @@ -74,6 +75,11 @@ class DataTable implements DataTableInterface */ private array $exporters = []; + /** + * @var array + */ + private array $columnVisibilityGroups = []; + /** * The sorting data currently applied to the data table. */ @@ -113,7 +119,7 @@ class DataTable implements DataTableInterface private bool $initialized = false; private ?string $turboFrameId = null; - private ?string $columnVisibilityGroup = null; + private ?string $requestedColumnVisibilityGroup = null; public function __construct( private ProxyQueryInterface $query, @@ -205,8 +211,8 @@ public function getVisibleColumns(): array if ($visible && $column->getConfig()->getOption('column_visibility_groups')) { return - null === $this->columnVisibilityGroup - || in_array($this->columnVisibilityGroup, (array) $column->getConfig()->getOption('column_visibility_groups'), true); + null === $this->requestedColumnVisibilityGroup + || in_array($this->requestedColumnVisibilityGroup, (array) $column->getConfig()->getOption('column_visibility_groups'), true); } return $visible; @@ -515,6 +521,18 @@ public function removeExporter(string $name): static return $this; } + public function addColumnVisibilityGroup(ColumnVisibilityGroupInterface $columnVisibilityGroup): static + { + $this->columnVisibilityGroups[$columnVisibilityGroup->getName()] = $columnVisibilityGroup; + + return $this; + } + + public function getColumnVisibilityGroups(): array + { + return $this->columnVisibilityGroups; + } + public function paginate(PaginationData $data, bool $persistence = true): void { if (!$this->config->isPaginationEnabled()) { @@ -887,9 +905,9 @@ public function isRequestFromTurboFrame(): bool return null !== $this->turboFrameId && 'kreyu_data_table_'.$this->getName() === $this->turboFrameId; } - public function setColumnVisibilityGroup(?string $columnVisibilityGroup): self + public function setRequestedColumnVisibilityGroup(?string $requestedColumnVisibilityGroup): self { - $this->columnVisibilityGroup = $columnVisibilityGroup; + $this->requestedColumnVisibilityGroup = $requestedColumnVisibilityGroup; return $this; } diff --git a/src/DataTableBuilder.php b/src/DataTableBuilder.php index 854b648c..588ac969 100755 --- a/src/DataTableBuilder.php +++ b/src/DataTableBuilder.php @@ -141,7 +141,7 @@ class DataTableBuilder extends DataTableConfigBuilder implements DataTableBuilde /** * The column visibility groups defined for the data table. * - * @var array + * @var array */ private array $columnVisibilityGroups = []; @@ -804,14 +804,18 @@ public function getDataTable(): DataTableInterface $this->addSearchFilter(); } - $this->resolveColumnVisibilityGroups(); - $this->resolveColumns(); foreach ($this->columns as $column) { $dataTable->addColumn($column->getColumn()); } + $this->resolveColumnVisibilityGroups(); + + foreach ($this->columnVisibilityGroups as $columnVisibilityGroup) { + $dataTable->addColumnVisibilityGroup($columnVisibilityGroup); + } + $this->resolveFilters(); foreach ($this->filters as $filter) { diff --git a/src/DataTableInterface.php b/src/DataTableInterface.php index b9d4881e..e29a17bf 100755 --- a/src/DataTableInterface.php +++ b/src/DataTableInterface.php @@ -10,6 +10,7 @@ use Kreyu\Bundle\DataTableBundle\Column\ColumnInterface; use Kreyu\Bundle\DataTableBundle\Column\Type\ColumnType; use Kreyu\Bundle\DataTableBundle\Column\Type\ColumnTypeInterface; +use Kreyu\Bundle\DataTableBundle\ColumnVisibilityGroup\ColumnVisibilityGroupInterface; use Kreyu\Bundle\DataTableBundle\Exception\OutOfBoundsException; use Kreyu\Bundle\DataTableBundle\Exporter\ExportData; use Kreyu\Bundle\DataTableBundle\Exporter\ExporterInterface; @@ -219,6 +220,10 @@ public function createExportView(): DataTableView; public function setTurboFrameId(string $turboFrameId): static; public function isRequestFromTurboFrame(): bool; + public function setRequestedColumnVisibilityGroup(?string $requestedColumnVisibilityGroup): self; - public function setColumnVisibilityGroup(?string $columnVisibilityGroup): self; + /** + * @return array + */ + public function getColumnVisibilityGroups(): array; } diff --git a/src/DataTableView.php b/src/DataTableView.php index 2fa5ea09..b98c0542 100755 --- a/src/DataTableView.php +++ b/src/DataTableView.php @@ -5,6 +5,7 @@ namespace Kreyu\Bundle\DataTableBundle; use Kreyu\Bundle\DataTableBundle\Action\ActionView; +use Kreyu\Bundle\DataTableBundle\ColumnVisibilityGroup\ColumnVisibilityGroupInterface; use Kreyu\Bundle\DataTableBundle\Filter\FilterView; use Kreyu\Bundle\DataTableBundle\Pagination\PaginationView; @@ -32,4 +33,9 @@ class DataTableView * @var array */ public array $actions = []; + + /** + * @var array + */ + public array $columnVisibilityGroups = []; } diff --git a/src/Request/HttpFoundationRequestHandler.php b/src/Request/HttpFoundationRequestHandler.php index f12c76a2..fb786d6e 100755 --- a/src/Request/HttpFoundationRequestHandler.php +++ b/src/Request/HttpFoundationRequestHandler.php @@ -146,8 +146,14 @@ private function columnVisibilityGroup(DataTableInterface $dataTable, Request $r { $parameterName = $dataTable->getConfig()->getColumnVisibilityGroupParameterName(); - $columnVisibility = $this->extractQueryParameter($request, "[$parameterName]"); - - $dataTable->setColumnVisibilityGroup($columnVisibility); + $requestColumnVisibilityGroup = $this->extractQueryParameter($request, "[$parameterName]"); + + // This also checks if the requested column visibility group exists + foreach ($dataTable->getColumnVisibilityGroups() as $columnVisibilityGroup) { + if ($columnVisibilityGroup->getName() === $requestColumnVisibilityGroup) { + $columnVisibilityGroup->setIsDefault(true); + $dataTable->setRequestedColumnVisibilityGroup($requestColumnVisibilityGroup); + } + } } } diff --git a/src/Resources/config/columns.php b/src/Resources/config/columns.php index c6d214cd..6711b833 100755 --- a/src/Resources/config/columns.php +++ b/src/Resources/config/columns.php @@ -60,6 +60,7 @@ $services ->set('kreyu_data_table.column_visibility_group.builder', ColumnVisibilityGroupBuilder::class) + ->args([service('translator')]) ->alias(ColumnVisibilityGroupBuilderInterface::class, 'kreyu_data_table.column_visibility_group.builder') ; diff --git a/src/Resources/views/themes/base.html.twig b/src/Resources/views/themes/base.html.twig index 53dcf297..31cbb5f7 100755 --- a/src/Resources/views/themes/base.html.twig +++ b/src/Resources/views/themes/base.html.twig @@ -59,7 +59,30 @@ {{ block('action_bar', theme) }} {% endblock %} -{% block action_bar %}{% endblock %} +{% block action_bar %} + {{ block('column_visibility_group_selector', theme) }} +{% endblock %} + +{% block column_visibility_group_selector %} + {% if data_table.columnVisibilityGroups is not empty %} + + {% set form_attr = { 'data-turbo-frame': '_self' }|merge(form_attr ?? {}) %} + +
+ {% set select_attr = { + name: data_table.vars.column_visibility_group_parameter_name, + autocomplete: 'off', + onchange: 'this.form.requestSubmit()', + }|merge(select_attr|default({})) %} + + +
+ {% endif %} +{% endblock %} {% block table %} diff --git a/src/Resources/views/themes/bootstrap_5.html.twig b/src/Resources/views/themes/bootstrap_5.html.twig index 59b64b0a..05217a91 100755 --- a/src/Resources/views/themes/bootstrap_5.html.twig +++ b/src/Resources/views/themes/bootstrap_5.html.twig @@ -36,6 +36,8 @@
+ {{ block('column_visibility_group_selector', theme) }} + {% if filtration_enabled and filtration_form and filtration_form.vars.search_fields|length > 0 %}
{{ block('action_search') }} @@ -492,6 +494,12 @@ {% endwith %} {% endblock %} +{% block column_visibility_group_selector %} + {% with { select_attr: { class: 'col form-select' } } %} + {{ parent() }} + {% endwith %} +{% endblock %} + {% block pagination_page %}
{% with { attr: { class: 'page-link' } } %} diff --git a/src/Type/DataTableType.php b/src/Type/DataTableType.php index 112f2c3e..62cb4b1f 100755 --- a/src/Type/DataTableType.php +++ b/src/Type/DataTableType.php @@ -103,6 +103,7 @@ public function buildView(DataTableView $view, DataTableInterface $dataTable, ar 'filtration_parameter_name' => $dataTable->getConfig()->getFiltrationParameterName(), 'personalization_parameter_name' => $dataTable->getConfig()->getPersonalizationParameterName(), 'export_parameter_name' => $dataTable->getConfig()->getExportParameterName(), + 'column_visibility_group_parameter_name' => $dataTable->getConfig()->getColumnVisibilityGroupParameterName(), 'has_active_filters' => $dataTable->hasActiveFilters(), 'filtration_data' => $dataTable->getFiltrationData(), 'sorting_data' => $dataTable->getSortingData(), @@ -117,6 +118,7 @@ public function buildView(DataTableView $view, DataTableInterface $dataTable, ar $view->pagination = $this->createPaginationView($view, $dataTable); $view->filters = $this->createFilterViews($view, $dataTable); $view->actions = $this->createActionViews($view, $dataTable); + $view->columnVisibilityGroups = $dataTable->getColumnVisibilityGroups(); $view->vars = array_replace($view->vars, [ 'header_row' => $view->headerRow, From 47dea7dd49566e1aaec89bd6318189b2cfeb96e0 Mon Sep 17 00:00:00 2001 From: Alexandre Castelain Date: Fri, 22 Aug 2025 15:47:52 +0200 Subject: [PATCH 3/5] Add documentation --- .../docs/features/column-visibility-group.md | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 docs/src/docs/features/column-visibility-group.md diff --git a/docs/src/docs/features/column-visibility-group.md b/docs/src/docs/features/column-visibility-group.md new file mode 100644 index 00000000..9e19cd92 --- /dev/null +++ b/docs/src/docs/features/column-visibility-group.md @@ -0,0 +1,65 @@ +# Column Visibility Groups + +Column Visibility Groups allow you to organize table columns into different "views." This is useful when you have a lot of information to display in a single row and want to separate it into multiple, easily switchable groups. Users can select which group of columns to display using a dropdown in the table UI. + +[[toc]] + +## Basic Usage + +By default, a data table has a single visibility group. You can define additional groups and assign columns to them. + +```php +use Kreyu\Bundle\DataTableBundle\DataTableBuilderInterface; +use Kreyu\Bundle\DataTableBundle\Type\AbstractDataTableType; +use Kreyu\Bundle\DataTableBundle\Column\Type\NumberColumnType; +use Kreyu\Bundle\DataTableBundle\Column\Type\TextColumnType; + +class ExampleDataTableType extends AbstractDataTableType +{ + public function buildDataTable(DataTableBuilderInterface $builder, array $options): void + { + // Define visibility groups + $builder->addColumnVisibilityGroup('default'); + $builder->addColumnVisibilityGroup('address', [ + // By default, the group label is the group name, but you can override it: + 'label' => 'Address related content', + // By default, the first defined group is the default one, but you can override it: + 'is_default' => true, + ]); + + // Assign groups to columns + $builder + ->addColumn('id', NumberColumnType::class, [ + 'sort' => true, + // Will always be displayed as it does not have any group assigned + ]) + ->addColumn('name', TextColumnType::class, [ + 'label' => 'Full name', + 'sort' => true, + ]) + ->addColumn('streetName', TextColumnType::class, [ + 'sort' => true, + // This column will only be visible when the "address" group is selected + 'column_visibility_groups' => ['address'], + ]) + ; + } +} +``` + +## How It Works + +- **Defining Groups:** Use `$builder->addColumnVisibilityGroup($name, $options)` to define one or more groups. The `label` option is used as the display name in the UI. +- **Assigning Columns:** Use the `column_visibility_groups` option in `addColumn()` to assign a column to one or more groups. If omitted or set to `null`/`[]`, the column will always be visible. +- **Switching Views:** A select dropdown appears in the table, allowing users to switch between the different column visibility groups. + +## Notes + +- You can define as many visibility groups as needed. +- A column can belong to multiple groups by specifying multiple group names in the `column_visibility_groups` array. +- If `column_visibility_groups` is `null` or an empty array, the column is shown in the "default" group. +- Creating a default group is optional but recommended for better user experience : it ensures that the user can go back to the base view. + +## UI + +When multiple visibility groups are present, a select dropdown is rendered above the table, allowing users to choose which group of columns to display. From ca34083b6196edd9c27a8ab16f1e85bd0f4c0c2d Mon Sep 17 00:00:00 2001 From: Alexandre Castelain Date: Fri, 22 Aug 2025 15:48:03 +0200 Subject: [PATCH 4/5] Run php-cs-fixer --- src/ColumnVisibilityGroup/ColumnVisibilityGroupInterface.php | 3 +++ src/DataTableBuilder.php | 1 - src/DataTableInterface.php | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ColumnVisibilityGroup/ColumnVisibilityGroupInterface.php b/src/ColumnVisibilityGroup/ColumnVisibilityGroupInterface.php index da76d67e..eb43ff68 100644 --- a/src/ColumnVisibilityGroup/ColumnVisibilityGroupInterface.php +++ b/src/ColumnVisibilityGroup/ColumnVisibilityGroupInterface.php @@ -7,7 +7,10 @@ interface ColumnVisibilityGroupInterface { public function getName(): string; + public function getLabel(): string; + public function isDefault(): bool; + public function setIsDefault(bool $isDefault): self; } diff --git a/src/DataTableBuilder.php b/src/DataTableBuilder.php index 588ac969..e3fc0f2b 100755 --- a/src/DataTableBuilder.php +++ b/src/DataTableBuilder.php @@ -13,7 +13,6 @@ use Kreyu\Bundle\DataTableBundle\Column\Type\CheckboxColumnType; use Kreyu\Bundle\DataTableBundle\Column\Type\ColumnTypeInterface; use Kreyu\Bundle\DataTableBundle\Column\Type\TextColumnType; -use Kreyu\Bundle\DataTableBundle\ColumnVisibilityGroup\ColumnVisibilityGroupBuilderInterface; use Kreyu\Bundle\DataTableBundle\ColumnVisibilityGroup\ColumnVisibilityGroupInterface; use Kreyu\Bundle\DataTableBundle\Exception\BadMethodCallException; use Kreyu\Bundle\DataTableBundle\Exception\InvalidArgumentException; diff --git a/src/DataTableInterface.php b/src/DataTableInterface.php index e29a17bf..9e2cc0c4 100755 --- a/src/DataTableInterface.php +++ b/src/DataTableInterface.php @@ -220,6 +220,7 @@ public function createExportView(): DataTableView; public function setTurboFrameId(string $turboFrameId): static; public function isRequestFromTurboFrame(): bool; + public function setRequestedColumnVisibilityGroup(?string $requestedColumnVisibilityGroup): self; /** From 4bdb339f83adec4213f7bfe928d1a6b54fcf3206 Mon Sep 17 00:00:00 2001 From: Alexandre Castelain Date: Fri, 22 Aug 2025 17:08:23 +0200 Subject: [PATCH 5/5] Update the URL with the visibility group + handle properly default values --- src/Request/HttpFoundationRequestHandler.php | 5 +++++ src/Resources/views/themes/base.html.twig | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Request/HttpFoundationRequestHandler.php b/src/Request/HttpFoundationRequestHandler.php index fb786d6e..a924ec20 100755 --- a/src/Request/HttpFoundationRequestHandler.php +++ b/src/Request/HttpFoundationRequestHandler.php @@ -148,8 +148,13 @@ private function columnVisibilityGroup(DataTableInterface $dataTable, Request $r $requestColumnVisibilityGroup = $this->extractQueryParameter($request, "[$parameterName]"); + if (null === $requestColumnVisibilityGroup) { + return; + } + // This also checks if the requested column visibility group exists foreach ($dataTable->getColumnVisibilityGroups() as $columnVisibilityGroup) { + $columnVisibilityGroup->setIsDefault(false); if ($columnVisibilityGroup->getName() === $requestColumnVisibilityGroup) { $columnVisibilityGroup->setIsDefault(true); $dataTable->setRequestedColumnVisibilityGroup($requestColumnVisibilityGroup); diff --git a/src/Resources/views/themes/base.html.twig b/src/Resources/views/themes/base.html.twig index 31cbb5f7..605274e7 100755 --- a/src/Resources/views/themes/base.html.twig +++ b/src/Resources/views/themes/base.html.twig @@ -66,7 +66,7 @@ {% block column_visibility_group_selector %} {% if data_table.columnVisibilityGroups is not empty %} - {% set form_attr = { 'data-turbo-frame': '_self' }|merge(form_attr ?? {}) %} + {% set form_attr = { 'data-turbo-frame': '_self', 'data-turbo-action': 'advance' }|merge(form_attr ?? {}) %}
{% set select_attr = {