Commit 85fc5d74 authored by borisson_'s avatar borisson_ Committed by borisson_

Issue #2725453 by borisson_, claudiu.cristea, marthinal, ekes, Nick_vh:...

Issue #2725453 by borisson_, claudiu.cristea, marthinal, ekes, Nick_vh: Refactor widget plugins by adding interface, base class, schema
parent 12632560
......@@ -33,8 +33,15 @@ facets.facet.*:
type: boolean
label: 'Exclude'
widget:
type: string
label: 'Widget identifier'
type: mapping
label: 'Facet widget'
mapping:
type:
type: string
label: 'Plugin ID'
config:
type: facet.widget.config.[%parent.type]
label: 'Configuration'
empty_behavior:
type: mapping
label: 'Empty behavior'
......@@ -48,12 +55,6 @@ facets.facet.*:
text:
type: string
label: 'Text'
widget_configs:
type: sequence
label: 'Widget plugin configurations'
sequence:
type: string
label: 'Widget plugin configurations'
only_visible_when_facet_source_is_visible:
type: boolean
label: 'Show this facet only when the facet source is visible.'
......
# Default config schema that all widgets can extend.
facet.widget.default_config:
type: mapping
label: 'Default widget configuration'
mapping:
show_numbers:
type: boolean
label: 'Show counts'
# Default schema for facet widgets of unknown type.
facet.widget.config.*:
type: facet.widget.default_config
# Config schema for dropdown, you can find the implementation in
# Drupal\facets\Plugin\facets\widget\DropdownWidget. Options for this widget are
# "show counts" and "default option label".
facet.widget.config.dropdown:
type: facet.widget.default_config
label: 'Dropdown widget configuration'
mapping:
default_option_label:
type: string
label: 'Default option label'
# Config schema for links, you can find the implementation in
# Drupal\facets\Plugin\facets\widget\LinksWidget. Options for this widget are
# "soft limit" and "show counts".
facet.widget.config.links:
type: facet.widget.default_config
label: 'List of links widget configuration'
mapping:
soft_limit:
type: integer
label: 'Soft limit'
# Config schema for checkbox, you can find the implementation in
# Drupal\facets\Plugin\facets\widget\CheckboxWidget. Options for this widget are
# "soft limit" and "show counts".
facet.widget.config.checkbox:
type: facet.widget.config.links
......@@ -216,7 +216,7 @@ class IntegrationTest extends WebTestBase {
$this->assertResponse(200);
$edit = [
'widget_configs[show_numbers]' => $show,
'widget_config[show_numbers]' => $show,
];
$this->drupalPostForm(NULL, $edit, $this->t('Save'));
}
......@@ -260,7 +260,7 @@ class IntegrationTest extends WebTestBase {
$edit = [
'facet_settings[only_visible_when_facet_source_is_visible]' => TRUE,
'widget' => 'links',
'widget_configs[show_numbers]' => '0',
'widget_config[show_numbers]' => '0',
];
$this->drupalPostForm(NULL, $edit, $this->t('Save'));
}
......
......@@ -40,7 +40,6 @@ use Drupal\facets\FacetInterface;
* "query_type_name",
* "facet_source_id",
* "widget",
* "widget_configs",
* "query_operator",
* "exclude",
* "only_visible_when_facet_source_is_visible",
......@@ -88,18 +87,18 @@ class Facet extends ConfigEntityBase implements FacetInterface {
protected $description;
/**
* The plugin name of the widget.
* The widget plugin definition.
*
* @var string
* @var array
*/
protected $widget;
/**
* Configuration for the widget. This is a key-value stored array.
* The widget plugin instance.
*
* @var array
* @var \Drupal\facets\Widget\WidgetPluginBase
*/
protected $widget_configs = [];
protected $widgetInstance;
/**
* The operator to hand over to the query, currently AND | OR.
......@@ -280,16 +279,25 @@ class Facet extends ConfigEntityBase implements FacetInterface {
/**
* {@inheritdoc}
*/
public function setWidget($widget) {
$this->widget = $widget;
return $this;
public function getQueryTypes() {
return $this->query_type_name;
}
/**
* {@inheritdoc}
*/
public function getQueryTypes() {
return $this->query_type_name;
public function setWidget($id, array $configuration = NULL) {
if ($configuration === NULL) {
$instance = $this->getWidgetManager()->createInstance($id);
// Get the default configuration for this plugin.
$configuration = $instance->getConfiguration();
}
$this->widget = ['type' => $id, 'config' => $configuration];
// Unset the widget instance, if exists.
unset($this->widgetInstance);
return $this;
}
/**
......@@ -299,6 +307,18 @@ class Facet extends ConfigEntityBase implements FacetInterface {
return $this->widget;
}
/**
* {@inheritdoc}
*/
public function getWidgetInstance() {
if (!isset($this->widgetInstance)) {
$definition = $this->getWidget();
$this->widgetInstance = $this->getWidgetManager()
->createInstance($definition['type'], (array) $definition['config']);
}
return $this->widgetInstance;
}
/**
* Retrieves all processors supported by this facet.
*
......@@ -349,8 +369,8 @@ class Facet extends ConfigEntityBase implements FacetInterface {
$query_types = $facet_source->getQueryTypesForFacet($this);
// Get our widget configured for this facet.
/** @var \Drupal\facets\Widget\WidgetInterface $widget */
$widget = $this->getWidgetManager()->createInstance($this->getWidget());
/** @var \Drupal\facets\Widget\WidgetPluginInterface $widget */
$widget = $this->getWidgetInstance();
// Give the widget the chance to select a preferred query type. This is
// useful with a date widget, as it needs to select the date query type.
return $widget->getQueryType($query_types);
......@@ -696,20 +716,6 @@ class Facet extends ConfigEntityBase implements FacetInterface {
$this->empty_behavior = $empty_behavior;
}
/**
* {@inheritdoc}
*/
public function setWidgetConfigs(array $widget_configs) {
$this->widget_configs = $widget_configs;
}
/**
* {@inheritdoc}
*/
public function getWidgetConfigs() {
return $this->widget_configs;
}
/**
* {@inheritdoc}
*/
......
......@@ -10,24 +10,36 @@ use Drupal\Core\Config\Entity\ConfigEntityInterface;
interface FacetInterface extends ConfigEntityInterface {
/**
* Sets the facet's widget plugin id.
* Sets the facet widget definition.
*
* @param string $widget
* @param string $id
* The widget plugin id.
* @param array $configuration
* (optional) The facet widget plugin configuration. If missed, the default
* plugin configuration will be filled.
*
* @return $this
* Returns self
*/
public function setWidget($widget);
public function setWidget($id, array $configuration = NULL);
/**
* Returns the facet's widget plugin id.
* Returns the facet widget definition.
*
* @return string
* The widget plugin id.
* @return array
* An associative array with the following structure:
* - id: The widget plugin id as a string.
* - config: The widget configuration as an array.
*/
public function getWidget();
/**
* Returns the facet widget instance.
*
* @return \Drupal\facets\Widget\WidgetPluginBase
* The plugin instance
*/
public function getWidgetInstance();
/**
* Returns field identifier.
*
......@@ -328,22 +340,6 @@ interface FacetInterface extends ConfigEntityInterface {
*/
public function getEmptyBehavior();
/**
* Returns the configuration of the selected widget.
*
* @return array
* The configuration settings for the widget.
*/
public function getWidgetConfigs();
/**
* Sets the configuration for the widget of this facet.
*
* @param array $widget_config
* The configuration settings for the widget.
*/
public function setWidgetConfigs(array $widget_config);
/**
* Returns any additional configuration for this facet, not defined above.
*
......
......@@ -75,6 +75,7 @@ class FacetListBuilder extends ConfigEntityListBuilder {
public function buildRow(EntityInterface $entity) {
/** @var \Drupal\facets\FacetInterface $entity */
$row = parent::buildRow($entity);
$facet = $entity;
return array(
'data' => array(
......@@ -85,14 +86,14 @@ class FacetListBuilder extends ConfigEntityListBuilder {
'title' => array(
'data' => array(
'#type' => 'link',
'#title' => $entity->getName(),
'#suffix' => '<div>' . $entity->getFieldAlias() . ' - ' . $entity->getWidget() . '</div>',
) + $entity->toUrl('edit-form')->toRenderArray(),
'#title' => $facet->label(),
'#suffix' => '<div>' . $facet->getFieldAlias() . ' - ' . $facet->getWidget()['type'] . '</div>',
) + $facet->toUrl('edit-form')->toRenderArray(),
'class' => array('search-api-title'),
),
'operations' => $row['operations'],
),
'title' => $this->t('ID: @name', array('@name' => $entity->id())),
'title' => $this->t('ID: @name', array('@name' => $facet->id())),
'class' => array('facet'),
);
}
......
......@@ -325,8 +325,8 @@ class DefaultFacetManager {
}
// Let the widget plugin render the facet.
/** @var \Drupal\facets\Widget\WidgetInterface $widget */
$widget = $this->widgetPluginManager->createInstance($facet->getWidget());
/** @var \Drupal\facets\Widget\WidgetPluginInterface $widget */
$widget = $facet->getWidgetInstance();
return [$widget->build($facet)];
}
......
......@@ -2,7 +2,6 @@
namespace Drupal\facets\Form;
use Drupal\Core\Config\Config;
use Drupal\Component\Utility\Html;
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Entity\EntityTypeManager;
......@@ -106,29 +105,32 @@ class FacetForm extends EntityForm {
* The current state of the complete form.
*/
public function buildWidgetConfigForm(array &$form, FormStateInterface $form_state) {
$widget = $form_state->getValue('widget') ?: $this->entity->getWidget();
if (!is_null($widget) && $widget !== '') {
$widget_instance = $this->getWidgetPluginManager()->createInstance($widget);
// @todo Create, use and save SubFormState already here, not only in
// validate(). Also, use proper subset of $form for first parameter?
$config = $this->config('facets.facet.' . $this->entity->id());
if ($config_form = $widget_instance->buildConfigurationForm([], $form_state, ($config instanceof Config) ? $config : NULL)) {
$form['widget_configs']['#type'] = 'fieldset';
$form['widget_configs']['#title'] = $this->t('%widget settings', ['%widget' => $this->getWidgetPluginManager()->getDefinition($widget)['label']]);
$form['widget_configs'] += $config_form;
}
else {
$form['widget_configs']['#type'] = 'container';
$form['widget_configs']['#open'] = TRUE;
$form['widget_configs']['widget_information_dummy'] = [
'#type' => 'hidden',
'#value' => '1',
'#default_value' => '1',
];
}
/** @var \Drupal\facets\FacetInterface $facet */
$facet = $this->getEntity();
$widget_plugin_id = $form_state->getValue('widget') ?: $facet->getWidget()['type'];
$widget_config = $form_state->getValue('widget_config') ?: $facet->getWidget()['config'];
if (empty($widget_plugin_id)) {
return;
}
/** @var \Drupal\facets\Widget\WidgetPluginBase $widget */
$facet->setWidget($widget_plugin_id, $widget_config);
$widget = $facet->getWidgetInstance();
$arguments = ['%widget' => $widget->getPluginDefinition()['label']];
if (!$config_form = $widget->buildConfigurationForm([], $form_state, $this->getEntity())) {
$type = 'details';
$config_form = ['#markup' => $this->t('%widget widget needs no configuration.', $arguments)];
}
else {
$type = 'fieldset';
}
$form['widget_config'] = [
'#type' => $type,
'#tree' => TRUE,
'#title' => $this->t('%widget settings', $arguments),
'#attributes' => ['id' => 'facets-widget-config-form'],
] + $config_form;
}
/**
......@@ -149,7 +151,7 @@ class FacetForm extends EntityForm {
'#title' => $this->t('Widget'),
'#description' => $this->t('The widget used for displaying this facet.'),
'#options' => $widget_options,
'#default_value' => $facet->getWidget(),
'#default_value' => $facet->getWidget()['type'],
'#required' => TRUE,
'#ajax' => [
'trigger_as' => ['name' => 'widget_configure'],
......@@ -159,7 +161,7 @@ class FacetForm extends EntityForm {
'effect' => 'fade',
],
];
$form['widget_configs'] = [
$form['widget_config'] = [
'#type' => 'container',
'#attributes' => [
'id' => 'facets-widget-config-form',
......@@ -551,10 +553,9 @@ class FacetForm extends EntityForm {
$facet->addProcessor($new_settings);
}
$facet->setWidget($form_state->getValue('widget'));
$facet->setWidget($form_state->getValue('widget'), $form_state->getValue('widget_config'));
$facet->setUrlAlias($form_state->getValue(['facet_settings', 'url_alias']));
$facet->setWeight((int) $form_state->getValue(['facet_settings', 'weight']));
$facet->setWidgetConfigs($form_state->getValue('widget_configs'));
$facet->setOnlyVisibleWhenFacetSourceIsVisible($form_state->getValue(['facet_settings', 'only_visible_when_facet_source_is_visible']));
$facet->setShowOnlyOneResult($form_state->getValue(['facet_settings', 'show_only_one_result']));
......@@ -596,7 +597,7 @@ class FacetForm extends EntityForm {
* Handles changes to the selected widgets.
*/
public function buildAjaxWidgetConfigForm(array $form, FormStateInterface $form_state) {
return $form['widget_configs'];
return $form['widget_config'];
}
/**
......
......@@ -16,32 +16,14 @@ use Drupal\facets\Result\ResultInterface;
*/
class CheckboxWidget extends LinksWidget {
/**
* The facet the widget is being built for.
*
* @var \Drupal\facets\FacetInterface
*/
protected $facet;
/**
* {@inheritdoc}
*/
public function build(FacetInterface $facet) {
$this->facet = $facet;
$build = parent::build($facet);
$build['#attributes']['class'][] = 'js-facets-checkbox-links';
$build['#attached']['library'][] = 'facets/drupal.facets.checkbox-widget';
return $build;
}
/**
* {@inheritdoc}
*/
protected function buildListItems(ResultInterface $result) {
$items = parent::buildListItems($result);
$items['#attributes']['data-drupal-facet-item-id'] = $this->facet->getUrlAlias() . '-' . $result->getRawValue();
return $items;
}
}
......@@ -4,95 +4,59 @@ namespace Drupal\facets\Plugin\facets\widget;
use Drupal\Core\Form\FormStateInterface;
use Drupal\facets\FacetInterface;
use Drupal\facets\Result\Result;
use Drupal\facets\Widget\WidgetPluginBase;
/**
* The dropdown widget.
*
* @FacetsWidget(
* id = "select",
* id = "dropdown",
* label = @Translation("Dropdown"),
* description = @Translation("A configurable widget that shows a dropdown."),
* )
*/
class DropdownWidget extends LinksWidget {
class DropdownWidget extends WidgetPluginBase {
/**
* The facet the widget is being built for.
*
* @var \Drupal\facets\FacetInterface
* {@inheritdoc}
*/
protected $facet;
public function defaultConfiguration() {
return [
'default_option_label' => $this->t('Choose'),
] + parent::defaultConfiguration();
}
/**
* {@inheritdoc}
*/
public function build(FacetInterface $facet) {
$this->facet = $facet;
/** @var \Drupal\facets\Result\Result[] $results */
$results = $facet->getResults();
$items = [];
$configuration = $facet->getWidgetConfigs();
$this->showNumbers = empty($configuration['show_numbers']) ? FALSE : (bool) $configuration['show_numbers'];
$this->defaultOptionLabel = isset($configuration['default_option_label']) ? $this->t($configuration['default_option_label']) : '';
foreach ($results as $result) {
if (is_null($result->getUrl())) {
$text = $this->extractText($result);
$items[] = ['#markup' => $text];
}
else {
$items[] = $this->buildListItems($result);
}
}
$build = [
'#theme' => 'item_list',
'#items' => $items,
'#attributes' => ['class' => ['js-facets-dropdown-links'], 'data-facet-default-option-label' => $this->defaultOptionLabel],
'#cache' => [
'contexts' => [
'url.path',
'url.query_args',
],
],
];
$build = parent::build($facet);
$build['#attributes']['class'][] = 'js-facets-dropdown-links';
$build['#attributes']['data-facet-default-option-label'] = $this->getConfiguration()['default_option_label'];
$build['#attached']['library'][] = 'facets/drupal.facets.dropdown-widget';
return $build;
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state, $config) {
public function buildConfigurationForm(array $form, FormStateInterface $form_state, FacetInterface $facet) {
$config = $this->getConfiguration();
$message = $this->t('This widget requires "Make sure only one result can be shown." to be enabled to behave as a standard dropdown.');
$form['warning'] = [
'#markup' => '<div class="messages messages--warning">' . $message . '</div>',
];
$form['show_numbers'] = [
'#type' => 'checkbox',
'#title' => $this->t('Show the amount of results'),
];
$form += parent::buildConfigurationForm($form, $form_state, $facet);
$form['default_option_label'] = [
'#type' => 'textfield',
'#title' => $this->t('Default option label'),
'#default_value' => $config['default_option_label'],
];
if (!is_null($config)) {
$widget_configs = $config->get('widget_configs');
if (isset($widget_configs['show_numbers'])) {
$form['show_numbers']['#default_value'] = $widget_configs['show_numbers'];
}
if (isset($widget_configs['default_option_label'])) {
$form['default_option_label']['#default_value'] = $widget_configs['default_option_label'];
}
}
return $form;
}
......
......@@ -2,13 +2,9 @@
namespace Drupal\facets\Plugin\facets\widget;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Link;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\facets\FacetInterface;
use Drupal\facets\Result\ResultInterface;
use Drupal\facets\Widget\WidgetInterface;
use Drupal\facets\Widget\WidgetPluginBase;
/**
* The links widget.
......@@ -19,174 +15,41 @@ use Drupal\facets\Widget\WidgetInterface;
* description = @Translation("A simple widget that shows a list of links"),
* )
*/
class LinksWidget implements WidgetInterface {
use StringTranslationTrait;
class LinksWidget extends WidgetPluginBase {
/**
* A flag that indicates if we should display the numbers.
*
* @var bool
* {@inheritdoc}
*/
protected $showNumbers = FALSE;
public function defaultConfiguration() {
return ['soft_limit' => 0] + parent::defaultConfiguration();
}
/**
* {@inheritdoc}
*/
public function build(FacetInterface $facet) {
/** @var \Drupal\facets\Result\Result[] $results */
$results = $facet->getResults();
$items = [];
$configuration = $facet->getWidgetConfigs();
$this->showNumbers = empty($configuration['show_numbers']) ? FALSE : (bool) $configuration['show_numbers'];
foreach ($results as $result) {
if (is_null($result->getUrl())) {
$text = $this->extractText($result);
$items[] = ['#markup' => $text];
}
else {
$items[] = $this->buildListItems($result);
}
}
$build = [
'#theme' => 'item_list',
'#items' => $items,
'#attributes' => ['data-drupal-facet-id' => $facet->id()],
'#cache' => [
'contexts' => [
'url.path',
'url.query_args',
],
],
];
$build = parent::build($facet);
if (!empty($configuration['soft_limit'])) {
$build['#attached']['library'][] = 'facets/soft-limit';
$build['#attached']['drupalSettings']['facets']['softLimit'][$facet->id()] = (int) $configuration['soft_limit'];
}
return $build;
}
/**
* Builds a renderable array of result items.
*
* @param \Drupal\facets\Result\ResultInterface $result
* A result item.
*
* @return array
* A renderable array of the result.
*/
protected function buildListItems(ResultInterface $result) {
$classes = ['facet-item'];
if ($children = $result->getChildren()) {
$items = $this->prepareLink($result);
$children_markup = [];
foreach ($children as $child) {
$children_markup[] = $this->buildChildren($child);
}
$classes[] = 'expanded';
$items['children'] = [$children_markup];
if ($result->isActive()) {
$items['#attributes'] = ['class' => 'active-trail'];
}
}
else {
$items = $this->prepareLink($result);
if ($result->isActive()) {
$items['#attributes'] = ['class' => 'is-active'];
}
}
$items['#wrapper_attributes'] = ['class' => $classes];
return $items;
}
/**
* Returns the text or link for an item.
*
* @param \Drupal\facets\Result\ResultInterface $result
* A result item.
*
* @return array
* The item, as a renderable array.
*/
protected function prepareLink(ResultInterface $result) {
$text = $this->extractText($result);
if (is_null($result->getUrl())) {
$link = ['#markup' => $text];
}
else {
$link = new Link($text, $result->getUrl());
$link = $link->toRenderable();
}
return $link;
}
/**
* Builds a renderable array of a result.
*
* @param \Drupal\facets\Result\ResultInterface $child
* A result item.
*
* @return array
* A renderable array of the result.
*/
protected function buildChildren(ResultInterface $child) {
$text = $this->extractText($child);
if (!is_null($child->getUrl())) {
$link = new Link($text, $child->getUrl());
$item = $link->toRenderable();
}
else {
$item = ['#markup' => $text];
}
$item['#wrapper_attributes'] = ['class' => ['leaf']];
return $item;
}
/**
* {@inheritdoc}
*
* @todo This is inheriting nothing. We need a method on the interface and,
* probably, a base class.
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state, $config) {
$widget_configs = !is_null($config) ? $config->get('widget_configs') : [];
// Assure sane defaults.
// @todo This should be handled upstream, in facet entity. Facet schema