diff --git a/docs/src/docs/features/asynchronicity.md b/docs/src/docs/features/asynchronicity.md index 33b9b2f7..c05a037f 100644 --- a/docs/src/docs/features/asynchronicity.md +++ b/docs/src/docs/features/asynchronicity.md @@ -85,6 +85,36 @@ Notes: - Turbo sends the Turbo-Frame header with the frame id; the bundle reads it for you. You don't need to access headers directly. - The trait requires Twig to be available in your controller service (it is auto-wired by Symfony via the #[Required] setter). +## Asynchronous Data Table Loading + +You can enable asynchronous loading for your data tables using the `async` option. This feature is especially useful for tables with slow data sources or when displaying multiple tables on a single page. + +### Enabling Asynchronous Loading + +To enable asynchronous loading, set the async option to true in your data table type: +```php +class ProductDataTableType extends AbstractDataTableType +{ + // ... + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'async' => true, + ]); + } +} +``` + +With this option enabled, the data table will not load its content immediately when the page is rendered. Instead, it will trigger a backend request to fetch and display the table data. + +### How It Works + +- When 'async' is enabled, the table's initial HTML does not include its data rows. +- The table content is loaded asynchronously via a backend call after the page loads. +- If the data table is not visible in the user's viewport, its content is not loaded until the user scrolls to it. This optimizes performance, especially on pages with multiple or heavy tables. +- If you need the table to load immediately regardless of visibility, keep async set to false (the default). + + ## Prefetching diff --git a/src/DataTable.php b/src/DataTable.php index 24d4a1a6..9fc402f5 100755 --- a/src/DataTable.php +++ b/src/DataTable.php @@ -38,6 +38,7 @@ use Kreyu\Bundle\DataTableBundle\Personalization\Form\Type\PersonalizationDataType; use Kreyu\Bundle\DataTableBundle\Personalization\PersonalizationData; use Kreyu\Bundle\DataTableBundle\Query\ProxyQueryInterface; +use Kreyu\Bundle\DataTableBundle\Query\ResultSet; use Kreyu\Bundle\DataTableBundle\Query\ResultSetInterface; use Kreyu\Bundle\DataTableBundle\Sorting\SortingData; use Symfony\Component\Form\FormBuilderInterface; @@ -670,7 +671,16 @@ public function getPagination(): PaginationInterface private function getResultSet(): ResultSetInterface { - return $this->resultSet ??= $this->query->getResult(); + if ( + !$this->config->isAsync() + || $this->isRequestFromTurboFrame() + ) { + return $this->resultSet ??= $this->query->getResult(); + } + + // In this case, we don't want to fetch the results immediately, + // but rather return an empty result set that will be filled later through AJAX. + return new ResultSet(new \ArrayIterator([]), 0, 0); } private function createPagination(): PaginationInterface diff --git a/src/DataTableConfigBuilder.php b/src/DataTableConfigBuilder.php index 4e65af54..3178d7b3 100755 --- a/src/DataTableConfigBuilder.php +++ b/src/DataTableConfigBuilder.php @@ -876,4 +876,9 @@ private function createBuilderLockedException(): BadMethodCallException { return new BadMethodCallException('DataTableConfigBuilder methods cannot be accessed anymore once the builder is turned into a DataTableConfigInterface instance.'); } + + public function isAsync(): bool + { + return $this->options['async']; + } } diff --git a/src/DataTableConfigInterface.php b/src/DataTableConfigInterface.php index c78eba6a..ffebcbce 100755 --- a/src/DataTableConfigInterface.php +++ b/src/DataTableConfigInterface.php @@ -134,4 +134,6 @@ public function getFiltrationParameterName(): string; public function getPersonalizationParameterName(): string; public function getExportParameterName(): string; + + public function isAsync(): bool; } diff --git a/src/Resources/views/themes/base.html.twig b/src/Resources/views/themes/base.html.twig index 53dcf297..d4faedc9 100755 --- a/src/Resources/views/themes/base.html.twig +++ b/src/Resources/views/themes/base.html.twig @@ -9,19 +9,27 @@ {% set stimulus_controllers = stimulus_controllers|merge(['kreyu--data-table-bundle--batch']) %} {% endif %} - -
- {{ block('action_bar', theme) }} - {{ block('table', theme) }} + {% if data_table.vars.is_async and not data_table.vars.is_request_from_turbo_frame %} + +
+ {{ 'Loading'|trans({}, 'KreyuDataTable') }} +
+
+ {% else %} + +
+ {{ block('action_bar', theme) }} + {{ block('table', theme) }} - {% if pagination_enabled %} - {{ data_table_pagination(pagination) }} - {% endif %} -
-
+ {% if pagination_enabled %} + {{ data_table_pagination(pagination) }} + {% endif %} +
+
+ {% endif %} {% endblock %} {% block kreyu_data_table_form_aware %} diff --git a/src/Type/DataTableType.php b/src/Type/DataTableType.php index e76e935a..960cbc67 100755 --- a/src/Type/DataTableType.php +++ b/src/Type/DataTableType.php @@ -107,6 +107,8 @@ public function buildView(DataTableView $view, DataTableInterface $dataTable, ar 'sorting_clearable' => $dataTable->getConfig()->isSortingClearable(), 'has_batch_actions' => !empty($dataTable->getBatchActions()), 'per_page_choices' => $options['per_page_choices'], + 'is_request_from_turbo_frame' => $dataTable->isRequestFromTurboFrame(), + 'is_async' => $dataTable->getConfig()->isAsync(), ]); $view->headerRow = $this->createHeaderRowView($view, $dataTable, $visibleColumns); @@ -186,6 +188,7 @@ public function configureOptions(OptionsResolver $resolver): void 'personalization_form_factory' => $this->defaults['personalization']['form_factory'] ?? null, 'exporting_enabled' => $this->defaults['exporting']['enabled'] ?? false, 'exporting_form_factory' => $this->defaults['exporting']['form_factory'] ?? null, + 'async' => $this->defaults['async'] ?? false, ]) ->setAllowedTypes('title', ['null', 'string', TranslatableInterface::class]) ->setAllowedTypes('title_translation_parameters', ['array']) @@ -217,6 +220,7 @@ public function configureOptions(OptionsResolver $resolver): void ->setAllowedTypes('personalization_form_factory', ['null', FormFactoryInterface::class]) ->setAllowedTypes('exporting_enabled', 'bool') ->setAllowedTypes('exporting_form_factory', ['null', FormFactoryInterface::class]) + ->setAllowedTypes('async', ['bool']) ; }