diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 1306f43..0f1c83d 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -1,4 +1,4 @@
-name: tableBundle
+name: PHP Symfony CI
on:
push:
@@ -6,21 +6,51 @@ on:
pull_request:
branches: [ main, develop ]
+env:
+ DATABASE_URL: mysql://root:root@127.0.01:3306/table_bundle
+ DATABASE_DRIVER: pdo_mysql
+
jobs:
- phpunit:
+ build:
runs-on: ubuntu-latest
+
+ strategy:
+ matrix:
+ php: [8.1, 8.2, 8.3]
+ symfony: [6.4.*, 7.0.*, 7.1.*]
+ exclude:
+ - php: 8.1
+ symfony: 7.1.*
+ - php: 8.1
+ symfony: 7.0.*
+
+ services:
+ mysql:
+ image: mysql:latest
+ env:
+ MYSQL_ROOT_PASSWORD: root
+ ports:
+ - 3306:3306
+ options: >-
+ --health-cmd "mysqladmin ping --silent"
+ --health-interval 10s
+ --health-timeout 5s
+ --health-retries 3
+
steps:
- - uses: shivammathur/setup-php@2cb9b829437ee246e9b3cac53555a39208ca6d28
+ - uses: actions/checkout@v4
+ - name: Setup PHP ${{ matrix.php }}
+ uses: shivammathur/setup-php@v2
with:
- php-version: '8.1'
- - uses: actions/checkout@v2
- - name: Install Dependencies
- run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist
- - name: Execute tests (Unit and Feature tests) via PHPUnit
+ php-version: ${{ matrix.php }}
+ tools: flex
+ - name: Download dependencies
env:
- DATABASE_URL: sqlite:///%kernel.project_dir%/var/app.db
+ SYMFONY_REQUIRE: ${{ matrix.symfony }}
+ uses: ramsey/composer-install@v2
+ - name: Run test suite on PHP ${{ matrix.php }} and Symfony ${{ matrix.symfony }}
run: vendor/bin/simple-phpunit
- - name: Check Code Styles
+ - name: Run ECS
run: vendor/bin/ecs
- - name: Check PHP Stan
+ - name: Run PHPStan
run: vendor/bin/phpstan analyse src tests
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 40ff2a2..53b4aec 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
# CHANGELOG
+## v1.2.0
+ - Removed symfony ^5.4 support
+ - Added Table option `OPT_SUB_TABLE_COLLAPSED`. This will collapse the sub table by default, you can also pass a callable to determine if the sub table should be collapsed or not
+ - Added footer columns to the table. This can be used to display totals or other information
+ - Fixed a bug where the table count would be of when using group by in the default query builder
+ - Date filters now use the `datetime_controller.js` provided by the core-bundle
+ - UX improvements
+
## v1.0.7
- More documentation and better styling of the documentation
- Added a new optional parameter to the `FilterTypeInterface::getValueField()` method to allow for more complex value fields.
diff --git a/composer.json b/composer.json
index c2b9b52..15ea69f 100644
--- a/composer.json
+++ b/composer.json
@@ -16,30 +16,30 @@
],
"require": {
"php": ">=8.1",
- "symfony/framework-bundle": "^5.4|^6.4|^7.0",
- "symfony/http-kernel": "^5.4|^6.4|^7.0",
- "symfony/validator": "^5.4|^6.4|^7.0",
+ "symfony/framework-bundle": "^6.4|^7.0",
+ "symfony/http-kernel": "^6.4|^7.0",
+ "symfony/validator": "^6.4|^7.0",
"araise/core-bundle": "^1.1",
"araise/search-bundle": "^3.1",
"phpoffice/phpspreadsheet": "^1.22|^2.0",
"symfony/stimulus-bundle": "^2.16"
},
"require-dev": {
- "symfony/phpunit-bridge": "^5.4|^6.4|^7.0",
- "symfony/config": "^5.4|^6.4|^7.0",
- "symfony/dependency-injection": "^5.4|^6.4|^7.0",
- "symfony/yaml": "^5.4|^6.4|^7.0",
+ "symfony/phpunit-bridge": "^6.4|^7.0",
+ "symfony/config": "^6.4|^7.0",
+ "symfony/dependency-injection": "^6.4|^7.0",
+ "symfony/yaml": "^6.4|^7.0",
"doctrine/doctrine-bundle": "^2.5.5",
- "doctrine/orm": "^2.11|^3.1",
+ "doctrine/orm": "^2.11",
"whatwedo/php-coding-standard": "^1.0",
- "zenstruck/foundry": "^1.16",
+ "zenstruck/foundry": "^v2.0.7",
"zenstruck/console-test": "^v1.1.0",
- "symfony/translation": "^5.4|^6.4|^7.0",
- "symfony/twig-bundle": "^5.4|^6.4|^7.0",
+ "symfony/translation": "^6.4|^7.0",
+ "symfony/twig-bundle": "^6.4|^7.0",
"gedmo/doctrine-extensions": "^3.15",
"symfony/webpack-encore-bundle": "^1.14|^2.1",
- "symfony/security-core": "^5.4|^6.4|^7.0",
- "symfony/security-bundle": "^5.4|^6.4|^7.0",
+ "symfony/security-core": "^6.4|^7.0",
+ "symfony/security-bundle": "^6.4|^7.0",
"phpstan/phpstan": "^1.5"
},
"autoload": {
diff --git a/docs/index.html b/docs/index.html
index 332c1f6..531cf50 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -79,7 +79,19 @@
diff --git a/docs/table-configuration.md b/docs/table-configuration.md
index b80157e..ed6d8c4 100644
--- a/docs/table-configuration.md
+++ b/docs/table-configuration.md
@@ -22,6 +22,32 @@ All Options are as constants in `Table` class.
- - `content_show_header`: Boolean, default: `true`
- - `content_show_entry_dropdown`: Boolean, default: `true`
- - `content_show_pagination_if_page_total_less_than_limit`: Boolean, default: `true`
+- `sub_table_collapsed`: Boolean or callable, default: `true`
+
+
+### Collapse Sub-Tables
+By default, sub-tables will be rendered collapsed.
+This can be changed by setting the `sub_table_collapsed` option to either `false` or pass a callable that returns a boolean.
+
+```php
+$table->setOption(Table::OPT_SUB_TABLE_COLLAPSED, false);
+```
+
+Note that if you pass a function, you will get the current row as an argument.
+You can use this to only expand certain rows:
+
+```php
+// Expand only users with an email
+$table->setOption(Table::OPT_SUB_TABLE_COLLAPSED, function(array|object $row) {
+ if(!$row instanceof User) {
+ return true;
+ }
+ if($row->getEmail() !== null) {
+ return false;
+ }
+ return true;
+});
+```
## Column Options
@@ -48,6 +74,33 @@ All Options are as constants in `Column` class.
- `formatter`: [Formatter](formatter.md)
- `sort_expression`: String, example: `'trainerGroup.name'`
+## Footer Columns
+In this example we would like to add a count of the content at the end of our table.
+We can to this by adding a footer column like so:
+
+```php
+$data= [
+ [
+ 'id' => 1,
+ ],
+ [
+ 'id' => 2,
+ ]
+];
+
+$table
+ ->setFooterData(['count' => count($data)])
+ ->addFooterColumn('totalLabel', null, [
+ Column::OPT_CALLABLE => fn (array $content) => 'Total',
+ ])
+ ->addFooterColumn('count', null, [
+ Column::OPT_CALLABLE => fn(array $content) => $content['count'],
+ ])
+;
+```
+
+Note that this is only a simple example. The data could easily be replaced with an array of entities for example.
+
## Action Columns
Action Columns are here to link to other pages (f.ex. link to edit or view).
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 6daad39..3425ad4 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -11,6 +11,7 @@
+
diff --git a/src/DataLoader/DoctrineDataLoader.php b/src/DataLoader/DoctrineDataLoader.php
index 3b5918c..3dfb07a 100644
--- a/src/DataLoader/DoctrineDataLoader.php
+++ b/src/DataLoader/DoctrineDataLoader.php
@@ -6,6 +6,7 @@
use araise\TableBundle\Extension\PaginationExtension;
use Doctrine\ORM\EntityManagerInterface;
+use Doctrine\ORM\Query\Parameter;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\QueryBuilder;
use Doctrine\ORM\Tools\Pagination\Paginator;
@@ -43,9 +44,24 @@ public function getResults(): iterable
/** @var QueryBuilder $qb */
$qb = (clone $this->options[self::OPT_QUERY_BUILDER]);
$qb->select('COUNT('.$qb->getRootAliases()[0].')');
- $qb->resetDQLPart('groupBy');
- $qb->resetDQLPart('orderBy');
- $this->paginationExtension->setTotalResults((int) $qb->getQuery()->getSingleScalarResult());
+
+ if (count($qb->getDQLPart('groupBy')) > 0) {
+ $sql = $qb->getQuery()->getSQL();
+ $params = array_values(
+ array_map(
+ static fn (Parameter $parameter) => $parameter->getValue(),
+ $qb->getParameters()->toArray()
+ )
+ );
+
+ $sql = sprintf('SELECT COUNT(*) as row_count FROM (%s) AS sub;', $sql);
+ $rsm = new ResultSetMapping();
+ $rsm->addScalarResult('row_count', 'row_count', 'integer');
+ $count = $this->entityManager->createNativeQuery($sql, $rsm)->setParameters($params)->getSingleScalarResult();
+ $this->paginationExtension->setTotalResults((int) $count);
+ } else {
+ $this->paginationExtension->setTotalResults($qb->getQuery()->getSingleScalarResult());
+ }
if ($this->getOption(self::OPT_SAVE_LAST_QUERY) && $this->requestStack->getCurrentRequest()?->hasSession()) {
/** @var QueryBuilder $qbSave */
diff --git a/src/DependencyInjection/araiseTableExtension.php b/src/DependencyInjection/araiseTableExtension.php
index ec56e73..3eb5479 100644
--- a/src/DependencyInjection/araiseTableExtension.php
+++ b/src/DependencyInjection/araiseTableExtension.php
@@ -6,9 +6,9 @@
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
-use Symfony\Component\HttpKernel\DependencyInjection\Extension;
class araiseTableExtension extends Extension implements PrependExtensionInterface
{
diff --git a/src/Exception/DataLoaderNotAvailableException.php b/src/Exception/DataLoaderNotAvailableException.php
index 3a7885d..dab50b0 100644
--- a/src/Exception/DataLoaderNotAvailableException.php
+++ b/src/Exception/DataLoaderNotAvailableException.php
@@ -31,7 +31,7 @@
class DataLoaderNotAvailableException extends \Exception
{
- public function __construct($message = '', $code = 0, \Throwable $previous = null)
+ public function __construct($message = '', $code = 0, ?\Throwable $previous = null)
{
if (! $message) {
$message = 'Table data loader is not availabe. Please set one by calling Table::setDataLoader($dataLoader)';
diff --git a/src/Exception/InvalidFilterAcronymException.php b/src/Exception/InvalidFilterAcronymException.php
index ee058ef..6a10e54 100644
--- a/src/Exception/InvalidFilterAcronymException.php
+++ b/src/Exception/InvalidFilterAcronymException.php
@@ -31,7 +31,7 @@
class InvalidFilterAcronymException extends \InvalidArgumentException
{
- public function __construct($acronym, $message = '', $code = 0, \Throwable $previous = null)
+ public function __construct($acronym, $message = '', $code = 0, ?\Throwable $previous = null)
{
if (! $message) {
$message = sprintf(
diff --git a/src/Factory/TableFactory.php b/src/Factory/TableFactory.php
index bf3231d..7a212fd 100644
--- a/src/Factory/TableFactory.php
+++ b/src/Factory/TableFactory.php
@@ -53,7 +53,7 @@ public function __construct(
) {
}
- public function create($identifier, string $dataLoader = null, $options = []): Table
+ public function create($identifier, ?string $dataLoader = null, $options = []): Table
{
if (! $dataLoader) {
$dataLoader = DoctrineDataLoader::class;
diff --git a/src/Filter/Type/DateFilterType.php b/src/Filter/Type/DateFilterType.php
index 1f9f319..5600eb4 100644
--- a/src/Filter/Type/DateFilterType.php
+++ b/src/Filter/Type/DateFilterType.php
@@ -5,17 +5,36 @@
namespace araise\TableBundle\Filter\Type;
use Doctrine\ORM\QueryBuilder;
+use Symfony\Component\HttpFoundation\RequestStack;
+use Symfony\UX\StimulusBundle\Helper\StimulusHelper;
class DateFilterType extends DatetimeFilterType
{
+ protected $locale;
+
+ public function __construct(
+ ?string $column = null,
+ array $joins = [],
+ protected ?RequestStack $requestStack = null
+ ) {
+ parent::__construct($column, $joins);
+ $this->locale = $requestStack->getMainRequest()?->getLocale() ?? 'en';
+ }
+
public function getValueField(?string $value = null, ?string $operator = null): string
{
$date = \DateTime::createFromFormat(static::getQueryDataFormat(), (string) $value) ?: new \DateTime();
$value = $date->format(static::getDateFormat());
-
+ $stimulusAttrs = (new StimulusHelper(null))->createStimulusAttributes();
+ $stimulusAttrs
+ ->addController('araise/core-bundle/datetime', [
+ 'lang' => $this->locale,
+ ])
+ ;
return sprintf(
- '',
- $operator !== static::CRITERIA_IS_EMPTY ? $value : ''
+ '',
+ $operator !== static::CRITERIA_IS_EMPTY ? $value : '',
+ $stimulusAttrs
);
}
diff --git a/src/Filter/Type/DatetimeFilterType.php b/src/Filter/Type/DatetimeFilterType.php
index c1f6c31..bb5872e 100644
--- a/src/Filter/Type/DatetimeFilterType.php
+++ b/src/Filter/Type/DatetimeFilterType.php
@@ -5,6 +5,8 @@
namespace araise\TableBundle\Filter\Type;
use Doctrine\ORM\QueryBuilder;
+use Symfony\Component\HttpFoundation\RequestStack;
+use Symfony\UX\StimulusBundle\Helper\StimulusHelper;
class DatetimeFilterType extends FilterType
{
@@ -20,6 +22,14 @@ class DatetimeFilterType extends FilterType
public const CRITERIA_IS_EMPTY = 'is_empty';
+ public function __construct(
+ ?string $column = null,
+ array $joins = [],
+ protected ?RequestStack $requestStack = null
+ ) {
+ parent::__construct($column, $joins);
+ }
+
public function getOperators(): array
{
return [
@@ -38,10 +48,17 @@ public function getValueField(?string $value = null, ?string $operator = null):
{
$date = \DateTime::createFromFormat(static::getQueryDataFormat(), (string) $value) ?: new \DateTime();
$value = $date->format(static::getDateFormat());
-
+ $locale = $this->requestStack->getMainRequest()?->getLocale();
+ $stimulusAttrs = (new StimulusHelper(null))->createStimulusAttributes();
+ $stimulusAttrs
+ ->addController('araise/core-bundle/datetime', [
+ 'lang' => $locale ?? 'en',
+ ])
+ ;
return sprintf(
- '',
- $operator !== static::CRITERIA_IS_EMPTY ? $value : ''
+ '',
+ $operator !== static::CRITERIA_IS_EMPTY ? $value : '',
+ $stimulusAttrs
);
}
diff --git a/src/Resources/assets/controllers/accordion_controller.js b/src/Resources/assets/controllers/accordion_controller.js
index 40afd17..eabe006 100644
--- a/src/Resources/assets/controllers/accordion_controller.js
+++ b/src/Resources/assets/controllers/accordion_controller.js
@@ -1,70 +1,145 @@
import { Controller } from '@hotwired/stimulus';
-import * as StickyThead from 'stickythead'
export default class extends Controller {
- static targets = ['content', 'arrow']
+ static targets = ['header', 'content', 'arrow']
+ static classes = ['arrowRotate', 'contentHidden']
- toggle(event) {
+ connect() {
+ /** @type {HTMLElement[]} */
+ const headerTargets = this.headerTargets;
+
+ headerTargets.forEach((header) => {
+ const isOpenDefault = header.getAttribute('aria-expanded') === 'true';
+ const contents = this.getAccordionContents(header);
+ const arrow = this.closestChildTarget(header, 'arrow');
+
+ if(!arrow) {
+ return;
+ }
+
+ // Open all accordions on load. This can be definied in araise (OPT_SUB_TABLE_COLLAPSED)
+ if (isOpenDefault) {
+ this.applyOpenState({arrow, contents});
+ }
+ });
+ }
+
+ /**
+ * @param {Event} event
+ */
+ toggle(event)
+ {
const current = event.currentTarget;
- const arrow = current.querySelector('[data-araise--table-bundle--accordion-target=arrow]');
- const isOpen = current.dataset.ariaExpanded == 'true';
- const nextSiblings = this.nextUntil(current, '[data-action="click->araise--table-bundle--accordion#toggle"]');
+ const header = this.closestTarget(current, 'header');
+ const arrow = this.closestTarget(current, 'arrow');
+ const contents = this.getAccordionContents(header);
- if(isOpen) {
- arrow.classList.remove('rotate-90');
- current.dataset.ariaExpanded = 'false';
+ const isOpen = header.getAttribute('aria-expanded') === 'true';
- nextSiblings.forEach((sibling) => {
- sibling.classList.add('hidden');
- });
+ // Open the current accordion if it wasn't open before (else we toggle it)
+ if (!isOpen) {
+ this.applyOpenState({arrow, contents, header});
} else {
- arrow.classList.add('rotate-90');
- current.dataset.ariaExpanded = 'true';
+ this.applyCloseState({arrow, contents, header});
+ }
+ }
+
+ /**
+ * @param {HTMLElement[]} elements
+ */
+ applyOpenState(elements) {
+ const {arrow, contents, header} = elements;
- nextSiblings.forEach((sibling) => {
- sibling.classList.remove('hidden');
- });
+ /** @type {string[]} */
+ const arrowRotateClasses = this.arrowRotateClasses;
+
+ /** @type {string[]} */
+ const contentHiddenClasses = this.contentHiddenClasses;
+
+ if(header) {
+ header.setAttribute('aria-expanded', 'true');
}
+ arrow.classList.add(...arrowRotateClasses);
+ contents.forEach((item) => {
+ item.classList.remove(...contentHiddenClasses);
+ });
}
- /*!
- * Get all following siblings of each element up to but not including the element matched by the selector
- * (c) 2017 Chris Ferdinandi, MIT License, https://gomakethings.com
- * @param {Node} elem The element
- * @param {String} selector The selector to stop at
- * @param {String} filter The selector to match siblings against [optional]
- * @return {Array} The siblings
+ /**
+ * @param {HTMLElement[]} elements
*/
- nextUntil(elem, selector, filter) {
+ applyCloseState(elements) {
+ const {arrow, contents, header} = elements;
- // Setup siblings array
- var siblings = [];
+ /** @type {string[]} */
+ const arrowRotateClasses = this.arrowRotateClasses;
- // Get the next sibling element
- elem = elem.nextElementSibling;
+ /** @type {string[]} */
+ const contentHiddenClasses = this.contentHiddenClasses;
- // As long as a sibling exists
- while (elem) {
+ if(header) {
+ header.setAttribute('aria-expanded', 'false');
+ }
+ arrow.classList.remove(...arrowRotateClasses);
+ contents.forEach((item) => {
+ item.classList.add(...contentHiddenClasses);
+ });
+ }
- // If we've reached our match, bail
- if (elem.matches(selector)) break;
+ /**
+ * @param {HTMLElement} header
+ * @return {HTMLElement[]}
+ *
+ * The content block can output subtables. Those can be defined in the definition of araise.
+ * Multiple subtables can be defined, so we want to toggle them in groups together.
+ * Those are children of the header and not wrapped inside a div, that's why we return an array of elements.
+ */
+ getAccordionContents(header) {
+ return this.getNextSiblingsWithClass(header, 'whatwedo_table-subtable');
+ }
- // If filtering by a selector, check if the sibling matches
- if (filter && !elem.matches(filter)) {
- elem = elem.nextElementSibling;
- continue;
- }
+ /**
+ * @param {HTMLElement} element
+ * @param {string} target
+ */
+ closestTarget(element, target)
+ {
+ const childElement = element.closest(`[data-araise--table-bundle--accordion-target='${target}']`);
+ if(childElement) {
+ return childElement;
+ }
- // Otherwise, push it to the siblings array
- siblings.push(elem);
+ return element.parentElement;
+ }
- // Get the next sibling element
- elem = elem.nextElementSibling;
+ /**
+ * @param {HTMLElement|null} element
+ * @param {string} target
+ */
+ closestChildTarget(element, target) {
+ const childElement = element.querySelector(`[data-araise--table-bundle--accordion-target='${target}']`);
+ if(childElement) {
+ return childElement;
}
- return siblings;
+ return null;
+ }
+
+ /**
+ * @param {HTMLElement} element
+ * @param {string} className
+ */
+ getNextSiblingsWithClass(element, className) {
+ let siblings = [];
+ let next = element.nextElementSibling;
- };
+ while (next && next.classList.contains(className)) {
+ siblings.push(next);
+ next = next.nextElementSibling;
+ }
+
+ return siblings;
+ }
}
diff --git a/src/Resources/assets/controllers/table_select_controller.js b/src/Resources/assets/controllers/table_select_controller.js
index d9e6e5e..c372f66 100644
--- a/src/Resources/assets/controllers/table_select_controller.js
+++ b/src/Resources/assets/controllers/table_select_controller.js
@@ -3,10 +3,11 @@ import 'regenerator-runtime/runtime'
export default class extends Controller {
- static targets = ["ids", "selector", "checkAll", "unCheckAll", "selectedCount"]
+ static targets = ['ids', 'selector', 'checkAll', 'unCheckAll', 'selectedCount']
static values = {
footSelectedTemplate: String
}
+ static classes = ['hideCount']
connect() {
if (!this.hasIdsTarget) {
@@ -18,6 +19,9 @@ export default class extends Controller {
});
}
+ /**
+ * @param {Event} event
+ */
selectId(event) {
if (!event.target.dataset.entityId) {
return;
@@ -46,8 +50,8 @@ export default class extends Controller {
this.addId(selector.dataset.entityId);
selector.checked = true;
});
- this.checkAllTarget.classList.add('hidden');
- this.unCheckAllTarget.classList.remove('hidden');
+ this.checkAllTarget.classList.add(this.hideCountClasses);
+ this.unCheckAllTarget.classList.remove(this.hideCountClasses);
}
unCheckAll() {
@@ -55,15 +59,21 @@ export default class extends Controller {
this.removeId(selector.dataset.entityId);
selector.checked = false;
});
- this.checkAllTarget.classList.remove('hidden');
- this.unCheckAllTarget.classList.add('hidden');
+ this.checkAllTarget.classList.remove(this.hideCountClasses);
+ this.unCheckAllTarget.classList.add(this.hideCountClasses);
}
+ /**
+ * @param {string} id
+ */
hasId(id) {
let ids = this.getIds();
return ids.includes(id)
}
+ /**
+ * @param {string} id
+ */
addId(id) {
let ids = this.getIds();
ids.push(id);
@@ -71,6 +81,9 @@ export default class extends Controller {
this.updateSelectedCount();
}
+ /**
+ * @param {string} id
+ */
removeId(id) {
let ids = this.getIds();
ids = ids.filter(function (value, index, arr) {
@@ -79,21 +92,23 @@ export default class extends Controller {
this.idsTarget.value = JSON.stringify(ids);
this.updateSelectedCount();
if (ids.length == 0) {
- this.checkAllTarget.classList.remove('hidden');
- this.unCheckAllTarget.classList.add('hidden');
+ this.checkAllTarget.classList.remove(this.hideCountClasses);
+ this.unCheckAllTarget.classList.add(this.hideCountClasses);
}
}
updateSelectedCount() {
const count = this.getIds().length;
- if (count === 0) {
- this.selectedCountTarget.classList.add('hidden');
- return;
- }
+ if(this.hasSelectedCountTarget) {
+ if (count === 0) {
+ this.selectedCountTarget.classList.add(this.hideCountClasses);
+ return;
+ }
- this.selectedCountTarget.classList.remove('hidden');
- this.selectedCountTarget.innerHTML = this.footSelectedTemplateValue.replace('{count}', count);
+ this.selectedCountTarget.classList.remove(this.hideCountClasses);
+ this.selectedCountTarget.innerHTML = this.footSelectedTemplateValue.replace('{count}', count);
+ }
}
syncSelectedIds() {
diff --git a/src/Resources/assets/styles/_tailwind.scss b/src/Resources/assets/styles/_tailwind.scss
index 590ddca..f3391a5 100644
--- a/src/Resources/assets/styles/_tailwind.scss
+++ b/src/Resources/assets/styles/_tailwind.scss
@@ -41,6 +41,10 @@
@apply border-none;
}
+ .whatwedo_table-wrapper {
+ @apply border-solid border-b-0 border-neutral-200;
+ }
+
.whatwedo_table-head {
@apply border-b border-neutral-200;
@@ -56,4 +60,4 @@
.whatwedo_table-row td:not(.whatwedo_table-actions) svg {
margin: auto;
-}
\ No newline at end of file
+}
diff --git a/src/Resources/translations/messages.de.yaml b/src/Resources/translations/messages.de.yaml
index 640f5a3..4ec2ab1 100644
--- a/src/Resources/translations/messages.de.yaml
+++ b/src/Resources/translations/messages.de.yaml
@@ -1,5 +1,6 @@
araise_table:
download:
+ tooltip: Export
info: Was möchten Sie herunterladen?
choices: Export wählen
all: Alle Seiten
@@ -41,6 +42,7 @@ araise_table:
next_page: nächste Seite
last_page: letzte Seite
filter:
+ tooltip: Filter
show_element_when: Zeige Elemente wenn
or: oder
and: und
diff --git a/src/Resources/translations/messages.en.yaml b/src/Resources/translations/messages.en.yaml
index 8c7661b..4eee469 100644
--- a/src/Resources/translations/messages.en.yaml
+++ b/src/Resources/translations/messages.en.yaml
@@ -23,6 +23,7 @@ araise_table:
options: Options
no_elements: No elements available
search:
+ title: Search
filter: Filter
remove_search: Remove search
placeholder: Search term...
diff --git a/src/Resources/views/tailwind_2/_header.html.twig b/src/Resources/views/tailwind_2/_header.html.twig
index f011f68..cd14d7b 100644
--- a/src/Resources/views/tailwind_2/_header.html.twig
+++ b/src/Resources/views/tailwind_2/_header.html.twig
@@ -1,7 +1,15 @@
{# Table Header #}
-{% if table.option('title') or view is defined and view.definition.hasCapability(constant('araise\\CrudBundle\\Enums\\Page::EXPORT')) or table.filterExtension and table.filterExtension.filters|length > 0 or table.searchExtension and table.option('searchable') %}
-
-
+{% if
+ table.option('title')
+ or (view is defined and table.option('definition').hasCapability(constant('araise\\CrudBundle\\Enums\\Page::EXPORT')))
+ or (table.filterExtension and table.filterExtension.filters|length > 0)
+ or (table.searchExtension and table.option('searchable'))
+ or (content.createUrl(view.data)
+ and (content.addVoterAttribute is null or is_granted(content.addVoterAttribute, view.data))
+ and view.route in content.option(constant('araise\\CrudBundle\\Content\\RelationContent::OPT_ADD_VISIBILITY'))
+ )
+%}
+
{% if table.option('title') %}
@@ -9,14 +17,15 @@
{% endif %}
-
+
{% if table.exporters|length > 0 and table.option('definition') and table.option('definition').hasCapability(constant('araise\\CrudBundle\\Enums\\Page::EXPORT')) %}
{% for label,queryParameters in {'araise_table.page' : app.request.query.all, 'araise_table.all' : {'all':1}|merge(app.request.query.all)} %}
+ {% if block is defined and content is defined %}
+ {% set queryParameters = queryParameters|merge({
+ 'definition': view.definition.alias,
+ 'block': block.acronym,
+ 'content': content.acronym,
+ 'entityId': app.request.attributes.get('id'),
+ }) %}
+ {% endif %}
1 %} {{ stimulus_target('araise/table-bundle/exporter', 'link') }} {% endif %}
>
@@ -66,6 +84,10 @@
{% endif %}
+ {% if block is defined and content is defined %}
+ {{ _self.add_button(content, view, isOnShow) }}
+ {% endif %}
+
{% if table.filterExtension and table.filterExtension.filters|length > 0 %}
@@ -91,17 +114,85 @@
-
{% endif %}
-
{% endif %}
+
+{% macro add_button(content, view, isOnShow) %}
+ {% set createUrl = content.createUrl(view.data) %}
+ {% if createUrl
+ and (content.addVoterAttribute is null or is_granted(content.addVoterAttribute, view.data))
+ and view.route in content.option(constant('araise\\CrudBundle\\Content\\RelationContent::OPT_ADD_VISIBILITY'))
+ %}
+
+
+ {% set additionalClass = (not isOnShow) ? (content.option('attr')['class'] ?? '') : '' %}
+ {% set additionalAttributes = (not isOnShow) ? attr|map((value, attr) => "#{attr}=\"#{value}\"")|join(' ') : '' %}
+
+
- {# This element is to trick the browser into centering the modal contents. #}
+ {# This element is to trick the browser into centering the modal contents. Code is coming from Tailwind UI #}