Skip to content

Commit

Permalink
bug #5989 Add more tests about field labels and fix some issues about…
Browse files Browse the repository at this point in the history
… labels (javiereguiluz)

This PR was squashed before being merged into the 4.x branch.

Discussion
----------

Add more tests about field labels and fix some issues about labels

Thanks to the new tests I found some issues, like tabs not allowing HTML contents by default in labels.

Commits
-------

b835bbf Add more tests about field labels and fix some issues about labels
  • Loading branch information
javiereguiluz committed Oct 29, 2023
2 parents aafd9ad + b835bbf commit 4cdbfd0
Show file tree
Hide file tree
Showing 9 changed files with 276 additions and 41 deletions.
41 changes: 41 additions & 0 deletions doc/fields.rst
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,18 @@ Add tabs to your forms with the ``addTab()`` method of the special ``FormField``
];
}

The arguments of the ``addTab()`` method are:

* ``$label``: (type: ``TranslatableInterface|string|false|null``) the text that
this tab displays in the clickable list of tabs; if you set it to ``false``,
``null`` or an empty string, no text will be displayed (make sure to show an
icon for the tab or users won't be able to click on it); You can also pass
``string`` and ``TranslatableInterface`` variables. In both cases, if they
contain HTML tags they will be rendered in stead of escaped;
* ``$icon``: (type: ``?string``) the full CSS class of a `FontAwesome icon`_
(e.g. ``far fa-folder-open``); if you don't display a text label for the tab,
make sure to display an icon or users won't be able to click on the tab.

Inside tabs you can include not only form fields but all the other form layout
fields explained in the following sections: columns, fieldsets and rows. This
is how a form using all those elements looks like:
Expand Down Expand Up @@ -337,6 +349,24 @@ spanning the other 4 Bootstrap columns)::
];
}

The arguments of the ``addColumn()`` method are:

* ``$cols``: (type: ``int|string``) the width of the column defined as any value
compatible with the `Bootstrap grid system`_ (e.g. ``'col-6'``, ``'col-md-6 col-xl-4'``,
etc.). Integer values are transformed like this: N -> 'col-N' (e.g. ``8`` is
transformed to ``col-8``);
* ``$label``: (type: ``TranslatableInterface|string|false|null``) an optional title
that is displayed at the top of the column. If you pass ``false``, ``null``
or an empy string, no title is displayed. You can also pass ``string`` and
``TranslatableInterface`` variables. In both cases, if they contain HTML tags
they will be rendered in stead of escaped;
* ``$icon``: (type: ``?string``) the full CSS class of a `FontAwesome icon`_
(e.g. ``far fa-folder-open``) that is displayed next to the column label;
* ``$help``: (type: ``?string``) an optional content that is displayed below the
column label; it's mostly used to describe the column contents or provide further
instructions or help contents. You can include HTML tags and they will be
rendered instead of escaped.

Thanks to Bootstrap responsive classes, you can have columns of different sizes,
or even no columns at all, depending on the browser window size. In the following
example, breakpoints below ``lg`` doesn't display columns. Also, the sum of the
Expand Down Expand Up @@ -434,6 +464,16 @@ Add fieldsets with the created with the ``addFieldset()`` method of the special
];
}

The arguments of the ``addFieldset()`` method are:

* ``$label``: (type: ``TranslatableInterface|string|false|null``) an optional title
that is displayed at the top of the fieldset. If you pass ``false``, ``null``
or an empy string, no title is displayed. You can also pass ``string`` and
``TranslatableInterface`` variables. In both cases, if they contain HTML tags
they will be rendered in stead of escaped;
* ``$icon``: (type: ``?string``) the full CSS class of a `FontAwesome icon`_
(e.g. ``far fa-folder-open``) that is displayed next to the fieldset label.

