Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
Kreyu committed Feb 9, 2025
1 parent ce52223 commit 75b284e
Show file tree
Hide file tree
Showing 11 changed files with 359 additions and 91 deletions.
129 changes: 129 additions & 0 deletions docs/src/docs/features/theming.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,74 @@ When creating your own theme, you can either create a template that extends one

Remember that in the second case, you cannot call the `parent()` function in the block.

### Rendering HTML attributes

The `@KreyuDataTable/themes/base.html.twig` theme provides `attributes` macro can be used to render HTML attributes from an array:

```twig
{% from '@KreyuDataTable/themes/base.html.twig' import attributes %}
<span{{ attributes({ id: 'foo', class: 'text-center' }) }}></span>
{# Rendered as: #}
<span id="foo" class="text-center"></span>
```

If you pass an empty array to the `attributes` macro, it will render an empty string:

```twig
<span{{ attributes([]) }}></span>
{# Rendered as: #}
<span></span>
```

The `attributes` macro will start with a whitespace if at least one attribute is passed.
Because of that, you shouldn't add the whitespace manually:

```twig
{% from '@KreyuDataTable/themes/base.html.twig' import attributes %}
<span {{ attributes({ id: 'foo', class: 'text-center' }) }}></span>
{# Rendered as: #}
<span id="foo" class="text-center"></span>
```

Notice the double whitespace before the `id` attribute.

In some cases you may want to merge the class attribute with some defaults. To do so, use the Twig `merge` filter:

```twig
{% set attr = { class: 'btn-sm' } #}
{% set attr = attr|merge({ class: 'btn btn-primary ' ~ attr.class }) %}
{# Now "attr" equals to: { class: 'btn btn-primary btn-sm' } #}
```

You can also append the class, instead of prepending:

```twig
{% set attr = { class: 'btn' } #}
{% set attr = attr|merge({ class: (attr.class|default(''))|trim ~ ' btn-primary' }) %}
{# Now "attr" equals to: { class: 'btn btn-primary' } #}
```

If you have no idea whether the `attr` variable contains any `class`, you can use the Twig `default` and `trim` filters to prevent unnecessary whitespace:

```twig
{% set attr = {} #}
{% set attr = attr|merge({ class: ('btn btn-primary ' ~ attr.class|default(''))|trim }) %}
{# Now "attr" equals to: { class: 'btn btn-primary' } #}
```

### Rendering blocks with hierarchy

When creating custom themes, you may find the `data_table_theme_block` Twig function useful.
For example, let's assume the data table has two themes:

Expand Down Expand Up @@ -248,3 +316,64 @@ However, if you use the `data_table_theme_block` instead of the `block`:

In this case, the `column_header` will render "Label B". The `data_table_theme_block` function will iterate
through the data table themes in reverse and render the first block that matches the name.

In some cases, the `attr` variable may be incorrectly rendered on each nested element. For example:

```twig
{% block column_header %}
{% set attr = { class: 'text-center' } %}
<span{{ _self.attributes(attr) }}>
{{ data_table_theme_block(data_table, 'column_label') }}
</span>
{% endblock %}
{% block column_label %}
{# When rendered from "column_header", the variable "attr" equals { class: 'text-center' } #}
<span{{ _self.attributes(attr) }}>
Label
</span>
{% endblock %}
```

Rendering the `column_header` block will result in the following HTML:

```
<span class="text-center">
<span class="text-center">
Label
</span>
</span>
```

This is because the `data_table_theme_block` passes the context, so `attr` will be rendered twice.
In some cases this is fine, but sometimes you may want to prevent this behavior.
To do so, you can pass the `resetAttr` argument set to `true` to the `data_table_theme_block` function.
This will reset the `attr` to an empty array:

```twig
{% block column_header %}
{% set attr = { class: 'text-center' } %}
<span{{ _self.attributes(attr) }}>
{{ data_table_theme_block(data_table, 'column_label', resetAttr = true) }}
</span>
{% endblock %}
{% block column_label %}
{# When rendered from "column_header", the variable "attr" now equals [] #}
<span{{ _self.attributes(attr) }}>
Label
</span>
{% endblock %}
```

Now, rendering the `column_header` block will result in the following HTML:

```twig
<span class="text-center">
<span>
Label
</span>
</span>
```
12 changes: 11 additions & 1 deletion src/Action/Type/Dropdown/DropdownActionType.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,24 @@ public function buildView(ActionView $view, ActionInterface $action, array $opti
$itemActions[] = $itemAction->createView($view->parent);
}

$view->vars['actions'] = $itemActions;
$view->vars = array_replace($view->vars, [
'actions' => $itemActions,
'with_caret' => $options['with_caret'],
]);
}

public function configureOptions(OptionsResolver $resolver): void
{
$resolver->define('actions')
->allowedTypes(ActionBuilderInterface::class.'[]', 'callable')
->required()
->info('The actions to display in the dropdown.')
;

$resolver->define('with_caret')
->default(true)
->allowedTypes('bool')
->info('Whether to display a caret next to the dropdown label.')
;
}
}
2 changes: 1 addition & 1 deletion src/Action/Type/FormActionType.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public function buildView(ActionView $view, ActionInterface $action, array $opti

$method = $htmlFriendlyMethod = strtoupper($options['method']);

if ('GET' !== $method) {
if (!in_array($method, ['GET', 'POST'])) {
$htmlFriendlyMethod = 'POST';
}

Expand Down
99 changes: 64 additions & 35 deletions src/Resources/views/themes/base.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -395,11 +395,13 @@
</button>
{% endblock %}

{# @see Kreyu\Bundle\DataTableBundle\Filter\Form\Type\DateRangeType #}
{% block kreyu_data_table_date_range_widget %}
{{ form_widget(form.from) }}
{{ form_widget(form.to) }}
{% endblock %}

{# @see Kreyu\Bundle\DataTableBundle\Column\Type\ColumnType #}
{% block column_header %}
{% set label_attr = label_attr|default({}) %}

Expand Down Expand Up @@ -442,6 +444,7 @@
{% endif %}
{% endblock %}

{# @see Kreyu\Bundle\DataTableBundle\Column\Type\ColumnType #}
{% block column_header_label %}
{% if translation_domain is not same as false %}
<span>{{- label|trans(translation_parameters, translation_domain) -}}</span>
Expand Down Expand Up @@ -576,7 +579,7 @@

{# @see Kreyu\Bundle\DataTableBundle\Column\Type\CheckboxColumnType #}
{% block column_checkbox_header -%}
<th {{ block('attributes') }}>
<th{{ _self.attributes(attr) }}>
{% set input_attr = {
'type': 'checkbox',
'aria-label': 'Select all checkbox',
Expand All @@ -585,7 +588,7 @@
'data-action': 'input->kreyu--data-table-bundle--batch#selectAll'
}|merge(input_attr|default({})) %}

<input {% with { attr: input_attr } %}{{ block('attributes') }}{% endwith %}>
<input{{ _self.attributes(input_attr) }}>
</th>
{%- endblock %}

Expand All @@ -601,95 +604,121 @@
'data-action': 'input->kreyu--data-table-bundle--batch#selectRow'
}|merge(input_attr|default({})) %}

<input {% with { attr: input_attr } %}{{ block('attributes') }}{% endwith %}>
<input{{ _self.attributes(input_attr) }}>
{%- endblock %}

{# Action type templates #}

{% block action_control_icon %}{% endblock %}

{% block action_control %}
{% if icon %}{{ data_table_theme_block(data_table, 'icon') }}{% endif %}
{# @see Kreyu\Bundle\DataTableBundle\Action\Type\ActionType #}
{% block action_control -%}
{%- if icon %}{{ data_table_theme_block(data_table, 'icon') }}{% endif -%}
{{- label|trans(translation_parameters, translation_domain) -}}
{% endblock %}
{%- endblock %}

{# @see Kreyu\Bundle\DataTableBundle\Action\Type\LinkActionType #}
{% block action_link_control %}
{% set attr = { href, target }|filter(v => v != null)|merge(attr|default({})) %}
{% set attr = { href, target }|filter(v => v != null)|merge(attr) %}
{% set tag = tag ?? 'a' %}

{% if batch %}
{% set attr = { 'data-kreyu--data-table-bundle--batch-target': 'identifierHolder' }|merge(attr) %}
{% endif %}

<a {% with { attr } %}{{- block('attributes') -}}{% endwith %}>
{% with { attr: {} } %}{{- block('action_control', theme, _context) -}}{% endwith %}
</a>
<{{ tag }}{{ _self.attributes(attr) }}>
{{- data_table_theme_block(data_table, 'action_control', resetAttr = true) -}}
</{{ tag }}>
{% endblock %}

{# @see Kreyu\Bundle\DataTableBundle\Action\Type\ButtonActionType #}
{% block action_button_control %}
{% set attr = { href, target }|filter(v => v != null)|merge(attr|default({})) %}
{% set attr = { href, target }|filter(v => v != null)|merge(attr) %}
{% set tag = tag ?? 'a' %}

{% if batch %}
{% set attr = { 'data-kreyu--data-table-bundle--batch-target': 'identifierHolder' }|merge(attr) %}
{% endif %}

<a {% with { attr } %}{{- block('attributes') -}}{% endwith %}>
{% with { attr: {} } %}{{- block('action_control', theme, _context) -}}{% endwith %}
</a>
{# Button action should work like a link action styled to look like a button #}

<{{ tag }}{{ _self.attributes(attr) }}>
{{- data_table_theme_block(data_table, 'action_control', resetAttr = true) -}}
</{{ tag }}>
{% endblock %}

{# @see Kreyu\Bundle\DataTableBundle\Action\Type\FormActionType #}
{% block action_form_control %}
{% set attr = { action, method: html_friendly_method }|merge(attr|default({})) %}
{# @var string form_id - Unique identifier of the form #}
{# @var string action - URL that the form will be submitted to #}
{# @var string method - Form method, e.g. GET, POST, PATCH, PUT, DELETE #}
{# @var string html_friendly_method - HTML-friendly form method, so only GET or POST #}

{% set attr = { id: form_id, action, method: html_friendly_method }|merge(attr) %}

{% if batch %}
{% set attr = { 'data-kreyu--data-table-bundle--batch-target': 'identifierHolder' }|merge(attr) %}
{% endif %}

<form id="{{ form_id }}" {{- block('attributes') -}}>
{# Reset attributes to prevent bubbling #}
{% set attr = {} %}
{% set button_tag = button_tag ?? 'button' %}
{% set button_attr = { type: 'submit' }|merge(button_attr) %}

<input type="hidden" name="_method" value="{{ method }}"/>
{# Form action should work like a button action wrapped in a form to allow methods other than GET #}

{% set button_tag = button_tag|default('button') %}
<form{{ _self.attributes(attr) }}>
{% if method != html_friendly_method -%}
<input type="hidden" name="_method" value="{{ method }}" />
{%- endif -%}

<{{ button_tag }} {% with { attr: { type: 'submit' }|merge(button_attr) } %}{{- block('attributes') -}}{% endwith %}>
{{- block('action_control', theme, _context) -}}
<{{ button_tag }}{{ _self.attributes(button_attr) }}>
{{- data_table_theme_block(data_table, 'action_control', resetAttr = true) -}}
</{{ button_tag }}>
</form>
{% endblock %}

{# @see Kreyu\Bundle\DataTableBundle\Action\Type\ModalActionType #}
{% block action_modal_control %}
{% set attr = { href }|filter(v => v != null)|merge(attr|default({})) %}
{# @var string href - URL that will respond with the modal contents #}

{% set attr = { href }|filter(v => v != null)|merge(attr) %}

{% if batch %}
{% set attr = { 'data-kreyu--data-table-bundle--batch-target': 'identifierHolder' }|merge(attr) %}
{% endif %}

<button {% with { attr } %}{{- block('attributes') -}}{% endwith %}>
{% with { attr: {} } %}{{- block('action_control', theme, _context) -}}{% endwith %}
</button>
{% set tag = tag ?? 'button' %}

{# Themes that extend base theme should provide their own implementation of modal #}

<{{ tag }}{{ _self.attributes(attr) }}>
{{- data_table_theme_block(data_table, 'action_control', resetAttr = true) -}}
</{{ tag }}>
{% endblock %}

{# @see Kreyu\Bundle\DataTableBundle\Action\Type\Dropdown\DropdownActionType #}
{% block action_dropdown_control %}
{# Themes that extend base theme should provide their own implementation of dropdown #}
<button>{{- block('action_control', theme, _context) -}}</button>
<button>
{{- data_table_theme_block(data_table, 'action_control') -}}
</button>
<ul>
{% for action in actions %}
<li>{{ data_table_action(action) }}</li>
{% endfor %}
</ul>
{% endblock %}

{# @see Kreyu\Bundle\DataTableBundle\Action\Type\Dropdown\LinkDropdownItemActionType #}
{% block action_link_dropdown_item_control %}
{% set attr = { href, target }|filter(v => v != null)|merge(attr|default({})) %}

{% if not confirmation and batch %}
{% set attr = { 'data-kreyu--data-table-bundle--batch-target': 'identifierHolder' }|merge(attr) %}
{% endif %}

<a {% with { attr } %}{{- block('attributes') -}}{% endwith %}>
{% with { attr: {} } %}{{- block('action_control', theme, _context) -}}{% endwith %}
</a>
{% set tag = tag ?? 'a' %}

<{{ tag }}{{ _self.attributes(attr) }}>
{{- data_table_theme_block(data_table, 'action_control', resetAttr = true) -}}
</{{ tag }}>
{% endblock %}

{% block sort_arrow_none %}{% endblock %}
Expand All @@ -699,11 +728,11 @@
{% block sort_arrow_desc %}↓{% endblock %}

{% block attributes -%}
{% for key, value in attr|default({}) %}{{ loop.first ? ' ' }}{{ key }}="{{ value }}"{% endfor %}
{%- for key, value in attr|default({}) %}{{ loop.first ? ' ' }}{{ key }}="{{ value }}"{% endfor -%}
{%- endblock %}

{% macro attributes(attributes) -%}
{% for key, value in attributes|default({}) %}{{ loop.first ? ' ' }}{{ key }}="{{ value }}"{% endfor %}
{% macro attributes(attr) -%}
{{- block('attributes') -}}
{%- endmacro %}

{# Transforms given array to form inputs. Supports nested arrays. #}
Expand Down
Loading

0 comments on commit 75b284e

Please sign in to comment.