Code development platform for open source projects from the European Union institutions

Skip to content
Snippets Groups Projects
Unverified Commit 2ad85abf authored by Carlos's avatar Carlos Committed by GitHub
Browse files

Merge pull request #136 from openeuropa/OEL-1367

OEL-1367: New Look & Feel for search form in search page and remove theming from block config form.
parents b216f490 39acd18d
No related branches found
No related tags found
1 merge request!157OEL-1668: Update epic list pages.
Showing
with 251 additions and 207 deletions
......@@ -18,13 +18,13 @@ settings:
provider: oe_whitelabel_search
form:
action: '#'
region: navigation_right
input:
name: text
label: Search
classes: ''
placeholder: Search
button:
classes: ''
label: ''
view_options:
enable_autocomplete: false
id: null
......
......@@ -9,6 +9,9 @@ block.settings.whitelabel_search_block:
action:
type: string
label: Action
region:
type: string
label: Region
input:
type: mapping
label: Input
......@@ -19,9 +22,6 @@ block.settings.whitelabel_search_block:
label:
type: string
label: Label
classes:
type: string
label: Classes
placeholder:
type: string
label: Placeholder
......@@ -29,9 +29,9 @@ block.settings.whitelabel_search_block:
type: mapping
label: Button
mapping:
classes:
label:
type: string
label: Classes
label: Button label
view_options:
type: mapping
label: View Options
......
......@@ -2,7 +2,7 @@
/**
* @file
* OE Whitelabel Search Module.
* Module file used for theming the search feature.
*/
declare(strict_types = 1);
......
<?php
/**
* @file
* OpenEuropa Whitelabel Search post updates.
*/
declare(strict_types=1);
use Drupal\block\Entity\Block;
/**
* Add region in form settings for template suggestions.
*/
function oe_whitelabel_search_post_update_00001(&$sandbox) {
$block = Block::load('oe_whitelabel_search_form');
$settings = $block->get('settings');
$settings['form']['region'] = $block->getRegion();
$block->set('settings', $settings);
$block->save();
}
......@@ -45,64 +45,46 @@ class SearchForm extends FormBase {
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, array $config = NULL): array {
if (empty($config['input']['name'])) {
return [];
}
$form_state->set('oe_whitelabel_search_config', $config);
$input_value = '';
if (!empty($config['input']['name'])) {
$input_value = $this->getRequest()->get($config['input']['name']);
$theme_hook_suffix = $this->getFormId();
if (isset($config['form']['region'])) {
$theme_hook_suffix = $config['form']['region'] . '__' . $theme_hook_suffix;
}
$form['#theme_wrappers'] = ['form__' . $theme_hook_suffix];
$form['search_input'] = [
'#prefix' => '<div class="bcl-search-form__group w-100">',
'#suffix' => '</div>',
/* @see \Drupal\Core\Render\Element\Textfield */
'#type' => 'textfield',
'#theme' => 'input__textfield__' . $theme_hook_suffix,
'#theme_wrappers' => ['form_element__' . $this->getFormId()],
'#title' => $config['input']['label'],
'#title_display' => 'invisible',
'#size' => 20,
'#margin_class' => 'mb-0',
'#default_value' => $this->getRequest()->get($config['input']['name']),
'#required' => TRUE,
'#attributes' => [
'placeholder' => $config['input']['placeholder'],
'class' => [
$config['input']['classes'],
'rounded-0',
'rounded-start',
],
],
'#default_value' => $input_value,
'#required' => TRUE,
];
$form['submit'] = [
'#input' => TRUE,
'#is_button' => TRUE,
'#executes_submit_callback' => TRUE,
'#type' => 'pattern',
'#id' => 'button',
'#variant' => 'light',
'#fields' => [
'icon' => 'search',
'settings' => [
'type' => 'submit',
],
'attributes' => [
'id' => 'submit',
'class' => [
'border-start-0',
'rounded-0 rounded-end',
'd-flex',
'btn btn-light',
'btn-md',
'py-2',
$config['button']['classes'],
],
],
],
/* @see \Drupal\Core\Render\Element\Submit */
'#type' => 'submit',
'#theme_wrappers' => ['input__submit__' . $theme_hook_suffix],
'#value' => $config['button']['label'],
];
if (!$config['view_options']['enable_autocomplete']) {
return $form;
}
/* @see \Drupal\search_api_autocomplete\Element\SearchApiAutocomplete */
$form['search_input']['#type'] = 'search_api_autocomplete';
// The view id.
$form['search_input']['#search_id'] = $config['view_options']['id'];
......
......@@ -4,7 +4,6 @@ declare(strict_types = 1);
namespace Drupal\oe_whitelabel_search\Plugin\Block;
use Drupal\Component\Utility\Html;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Config\ConfigFactoryInterface;
......@@ -94,15 +93,15 @@ class SearchBlock extends BlockBase implements ContainerFactoryPluginInterface {
return [
'form' => [
'action' => '',
'region' => '',
],
'input' => [
'name' => '',
'label' => '',
'classes' => '',
'placeholder' => $this->t('Search'),
],
'button' => [
'classes' => '',
'label' => '',
],
'view_options' => [
'enable_autocomplete' => FALSE,
......@@ -145,13 +144,6 @@ class SearchBlock extends BlockBase implements ContainerFactoryPluginInterface {
'#title' => $this->t('Input Label'),
'#description' => $this->t('A label text for the search input.'),
'#default_value' => $config['input']['label'],
'#required' => TRUE,
];
$form['input']['input_classes'] = [
'#type' => 'textfield',
'#title' => $this->t('Input classes'),
'#description' => $this->t('Add space-separated classes that will be added to the input.'),
'#default_value' => $config['input']['classes'],
];
$form['input']['input_placeholder'] = [
'#type' => 'textfield',
......@@ -166,11 +158,11 @@ class SearchBlock extends BlockBase implements ContainerFactoryPluginInterface {
'#tree' => TRUE,
'#description' => $this->t('Fill in the settings of the Button field.'),
];
$form['button']['button_classes'] = [
$form['button']['button_label'] = [
'#type' => 'textfield',
'#title' => $this->t('Button classes'),
'#description' => $this->t('Add space-separated classes that will be added to the button.'),
'#default_value' => $config['button']['classes'],
'#title' => $this->t('Button label'),
'#description' => $this->t('A label text for the button input.'),
'#default_value' => $config['button']['label'],
];
$form['enable_autocomplete'] = [
'#type' => 'checkbox',
......@@ -224,17 +216,17 @@ class SearchBlock extends BlockBase implements ContainerFactoryPluginInterface {
$values = $form_state->getValues();
$this->setConfigurationValue('form', [
'action' => $form_state->getValue('form_action'),
'region' => $form_state->getCompleteFormState()->getValue('region'),
]);
$input = $values['input'];
$this->setConfigurationValue('input', [
'name' => $input['input_name'],
'label' => $input['input_label'],
'classes' => $input['input_classes'],
'placeholder' => $input['input_placeholder'],
]);
$button = $values['button'];
$this->setConfigurationValue('button', [
'classes' => $button['button_classes'],
'label' => $button['button_label'],
]);
$this->setConfigurationValue('view_options', [
'id' => $form_state->getValue('view_id'),
......@@ -249,18 +241,6 @@ class SearchBlock extends BlockBase implements ContainerFactoryPluginInterface {
public function blockValidate($form, FormStateInterface $form_state): void {
$values = $form_state->getValues();
if ($values['input']['input_classes'] !== Html::cleanCssIdentifier($values['input']['input_classes'])) {
$form_state->setErrorByName('input][input_classes', $this->t('Field "@field_name" does not contain a valid css class.', [
'@field_name' => $form['input']['input_classes']['#title'],
]));
}
if ($values['button']['button_classes'] !== Html::cleanCssIdentifier($values['button']['button_classes'])) {
$form_state->setErrorByName('button][button_classes', $this->t('Field "@field_name" does not contain a valid css class.', [
'@field_name' => $form['button']['button_classes']['#title'],
]));
}
if (!$this->moduleHandler->moduleExists('views') || !$this->moduleHandler->moduleExists('search_api_autocomplete')) {
return;
}
......
......@@ -82,9 +82,46 @@ class SearchBlockTest extends KernelTestBase {
}
/**
* Tests the rendering of the whitelabel search block.
* Tests the rendering of the whitelabel search block navigation_right region.
*/
public function testBlockRendering(): void {
public function testNavigationRightSearchBlockRendering(): void {
$block_entity_storage = $this->container
->get('entity_type.manager')
->getStorage('block');
$entity = $block_entity_storage->load('oe_whitelabel_search_form');
$builder = \Drupal::entityTypeManager()->getViewBuilder('block');
$build = $builder->view($entity, 'block');
$render = $this->container->get('renderer')->renderRoot($build);
$crawler = new Crawler($render->__toString());
// Assert the form rendering.
$form = $crawler->filter('form');
$this->assertCount(1, $form);
$this->assertSame('oe-whitelabel-search-form', $form->attr('id'));
$this->assertStringContainsString('d-flex', $form->attr('class'));
// Assert search text box.
$input = $crawler->filter('input[name="search_input"]');
$this->assertCount(1, $input);
$classes = 'required form-control border-start-0 rounded-0 rounded-start';
$this->assertSame($classes, $input->attr('class'));
$this->assertSame('Search', $input->attr('placeholder'));
// Assert the button and icon rendering.
$button = $form->filter('button');
$this->assertCount(1, $button);
$this->assertStringContainsString('rounded-end', $button->attr('class'));
$this->assertStringContainsString('rounded-0', $button->attr('class'));
$this->assertStringContainsString('border-start-0', $button->attr('class'));
$this->assertStringContainsString('btn', $button->attr('class'));
$this->assertStringContainsString('btn-md', $button->attr('class'));
$this->assertStringContainsString('btn-light', $button->attr('class'));
$icon = $button->filter('.bi.icon--fluid');
$this->assertCount(1, $icon);
}
/**
* Tests the rendering of the whitelabel search block header region.
*/
public function testHeaderSearchBlockRendering(): void {
$block_entity_storage = $this->container
->get('entity_type.manager')
->getStorage('block');
......@@ -93,20 +130,20 @@ class SearchBlockTest extends KernelTestBase {
'theme' => 'oe_whitelabel',
'plugin' => 'whitelabel_search_block',
'settings' => [
'id' => 'search_block',
'label' => 'Search block',
'id' => 'whitelabel_search_block',
'label' => 'Header Search block',
'provider' => 'oe_whitelabel_search',
'form' => [
'action' => '/search',
'action' => 'search',
'region' => 'header',
],
'input' => [
'name' => 'text',
'name' => 'search_api_fulltext',
'label' => 'Search',
'placeholder' => 'Search',
'classes' => 'input-test-class',
],
'button' => [
'classes' => 'button-test-class',
'label' => 'Search',
],
'view_options' => [
'enable_autocomplete' => TRUE,
......@@ -122,33 +159,37 @@ class SearchBlockTest extends KernelTestBase {
$render = $this->container->get('renderer')->renderRoot($build);
$crawler = new Crawler($render->__toString());
// Select the search form in the block.
// The block template removes the block wrapper, so the form is the root
// element.
$form = $crawler->filter('body > form#oe-whitelabel-search-form');
// Assert header form wrappers.
$wrapper = $crawler->filter(
'div.bg-lighter > div.container > div.row > div.col-12.col-lg-6.offset-lg-3'
);
$this->assertCount(1, $wrapper);
// Assert the form rendering.
$form = $wrapper->filter('form');
$this->assertCount(1, $form);
$this->assertSame('d-flex mt-3 mt-lg-0', $form->attr('class'));
$this->assertSame('oe-whitelabel-search-form', $form->attr('id'));
$this->assertSame('bcl-search-form submittable', $form->attr('class'));
// Assert the field wrapper rendering.
$wrapper = $form->filter('.bcl-search-form__group');
$this->assertCount(1, $wrapper);
// Assert search text box.
$input = $crawler->filter('.input-test-class');
$input = $crawler->filter('input[name="search_input"]');
$this->assertCount(1, $input);
$classes = 'input-test-class rounded-0 rounded-start form-autocomplete required form-control';
$classes = 'form-autocomplete required form-control bcl-search-form__input';
$this->assertSame($classes, $input->attr('class'));
$this->assertSame('Search', $input->attr('placeholder'));
// Assert the hidden label.
$label = $wrapper->filter('label');
$this->assertSame('Search', $label->text());
$classes = 'visually-hidden js-form-required form-required form-label';
$this->assertSame($classes, $label->attr('class'));
// Assert the button and icon rendering.
$button = $crawler->filter('.button-test-class');
$button = $form->filter('button');
$this->assertCount(1, $button);
$classes = 'border-start-0 rounded-0 rounded-end d-flex btn btn-light btn-md py-2 button-test-class btn btn-light';
$this->assertSame($classes, $button->attr('class'));
$this->assertStringContainsString('bcl-search-form__submit', $button->attr('class'));
$this->assertStringContainsString('btn', $button->attr('class'));
$this->assertStringContainsString('btn-primary', $button->attr('class'));
$this->assertStringContainsString('btn-md', $button->attr('class'));
$icon = $button->filter('.bi.icon--fluid');
$this->assertCount(1, $icon);
$label = $button->filter('span.d-none.d-lg-inline-block');
$this->assertCount(1, $label);
$this->assertEquals('Search', $label->text());
}
}
......@@ -109,7 +109,7 @@
{% endblock %}
<main>
{{ page.hero }}
<div class="container {{ page.header ? 'mt-2': 'mt-5' }}">
<div class="container mt-md-4-75 mt-4">
<div class="row">
{% if page.highlighted %}
{{ page.highlighted }}
......
{#
/**
* @file
* Search form template for the header region.
*
* @see ./core/modules/system/templates/form.html.twig
*/
#}
<div class="bg-lighter py-4 py-lg-3">
<div class="container">
<div class="row">
<div class="col-12 col-lg-6 offset-lg-3">
<form {{ attributes.addClass('bcl-search-form', 'submittable') }}>
<div class="bcl-search-form__group">
{{ children }}
</div>
</form>
</div>
</div>
</div>
</div>
{#
/**
* @file
* Search form template for the navigation right region.
*/
#}
{% extends "form.html.twig" %}
{% set attributes = attributes.addClass('d-flex', 'mt-3', 'mt-lg-0') %}
{#
/**
* @file
* Theme implementation for the search form element.
*
* Display search form without wrappers or other elements like label, prefix, suffix or description.
*
* @see ./core/modules/system/templates/form-element.html.twig
*/
#}
{{ children }}
{% if errors %}
<div class="form-item--error-message invalid-feedback d-block">
{{ errors }}
</div>
{% endif %}
{#
/**
* @file
* Default theme implementation for a form element.
*
* Available variables:
* - attributes: HTML attributes for the containing element.
* - errors: (optional) Any errors for this form element, may not be set.
* - prefix: (optional) The form element prefix, may not be set.
* - suffix: (optional) The form element suffix, may not be set.
* - required: The required marker, or empty if the associated form element is
* not required.
* - type: The type of the element.
* - name: The name of the element.
* - label: A rendered label element.
* - label_display: Label display setting. It can have these values:
* - before: The label is output before the element. This is the default.
* The label includes the #title and the required marker, if #required.
* - after: The label is output after the element. For example, this is used
* for radio and checkbox #type elements. If the #title is empty but the
* field is #required, the label will contain only the required marker.
* - invisible: Labels are critical for screen readers to enable them to
* properly navigate through forms but can be visually distracting. This
* property hides the label for everyone except screen readers.
* - attribute: Set the title attribute on the element to create a tooltip but
* output no label element. This is supported only for checkboxes and radios
* in \Drupal\Core\Render\Element\CompositeFormElementTrait::preRenderCompositeFormElement().
* It is used where a visual label is not needed, such as a table of
* checkboxes where the row and column provide the context. The tooltip will
* include the title and required marker.
* - description: (optional) A list of description properties containing:
* - content: A description of the form element, may not be set.
* - attributes: (optional) A list of HTML attributes to apply to the
* description content wrapper. Will only be set when description is set.
* - description_display: Description display setting. It can have these values:
* - before: The description is output before the element.
* - after: The description is output after the element. This is the default
* value.
* - invisible: The description is output after the element, hidden visually
* but available to screen readers.
* - disabled: True if the element is disabled.
* - title_display: Title display setting.
*
* @see template_preprocess_form_element()
*
* @ingroup themeable
*/
#}
{%
set classes = [
'js-form-item',
'form-item',
'js-form-type-' ~ type|clean_class,
'form-item-' ~ name|clean_class,
'js-form-item-' ~ name|clean_class,
title_display not in ['after', 'before'] ? 'form-no-label',
disabled == 'disabled' ? 'form-disabled',
errors ? 'form-item--error',
]
%}
{%
set description_classes = [
'description',
'form-text',
'text-muted',
description_display == 'invisible' ? 'visually-hidden',
]
%}
<div{{ attributes.addClass(classes) }}>
{% if label_display in ['before', 'invisible'] %}
{{ label }}
{% endif %}
{% if prefix is not empty %}
<span class="field-prefix">{{ prefix }}</span>
{% endif %}
{% if description_display == 'before' and description.content %}
<small{{ description.attributes }}>
{{ description.content }}
</small>
{% endif %}
{{ children }}
{% if suffix is not empty %}
<span class="field-suffix">{{ suffix }}</span>
{% endif %}
{% if label_display == 'after' %}
{{ label }}
{% endif %}
{% if errors %}
<div class="form-item--error-message invalid-feedback d-block">
{{ errors }}
</div>
{% endif %}
{% if description_display in ['after', 'invisible'] and description.content %}
<small{{ description.attributes.addClass(description_classes) }}>
{{ description.content }}
</small>
{% endif %}
</div>
{#
/**
* @file
* Theme for the header search input submit element.
*
* @see ./core/modules/system/templates/input.html.twig
*/
#}
{% if element['#value'] is not empty %}
{% set label %}
<span class="d-none d-lg-inline-block">{{ element['#value']|t }}</span>
{% endset %}
{% endif %}
{{ pattern('button', {
'variant': 'primary',
'icon': 'search',
'label': label,
'type': 'submit',
'icon_position': 'before',
'attributes': attributes
.addClass([
'bcl-search-form__submit',
'px-3'
])
}) }}
{{ children }}
{#
/**
* @file
* Theme for the navigation_right search input submit element.
*
* @see ./core/modules/system/templates/input.html.twig
*/
#}
{% if element['#value'] is not empty %}
{% set label %}
<span class="d-none d-lg-inline-block">{{ element['#value']|t }}</span>
{% endset %}
{% endif %}
{{ pattern('button', {
'variant': 'light',
'label': label,
'icon': 'search',
'type': 'submit',
'icon_position': 'before',
'attributes': attributes
.addClass([
'border-start-0',
'rounded-0',
'rounded-end',
'px-3',
])
}) }}
{{ children }}
{#
/**
* @file
* Theme for the header search input textfield element.
*/
#}
{% extends "input--textfield.html.twig" %}
{% set attributes = attributes
.addClass([
'form-control',
'bcl-search-form__input',
attributes.hasClass('error') ? 'is-invalid',
])
.removeClass('form-text') %}
{#
/**
* @file
* Theme for the navigation right search input textfield element.
*/
#}
{% extends "input--textfield.html.twig" %}
{% set attributes = attributes
.addClass([
'form-control',
'border-start-0',
'rounded-0',
'rounded-start',
attributes.hasClass('error') ? 'is-invalid',
])
.removeClass('form-text') %}
{#
/**
* @file
* Override for forms with form id = 'oe_whitelabel_search_form'.
*
* @see ./core/modules/system/templates/form.html.twig
* @see \Drupal\oe_whitelabel_search\Form\SearchForm
*/
#}
<form{{ attributes.addClass('d-flex', 'mt-3', 'mt-lg-0') }}>
{{ children }}
</form>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment