diff --git a/app/Grid/Renderer/TwigBulkActionGridRenderer.php b/app/Grid/Renderer/TwigBulkActionGridRenderer.php new file mode 100644 index 000000000..256a425b0 --- /dev/null +++ b/app/Grid/Renderer/TwigBulkActionGridRenderer.php @@ -0,0 +1,58 @@ +getType(); + if (!isset($this->bulkActionTemplates[$type])) { + throw new \InvalidArgumentException(sprintf('Missing template for bulk action type "%s".', $type)); + } + + $options = $this->optionsParser->parseOptions( + $bulkAction->getOptions(), + $this->requestStack?->getCurrentRequest() ?? new Request(), + $data, + ); + + return $this->twig->render($this->bulkActionTemplates[$type], [ + 'grid' => $gridView, + 'action' => $bulkAction, + 'data' => $data, + 'options' => $options, + ]); + } +} diff --git a/app/Grid/Renderer/TwigGridRenderer.php b/app/Grid/Renderer/TwigGridRenderer.php new file mode 100644 index 000000000..51a3a4f3c --- /dev/null +++ b/app/Grid/Renderer/TwigGridRenderer.php @@ -0,0 +1,79 @@ +gridRenderer->render($gridView, $template); + } + + public function renderField(GridViewInterface $gridView, Field $field, $data): string + { + return $this->gridRenderer->renderField($gridView, $field, $data); + } + + public function renderAction(GridViewInterface $gridView, Action $action, $data = null): string + { + $type = $action->getType(); + + $template = $action->getOptions()['template'] ?? $this->actionTemplates[$type] ?? null; + + if (null === $template) { + throw new \InvalidArgumentException(sprintf('Missing template for action type "%s".', $type)); + } + + $options = $this->optionsParser->parseOptions( + $action->getOptions(), + $this->requestStack?->getCurrentRequest() ?? new Request(), + $data, + ); + + return $this->twig->render($template, [ + 'grid' => $gridView, + 'action' => $action, + 'data' => $data, + 'options' => $options, + ]); + } + + public function renderFilter(GridViewInterface $gridView, Filter $filter): string + { + return $this->gridRenderer->renderFilter($gridView, $filter); + } +} diff --git a/app/Grid/SpeakerGrid.php b/app/Grid/SpeakerGrid.php index 6384256bf..b4e7d7943 100644 --- a/app/Grid/SpeakerGrid.php +++ b/app/Grid/SpeakerGrid.php @@ -38,6 +38,7 @@ public static function getName(): string public function buildGrid(GridBuilderInterface $gridBuilder): void { $gridBuilder + ->setLimits([10, 25, 50]) ->addFilter( StringFilter::create('search', ['firstName', 'lastName', 'companyName']) ->setLabel('sylius.ui.search'), @@ -82,8 +83,23 @@ public function buildGrid(GridBuilderInterface $gridBuilder): void ], ], ]), - UpdateAction::create(), - DeleteAction::create(), + UpdateAction::create() + ->setOptions([ + 'link' => [ + 'route' => 'app_admin_speaker_update', + 'parameters' => [ + 'id' => 'resource.id', + ], + ], + ]), + DeleteAction::create()->setOptions([ + 'link' => [ + 'route' => 'app_admin_speaker_delete', + 'parameters' => [ + 'id' => 'resource.id', + ], + ], + ]), ), ) ->addActionGroup( diff --git a/app/Twig/Component/SpeakerDataTableComponent.php b/app/Twig/Component/SpeakerDataTableComponent.php new file mode 100644 index 000000000..44c42ea00 --- /dev/null +++ b/app/Twig/Component/SpeakerDataTableComponent.php @@ -0,0 +1,37 @@ + ['all' => true], Sylius\UiTranslations\Symfony\SyliusUiTranslationsBundle::class => ['all' => true], Sylius\BootstrapAdminUi\Symfony\SyliusBootstrapAdminUiBundle::class => ['all' => true], + Sylius\TwigComponentGrid\Symfony\SyliusTwigComponentGridBundle::class => ['all' => true], Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true], Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true], Symfony\UX\LiveComponent\LiveComponentBundle::class => ['all' => true], diff --git a/config/sylius/twig_hooks/speaker/index.php b/config/sylius/twig_hooks/speaker/index.php index 55bb5f6a4..a81b2d48b 100644 --- a/config/sylius/twig_hooks/speaker/index.php +++ b/config/sylius/twig_hooks/speaker/index.php @@ -13,6 +13,9 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; +use App\Twig\Component\SpeakerDataTableComponent; +use App\Twig\Component\SpeakerGridComponent; + return static function (ContainerConfigurator $container): void { $container->extension('sylius_twig_hooks', [ 'hooks' => [ @@ -26,6 +29,18 @@ ], ], ], + + 'sylius_admin.speaker.index.content.grid' => [ + 'data_table' => [ + 'component' => SpeakerDataTableComponent::class, + 'props' => [ + 'page' => '@=_context.page', + 'limit' => '@=_context.limit', + 'criteria' => '@=_context.criteria', + 'sorting' => '@=_context.sorting', + ], + ], + ], ], ]); }; diff --git a/importmap.php b/importmap.php index 3b49af095..ea1cce89e 100644 --- a/importmap.php +++ b/importmap.php @@ -45,4 +45,12 @@ '@symfony/ux-live-component' => [ 'path' => './vendor/symfony/ux-live-component/assets/dist/live_controller.js', ], + 'tom-select/dist/css/tom-select.bootstrap4.css' => [ + 'version' => '2.4.3', + 'type' => 'css', + ], + 'tom-select/dist/css/tom-select.bootstrap5.css' => [ + 'version' => '2.4.3', + 'type' => 'css', + ], ]; diff --git a/src/AdminUi/config/routes.php b/src/AdminUi/config/routes.php index 18f91d455..761142f24 100644 --- a/src/AdminUi/config/routes.php +++ b/src/AdminUi/config/routes.php @@ -32,4 +32,10 @@ 'template' => '@SyliusAdminUi/dashboard/index.html.twig', ]) ; + + $routes->add('sylius_admin_ui_live_component', '/_components/{_live_component}/{_live_action}') + ->defaults([ + '_live_action' => 'get', + ]) + ; }; diff --git a/src/BootstrapAdminUi/assets/styles/_body.scss b/src/BootstrapAdminUi/assets/styles/_body.scss index ecce06584..c34fefe00 100644 --- a/src/BootstrapAdminUi/assets/styles/_body.scss +++ b/src/BootstrapAdminUi/assets/styles/_body.scss @@ -40,6 +40,13 @@ a.link-reset { text-decoration: none; } +button.link-reset { + border: none; + background-color: transparent; + text-transform: inherit; + font-weight: inherit; +} + .btn-collapse { &.collapsed { .icon-chevron-right { diff --git a/src/BootstrapAdminUi/public/app.css b/src/BootstrapAdminUi/public/app.css index 773ef909b..2232117b3 100644 --- a/src/BootstrapAdminUi/public/app.css +++ b/src/BootstrapAdminUi/public/app.css @@ -20,7 +20,7 @@ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - */*{--tblr-breadcrumb-item-active-color:var(--tblr-gray-500);--tblr-breadcrumb-divider-color:var(--tblr-gray-300);--tblr-code-color:#36393b;--tblr-blue-rgb:17,81,141;--tblr-green-rgb:0,97,16}[data-bs-theme=dark],body[data-bs-theme=dark] [data-bs-theme=light]{--tblr-bg-surface:#1e2433}@font-face{font-family:Inter;font-weight:1 999;src:url(fonts/Inter-VariableFont_slnt,wght.853e0197.ttf) format("truetype-variations")}body{--bs-body-bg:#f6f8fb;--bs-tertiary-bg:#f6f8fb;--bs-body-color:#212529;font-feature-settings:"cv03","cv04","cv11"}a{text-underline-offset:.25em}a.link-reset{text-decoration:none}.btn-collapse.collapsed .icon-chevron-right{display:inline-flex}.btn-collapse.collapsed .icon-chevron-down,.btn-collapse:not(.collapsed) .icon-chevron-right{display:none}.btn-collapse:not(.collapsed) .icon-chevron-down{display:inline-flex}.breadcrumb-item a{text-decoration:none}.breadcrumb-item a:hover{color:#22b99a}.switch-collapse,body[data-bs-theme=dark] html[data-bs-theme=light] [data-theme-switch=dark],html[data-bs-theme=dark] [data-theme-switch=dark],html[data-bs-theme=light] [data-theme-switch=light]{display:none}label:has(input:checked)~.switch-collapse{display:block}.btn:not(.btn-sm){min-height:44px;min-width:44px}/*! + */*{--tblr-breadcrumb-item-active-color:var(--tblr-gray-500);--tblr-breadcrumb-divider-color:var(--tblr-gray-300);--tblr-code-color:#36393b;--tblr-blue-rgb:17,81,141;--tblr-green-rgb:0,97,16}[data-bs-theme=dark],body[data-bs-theme=dark] [data-bs-theme=light]{--tblr-bg-surface:#1e2433}@font-face{font-family:Inter;font-weight:1 999;src:url(fonts/Inter-VariableFont_slnt,wght.853e0197.ttf) format("truetype-variations")}body{--bs-body-bg:#f6f8fb;--bs-tertiary-bg:#f6f8fb;--bs-body-color:#212529;font-feature-settings:"cv03","cv04","cv11"}a{text-underline-offset:.25em}a.link-reset{text-decoration:none}button.link-reset{background-color:transparent;border:none;font-weight:inherit;text-transform:inherit}.btn-collapse.collapsed .icon-chevron-right{display:inline-flex}.btn-collapse.collapsed .icon-chevron-down,.btn-collapse:not(.collapsed) .icon-chevron-right{display:none}.btn-collapse:not(.collapsed) .icon-chevron-down{display:inline-flex}.breadcrumb-item a{text-decoration:none}.breadcrumb-item a:hover{color:#22b99a}.switch-collapse,body[data-bs-theme=dark] html[data-bs-theme=light] [data-theme-switch=dark],html[data-bs-theme=dark] [data-theme-switch=dark],html[data-bs-theme=light] [data-theme-switch=light]{display:none}label:has(input:checked)~.switch-collapse{display:block}.btn:not(.btn-sm){min-height:44px;min-width:44px}/*! * This file is part of the Sylius package. * * (c) Sylius Sp. z o.o. diff --git a/src/BootstrapAdminUi/public/app.rtl.css b/src/BootstrapAdminUi/public/app.rtl.css index cbd0b8f57..2ee9c8c87 100644 --- a/src/BootstrapAdminUi/public/app.rtl.css +++ b/src/BootstrapAdminUi/public/app.rtl.css @@ -20,7 +20,7 @@ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - */*{--tblr-breadcrumb-item-active-color:var(--tblr-gray-500);--tblr-breadcrumb-divider-color:var(--tblr-gray-300);--tblr-code-color:#36393b;--tblr-blue-rgb:17,81,141;--tblr-green-rgb:0,97,16}[data-bs-theme=dark],body[data-bs-theme=dark] [data-bs-theme=light]{--tblr-bg-surface:#1e2433}@font-face{font-family:Inter;font-weight:1 999;src:url(fonts/Inter-VariableFont_slnt,wght.853e0197.ttf) format("truetype-variations")}body{--bs-body-bg:#f6f8fb;--bs-tertiary-bg:#f6f8fb;--bs-body-color:#212529;font-feature-settings:"cv03","cv04","cv11"}a{text-underline-offset:.25em}a.link-reset{text-decoration:none}.btn-collapse.collapsed .icon-chevron-right{display:inline-flex}.btn-collapse.collapsed .icon-chevron-down,.btn-collapse:not(.collapsed) .icon-chevron-right{display:none}.btn-collapse:not(.collapsed) .icon-chevron-down{display:inline-flex}.breadcrumb-item a{text-decoration:none}.breadcrumb-item a:hover{color:#22b99a}.switch-collapse,body[data-bs-theme=dark] html[data-bs-theme=light] [data-theme-switch=dark],html[data-bs-theme=dark] [data-theme-switch=dark],html[data-bs-theme=light] [data-theme-switch=light]{display:none}label:has(input:checked)~.switch-collapse{display:block}.btn:not(.btn-sm){min-height:44px;min-width:44px}/*! + */*{--tblr-breadcrumb-item-active-color:var(--tblr-gray-500);--tblr-breadcrumb-divider-color:var(--tblr-gray-300);--tblr-code-color:#36393b;--tblr-blue-rgb:17,81,141;--tblr-green-rgb:0,97,16}[data-bs-theme=dark],body[data-bs-theme=dark] [data-bs-theme=light]{--tblr-bg-surface:#1e2433}@font-face{font-family:Inter;font-weight:1 999;src:url(fonts/Inter-VariableFont_slnt,wght.853e0197.ttf) format("truetype-variations")}body{--bs-body-bg:#f6f8fb;--bs-tertiary-bg:#f6f8fb;--bs-body-color:#212529;font-feature-settings:"cv03","cv04","cv11"}a{text-underline-offset:.25em}a.link-reset{text-decoration:none}button.link-reset{background-color:transparent;border:none;font-weight:inherit;text-transform:inherit}.btn-collapse.collapsed .icon-chevron-right{display:inline-flex}.btn-collapse.collapsed .icon-chevron-down,.btn-collapse:not(.collapsed) .icon-chevron-right{display:none}.btn-collapse:not(.collapsed) .icon-chevron-down{display:inline-flex}.breadcrumb-item a{text-decoration:none}.breadcrumb-item a:hover{color:#22b99a}.switch-collapse,body[data-bs-theme=dark] html[data-bs-theme=light] [data-theme-switch=dark],html[data-bs-theme=dark] [data-theme-switch=dark],html[data-bs-theme=light] [data-theme-switch=light]{display:none}label:has(input:checked)~.switch-collapse{display:block}.btn:not(.btn-sm){min-height:44px;min-width:44px}/*! * This file is part of the Sylius package. * * (c) Sylius Sp. z o.o. diff --git a/src/BootstrapAdminUi/templates/shared/components/grid/data_table.html.twig b/src/BootstrapAdminUi/templates/shared/components/grid/data_table.html.twig new file mode 100644 index 000000000..4d6b3f455 --- /dev/null +++ b/src/BootstrapAdminUi/templates/shared/components/grid/data_table.html.twig @@ -0,0 +1,55 @@ +{% import '@SyliusBootstrapAdminUi/shared/helper/table.html.twig' as table %} +{% import '@SyliusBootstrapAdminUi/shared/helper/pagination.html.twig' as pagination %} + +{% set resources = hookable_metadata.context.resources|default(this.resources|default(null)) %} +{% set data = resources.data|default([]) %} +{% set definition = resources.definition|default(null) %} +{% set live = attributes is defined %} + +
+ {% if data|length > 0 %} +
+
+
+ {% if data|length > 0 and definition.actionGroups.bulk is defined and definition.getEnabledActions('bulk')|length > 0 %} +
+ {% for action in definition.getEnabledActions('bulk') %} + {{ sylius_grid_render_bulk_action(resources, action, null) }} + {% endfor %} +
+ {% endif %} +
+ {{ pagination.number_of_results_selector(data, definition.limits, live) }} +
+
+
+
+
+ Loading +
+
+
+ + + + {{ table.headers(resources, resources.definition, app.request.attributes, live) }} + + + + {% for row in resources.data %} + {{ table.row(resources, resources.definition, row, columns) }} + {% endfor %} + +
+
+ +
+ {% endif %} +
diff --git a/src/BootstrapAdminUi/templates/shared/crud/index/content/grid.html.twig b/src/BootstrapAdminUi/templates/shared/crud/index/content/grid.html.twig index 9672fdf4a..6ae1efd0b 100644 --- a/src/BootstrapAdminUi/templates/shared/crud/index/content/grid.html.twig +++ b/src/BootstrapAdminUi/templates/shared/crud/index/content/grid.html.twig @@ -1,3 +1,10 @@ +{% set grid_definition = hookable_metadata.context.resources.definition|default(null) %} +{% set grid = grid_definition.code|default(null) %} +{% set page = app.request.query.getInt('page', 1) %} +{% set limit = app.request.query.has('limit') ? app.request.query.getInt('limit') : grid_definition.limits|first|default(null) %} +{% set criteria = app.request.query.has('criteria') ? app.request.query.all('criteria') : null %} +{% set sorting = app.request.query.has('sorting') ? app.request.query.all('sorting') : null %} +
{% hook 'grid' %} diff --git a/src/BootstrapAdminUi/templates/shared/grid/bulk_action/delete.html.twig b/src/BootstrapAdminUi/templates/shared/grid/bulk_action/delete.html.twig index 465bc69ec..9b9706006 100644 --- a/src/BootstrapAdminUi/templates/shared/grid/bulk_action/delete.html.twig +++ b/src/BootstrapAdminUi/templates/shared/grid/bulk_action/delete.html.twig @@ -1,4 +1,5 @@ -{% set path = options.link.url|default(path(options.link.route|default(grid.requestConfiguration.getRouteName('bulk_delete')), options.link.parameters|default({}))) %} +{% set default_route = options.link.route|default(grid.requestConfiguration is defined ? grid.requestConfiguration.getRouteName('bulk_delete') : null) %} +{% set path = options.link.url|default(default_route ? path(default_route, options.link.parameters|default({})): null) %} {% component 'sylius_bootstrap_admin_ui:delete_modal' with {id: 'bulk_delete', path: path, type: 'bulk-delete', form_attr: 'data-bulk-delete=index'} %} {% import '@SyliusBootstrapAdminUi/shared/helper/button.html.twig' as button %} diff --git a/src/BootstrapAdminUi/templates/shared/helper/pagination.html.twig b/src/BootstrapAdminUi/templates/shared/helper/pagination.html.twig index ade4ba990..259f1ef0e 100644 --- a/src/BootstrapAdminUi/templates/shared/helper/pagination.html.twig +++ b/src/BootstrapAdminUi/templates/shared/helper/pagination.html.twig @@ -1,5 +1,7 @@ -{% macro simple(paginator) %} - {{ pagerfanta(paginator, 'twig', options|default({'template': '@SyliusBootstrapAdminUi/shared/pagerfanta.html.twig'})) }} +{% macro simple(paginator, live = false) %} + {% set template = live ? '@SyliusBootstrapAdminUi/shared/live_pagerfanta.html.twig' : '@SyliusBootstrapAdminUi/shared/pagerfanta.html.twig' %} + + {{ pagerfanta(paginator, 'twig', options|default({'template': template})) }} {% endmacro %} {% macro results_count(paginator) %} @@ -10,7 +12,7 @@ {{ 'sylius.ui.pagination.number_of_results'|trans({'%from%': from, '%to%': to, '%total%': paginator.nbResults}) }} {% endmacro %} -{% macro number_of_results_selector(paginator, pagination_limits) %} +{% macro number_of_results_selector(paginator, pagination_limits, live) %} {% set other_pagination_limits = pagination_limits|filter(limit => limit != paginator.maxPerPage) %} {% if other_pagination_limits is not empty %} @@ -21,7 +23,15 @@
diff --git a/src/BootstrapAdminUi/templates/shared/helper/sorting.html.twig b/src/BootstrapAdminUi/templates/shared/helper/sorting.html.twig index 16c18625a..0bd1e0dd8 100644 --- a/src/BootstrapAdminUi/templates/shared/helper/sorting.html.twig +++ b/src/BootstrapAdminUi/templates/shared/helper/sorting.html.twig @@ -1,9 +1,18 @@ -{% macro table_header(grid, field, attributes) %} +{% macro table_header(grid, field, attributes, live = false) %} {% set sorting_order = grid.getSortingOrder(field.name)|default('asc') %} {% if grid.isSortedBy(field.name) %} + {% set new_sorting_order = sorting_order == 'desc' ? 'asc' : 'desc' %} - + <{{ live ? 'button' : 'a' }} class="link-reset" + {% if not live %} + href="{{ _self.link(field.name, attributes, (sorting_order == 'desc' ? 'asc' : 'desc'), grid.parameters.all) }}" + {% endif %} + data-action="live#action" + data-live-action-param="sortBy" + data-live-field-param="{{ field.name }}" + data-live-order-param="{{ new_sorting_order }}" + > {{ field.label|trans }} {% if sorting_order == 'desc' %} @@ -15,16 +24,24 @@ {% endif %} - + {% else %} - + <{{ live ? 'button' : 'a' }} class="link-reset" + {% if not live %} + href="{{ _self.link(field.name, attributes, sorting_order, grid.parameters.all) }}" + {% endif %} + data-action="live#action" + data-live-action-param="sortBy" + data-live-field-param="{{ field.name }}" + data-live-order-param="{{ sorting_order }}" + > {{ field.label|trans }} - + {% endif %} {% endmacro %} diff --git a/src/BootstrapAdminUi/templates/shared/helper/table.html.twig b/src/BootstrapAdminUi/templates/shared/helper/table.html.twig index 386830551..348cb8088 100644 --- a/src/BootstrapAdminUi/templates/shared/helper/table.html.twig +++ b/src/BootstrapAdminUi/templates/shared/helper/table.html.twig @@ -1,4 +1,4 @@ -{% macro headers(grid, definition, request_attributes) %} +{% macro headers(grid, definition, request_attributes, live = false) %} {% import '@SyliusBootstrapAdminUi/shared/helper/sorting.html.twig' as sorting %} {% if definition.actionGroups.bulk is defined and definition.getEnabledActions('bulk')|length > 0 %} @@ -10,7 +10,7 @@ {% for field in definition.fields|sylius_sort_by('position') %} {% if field.enabled %} {% if field.isSortable %} - {{ sorting.table_header(grid, field, request_attributes) }} + {{ sorting.table_header(grid, field, request_attributes, live) }} {% else %} {{ field.label|trans }} {% endif %} diff --git a/src/BootstrapAdminUi/templates/shared/live_pagerfanta.html.twig b/src/BootstrapAdminUi/templates/shared/live_pagerfanta.html.twig new file mode 100644 index 000000000..1af17ce96 --- /dev/null +++ b/src/BootstrapAdminUi/templates/shared/live_pagerfanta.html.twig @@ -0,0 +1,42 @@ +{%- extends '@SyliusBootstrapAdminUi/shared/pagerfanta.html.twig' -%} + +{%- block page_link -%} +
  • + {{- page -}} + +
  • +{%- endblock page_link -%} + +{%- block current_page_link -%} +
  • {{- page -}}
  • +{%- endblock current_page_link -%} + +{%- block previous_page_link -%} +
  • + +
  • +{%- endblock previous_page_link -%} + +{%- block next_page_link -%} +
  • + +
  • +{%- endblock next_page_link -%} diff --git a/src/TwigComponentGrid/composer.json b/src/TwigComponentGrid/composer.json new file mode 100644 index 000000000..f2c0a7ab2 --- /dev/null +++ b/src/TwigComponentGrid/composer.json @@ -0,0 +1,38 @@ +{ + "name": "sylius/twig-component-grid", + "description": "Additional Symfony UX components for your Sylius Grids", + "type": "library", + "require": { + "php": "^8.1", + "sylius/grid-bundle": "^1.13", + "sylius/twig-hooks": "^0.9", + "symfony/ux-live-component": "^2.20" + }, + "autoload": { + "psr-4": { + "Sylius\\TwigComponentGrid\\": "src/" + } + }, + "repositories": [ + { + "type": "path", + "url": "../**" + } + ], + "extra": { + "symfony": { + "require": "7.1.*" + }, + "branch-alias": { + "dev-main": "0.9-dev" + } + }, + "config": { + "allow-plugins": { + "symfony/runtime": true + }, + "sort-packages": true + }, + "minimum-stability": "dev", + "prefer-stable": true +} diff --git a/src/TwigComponentGrid/src/Symfony/SyliusTwigComponentGridBundle.php b/src/TwigComponentGrid/src/Symfony/SyliusTwigComponentGridBundle.php new file mode 100644 index 000000000..af015fa0c --- /dev/null +++ b/src/TwigComponentGrid/src/Symfony/SyliusTwigComponentGridBundle.php @@ -0,0 +1,29 @@ +path)) { + $reflected = new \ReflectionObject($this); + $this->path = \dirname($reflected->getFileName(), 3); + } + + return $this->path; + } +} diff --git a/src/TwigComponentGrid/src/Twig/Component/ComponentWithDataTableTrait.php b/src/TwigComponentGrid/src/Twig/Component/ComponentWithDataTableTrait.php new file mode 100644 index 000000000..197c1427d --- /dev/null +++ b/src/TwigComponentGrid/src/Twig/Component/ComponentWithDataTableTrait.php @@ -0,0 +1,161 @@ +|null */ + #[LiveProp(writable: true)] + public array|null $criteria; + + /** @var array|null */ + #[LiveProp(writable: true)] + public array|null $sorting = null; + + #[LiveProp(writable: true)] + public int|null $limit = null; + + #[LiveProp(writable: true)] + public array|null $columns = null; + + abstract protected function getGrid(): ?string; + + private GridViewFactoryInterface $gridViewFactory; + + private GridProviderInterface $gridProvider; + + public function getResources(): GridViewInterface + { + $gridDefinition = $this->gridProvider->get($this->getGrid()); + + $config = [ + 'page' => $this->page, + ]; + + if (null !== $this->criteria) { + $config['criteria'] = $this->criteria; + } + + if (null !== $this->sorting) { + $config['sorting'] = $this->sorting; + } + + if (null !== $this->limit) { + $config['limit'] = $this->limit; + } + + $gridView = $this->gridViewFactory->create( + $gridDefinition, + new Parameters($config), + ); + + $data = $gridView->getData(); + + if ($data instanceof PagerfantaInterface) { + if (null !== $this->limit) { + $data->setMaxPerPage($this->limit); + } + + $data->setCurrentPage($this->page); + + $this->emit('gridLoaded', [ + 'nbResults' => $data->getNbResults(), + ]); + } + + return $gridView; + } + + #[LiveAction] + public function sortBy(#[LiveArg] string $field, #[LiveArg] string $order): void + { + $this->sorting = [ + $field => $order, + ]; + } + + #[LiveAction] + public function updateLimit(#[LiveArg] int $limit): void + { + $this->page = 1; + $this->limit = $limit; + } + + #[LiveAction] + public function changePage(#[LiveArg] int $page): void + { + $this->page = $page; + } + + #[LiveAction] + public function previousPage(): void + { + --$this->page; + } + + #[LiveAction] + public function nextPage(): void + { + ++$this->page; + } + + /** @param array $criteria */ + #[LiveListener('criteriaSubmitted')] + public function onCriteriaSubmitted(#[LiveArg] array $criteria): void + { + $this->criteria = array_merge($this->criteria ?? [], $criteria); + } + + /** + * @internal + */ + #[Required] + public function setGridProvider( + #[Autowire(service: ChainProvider::class)] + GridProviderInterface $gridProvider, + ): void { + $this->gridProvider = $gridProvider; + } + + /** + * @internal + */ + #[Required] + public function setGridViewFactory(GridViewFactoryInterface $gridViewFactory): void + { + $this->gridViewFactory = $gridViewFactory; + } +} diff --git a/src/TwigComponentGrid/templates/components/default.html.twig b/src/TwigComponentGrid/templates/components/default.html.twig new file mode 100644 index 000000000..f04fcf550 --- /dev/null +++ b/src/TwigComponentGrid/templates/components/default.html.twig @@ -0,0 +1 @@ +{% extends template %} diff --git a/symfony.lock b/symfony.lock index 5ecd19b46..887a0227e 100644 --- a/symfony.lock +++ b/symfony.lock @@ -11,6 +11,15 @@ "ref": "64d8583af5ea57b7afa4aba4b159907f3a148b05" } }, + "doctrine/deprecations": { + "version": "1.1", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "1.0", + "ref": "87424683adc81d7dc305eefec1fced883084aab9" + } + }, "doctrine/doctrine-bundle": { "version": "2.12", "recipe": { @@ -142,6 +151,18 @@ ".env" ] }, + "symfony/form": { + "version": "7.2", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "7.2", + "ref": "7d86a6723f4a623f59e2bf966b6aad2fc461d36b" + }, + "files": [ + "config/packages/csrf.yaml" + ] + }, "symfony/framework-bundle": { "version": "5.4", "recipe": {