When using form columns, fieldsets inside them display a slightly different
design to better group the different fields. That's why it's recommended to
use fieldsets whenever you use columns. This is how it looks like:
Expand Down Expand Up @@ -944,3 +984,4 @@ attribute of the tag to run your configurator before or after the built-in ones.
.. _`Doctrine DBAL Type`: https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/types.html
.. _`Custom Mapping Types`: https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/types.html#custom-mapping-types
.. _`Custom Form Field Types`: https://symfony.com/doc/current/form/create_custom_field_type.html
.. _`FontAwesome icon`: https://fontawesome.com/v6/search?m=free
2 changes: 1 addition & 1 deletion src/Factory/FormLayoutFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ private function linearizeLayoutConfiguration(FieldCollection $fields): void

if ($fieldDto->isFormTab()) {
$isTabActive = 0 === \count($tabs);
$tabId = sprintf('tab-%s', $fieldDto->getLabel() ? $slugger->slug($fieldDto->getLabel())->lower()->toString() : ++$tabsWithoutLabelCounter);
$tabId = sprintf('tab-%s', $fieldDto->getLabel() ? $slugger->slug(strip_tags($fieldDto->getLabel()))->lower()->toString() : ++$tabsWithoutLabelCounter);
$fieldDto->setCustomOption(FormField::OPTION_TAB_ID, $tabId);
$fieldDto->setCustomOption(FormField::OPTION_TAB_IS_ACTIVE, $isTabActive);

Expand Down
4 changes: 2 additions & 2 deletions src/Field/FormField.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ public static function addRow(string $breakpointName = ''): self
/**
* @return static
*/
public static function addTab(TranslatableInterface|string $label, ?string $icon = null): self
public static function addTab(TranslatableInterface|string|false|null $label = null, ?string $icon = null): self
{
$field = new self();
$icon = $field->fixIconFormat($icon, 'FormField::addTab()');
Expand All @@ -124,7 +124,7 @@ public static function addTab(TranslatableInterface|string $label, ?string $icon
* (e.g. 'col-6', 'col-sm-3', 'col-md-6 col-xl-4', etc.)
* (integer values are transformed like this: N -> 'col-N')
*/
public static function addColumn(int|string $cols = 'col', TranslatableInterface|string|null $label = null, ?string $icon = null, ?string $help = null): self
public static function addColumn(int|string $cols = 'col', TranslatableInterface|string|false|null $label = null, ?string $icon = null, ?string $help = null): self
{
$field = new self();
// $icon = $field->fixIconFormat($icon, 'FormField::addTab()');
Expand Down
29 changes: 16 additions & 13 deletions src/Resources/views/crud/detail.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@
{%- if tab.getCustomOption('icon')|default(false) -%}
<i class="fa-fw {{ tab.getCustomOption('icon') }}"></i>
{%- endif -%}
{{ tab.label|trans(domain = ea.i18n.translationDomain) }}
{{ tab.label|trans(domain = ea.i18n.translationDomain)|raw }}

{% set tab_error_count = tab.getCustomOption(tab_error_count_option_name) %}
{%- if tab_error_count > 0 -%}
Expand Down Expand Up @@ -194,18 +194,21 @@

{% macro render_column_open(field) %}
{% set field_icon = field.getCustomOption('icon') %}
{% set column_has_title = field_icon != null or field.label != false or field.label != null or field.label != '' or field.help != null %}

<div class="form-column {{ not column_has_title ? 'form-column-no-header' }} {{ field.cssClass }}">
{% if column_has_title %}
<div class="form-column-title">
<div class="form-column-title-content">
{% if field_icon %}<i class="form-column-icon fa fa-fw fa-{{ field_icon }}"></i>{% endif %}
{% if field.label %}{{ field.label|trans(domain = ea.i18n.translationDomain)|raw }}{% endif %}
</div>

<div class="form-column {{ field.icon|default(false) == false and field.label|default(false) == false ? 'form-column-no-header' }} {{ field.cssClass|default('') }}">
<div class="form-column-title">
<div class="form-column-title-content">
{% if field_icon|default(false) %}<i class="form-column-icon fa fa-fw fa-{{ field_icon }}"></i>{% endif %}
{% if field.label|default(false) %}{{ field.label|default('')|trans(domain = ea.i18n.translationDomain)|raw }}{% endif %}
{% if field.help %}
<div class="form-column-help">{{ field.help|trans(domain = ea.i18n.translationDomain)|raw }}</div>
{% endif %}
</div>

{% if field.help|default(false) %}
<div class="form-column-help">{{ field.help|trans(domain = ea.i18n.translationDomain)|raw }}</div>
{% endif %}
</div>
{% endif %}
{% endmacro %}

{% macro render_column_close(field) %}
Expand All @@ -219,8 +222,8 @@
{% set is_collapsible = field.getCustomOption(is_collapsible_option_name) %}
{% set is_collapsed = field.getCustomOption(is_collapsed_option_name) %}

<div class="{{ field.cssClass }}">
<fieldset class="form-fieldset">
<div class="form-fieldset {{ not fieldset_has_header ? 'form-fieldset-no-header' }} {{ field.cssClass }}">
<fieldset>
{% if fieldset_has_header %}
<div class="form-fieldset-header {{ is_collapsible ? 'collapsible' }} {{ field.help is not empty ? 'with-help' }}">
<div class="form-fieldset-title">
Expand Down
8 changes: 4 additions & 4 deletions src/Resources/views/crud/form_theme.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -546,7 +546,7 @@
{% block ea_form_column_open_row %}
{% set field = form.vars.ea_vars.field %}
{% set field_icon = field.getCustomOption('icon') %}
{% set column_has_title = field_icon != null or field.label != false or field.label != null or field.help != null %}
{% set column_has_title = field_icon != null or field.label != false or field.label != null or field.label != '' or field.help != null %}

<div class="form-column {{ not column_has_title ? 'form-column-no-header' }} {{ field.cssClass }}">
{% if column_has_title %}
Expand All @@ -570,8 +570,8 @@
{% block ea_form_fieldset_open_row %}
{% set fieldset_has_header = form.vars.label or ea_icon or ea_help %}

<div class="{{ ea_css_class }}">
<fieldset class="form-fieldset">
<div class="form-fieldset {{ not fieldset_has_header ? 'form-fieldset-no-header' }} {{ ea_css_class }}">
<fieldset>
{% if fieldset_has_header %}
<div class="form-fieldset-header {{ ea_is_collapsible ? 'collapsible' }} {{ ea_help is not empty ? 'with-help' }}">
<div class="form-fieldset-title">
Expand Down Expand Up @@ -631,7 +631,7 @@
{%- if tab.getCustomOption('icon')|default(false) -%}
<i class="fa-fw {{ tab.getCustomOption('icon') }}"></i>
{%- endif -%}
{{ tab.label|trans(domain = ea.i18n.translationDomain) }}
{{ tab.label|trans(domain = ea.i18n.translationDomain)|raw }}

{% set tab_error_count = tab.getCustomOption(tab_error_count_option_name) %}
{%- if tab_error_count > 0 -%}
Expand Down
38 changes: 37 additions & 1 deletion tests/Controller/FormFieldHelpControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,43 @@ protected function getDashboardFqcn(): string
return DashboardController::class;
}

public function testFieldsHelpMessages()
public function testFieldsHelpMessagesInForms()
{
$crawler = $this->client->request('GET', $this->generateNewFormUrl());

// fields with no help defined
static::assertSelectorNotExists('#tab-tab-1 .tab-help', 'The Tab 1 does not define a help message.');
static::assertSelectorNotExists('.form-column.column-1 .form-column-help', 'The Column 1 does not define a help message.');
static::assertSelectorNotExists('.form-fieldset-title:contains("Fieldset 1") .form-fieldset-help', 'The Fieldset 1 does not define a help message.');
static::assertSelectorNotExists('.form-group #BlogPost_id + .form-help', 'The ID field does not define a help message.');
static::assertSelectorNotExists('.form-group #BlogPost_title + .form-help', 'The title field defines an empty string as a help message, so it does not render an HTML element for that help message.');

// fields with help defined as simple text strings
static::assertSelectorTextContains('#tab-tab-2 .tab-help', 'Tab 2 Lorem Ipsum', 'The Tab 2 field defines a text help message.');
static::assertSelectorTextContains('.form-column.column-2 .form-column-help', 'Column 2 Lorem Ipsum', 'The Column 2 field defines a text help message.');
static::assertSelectorTextContains('.form-fieldset-title:contains("Fieldset 2") .form-fieldset-help', 'Fieldset 2 Lorem Ipsum', 'The Fieldset 2 field defines a text help message.');
static::assertSelectorTextContains('.form-group #BlogPost_slug + .form-help', 'Slug Lorem Ipsum', 'The slug field defines a text help message.');

// fields with help defined as text strings with HTML contents
static::assertSame('<a href="https://example.com">Tab 3</a> <b>Lorem</b> Ipsum', trim($crawler->filter('#tab-tab-3 .tab-help')->html()), 'The Tab 3 field defines a help message with HTML contents, which must be rendered instead of escaped.');
static::assertSame('<a href="https://example.com">Column 3</a> <b>Lorem</b> Ipsum', trim($crawler->filter('.form-column.column-3 .form-column-help')->html()), 'The Column 3 field defines a help message with HTML contents, which must be rendered instead of escaped.');
static::assertSame('<a href="https://example.com">Fieldset 3</a> <b>Lorem</b> Ipsum', trim($crawler->filter('.form-fieldset-title:contains("Fieldset 3") .form-fieldset-help')->html()), 'The Fieldset 3 field defines a help message with HTML contents, which must be rendered instead of escaped.');
static::assertSame('<a href="https://example.com">Content</a> <b>Lorem</b> Ipsum', $crawler->filter('.form-group #BlogPost_content + .form-help')->html(), 'The content field defines an help message with HTML contents, which must be rendered instead of escaped.');

// fields with help defined as Translatable objects using simple text strings
static::assertSelectorTextContains('#tab-tab-4 .tab-help', 'Tab 4 Lorem Ipsum', 'The Tab 4 field defines a translatable text help message.');
static::assertSelectorTextContains('.form-column.column-4 .form-column-help', 'Column 4 Lorem Ipsum', 'The Column 4 field defines a translatable text help message.');
static::assertSelectorTextContains('.form-fieldset-title:contains("Fieldset 4") .form-fieldset-help', 'Fieldset 4 Lorem Ipsum', 'The Fieldset 4 field defines a translatable text help message.');
static::assertSelectorTextContains('.form-group:contains("Created At") .form-help', 'CreatedAt Lorem Ipsum', 'The createdAt field defines a translatable text help message.');

// fields with help defined as Translatable objects using text strings with HTML contents
static::assertSelectorTextContains('#tab-tab-5 .tab-help', 'Tab 5 Lorem Ipsum', 'The Tab 5 field defines a translatable help message with HTML contents, which must be rendered instead of escaped.');
static::assertSelectorTextContains('.form-column.column-5 .form-column-help', 'Column 5 Lorem Ipsum', 'The Column 5 field defines a translatable help message with HTML contents, which must be rendered instead of escaped..');
static::assertSelectorTextContains('.form-fieldset-title:contains("Fieldset 5") .form-fieldset-help', 'Fieldset 5 Lorem Ipsum', 'The Fieldset 5 field defines a translatable help message with HTML contents, which must be rendered instead of escaped..');
static::assertSame('<a href="https://example.com">PublishedAt</a> <b>Lorem</b> Ipsum', $crawler->filter('.form-group:contains("Published At") .form-help')->html(), 'The publishedAt field defines a translatable help message with HTML contents, which must be rendered instead of escaped.');
}

public function testFieldsHelpMessagesOnDetailPage()
{
$crawler = $this->client->request('GET', $this->generateNewFormUrl());

Expand Down
Loading

0 comments on commit 4cdbfd0

Please sign in to comment.