Cómo crear nuestro propio Plugin de Exposed Form para Views en Drupal 8

Recientemente hemos estado trabajando en un proyecto Drupal 8 desarrollando una funcionalidad basada en la API de Views, donde la parte central de la misma gira en torno a un gran buscador con decenas de filtros expuestos donde no todos corresponden a campos de Drupal.

Unos de los muchos requisitos de esta funcionalidad era tener la posibilidad de seleccionar de forma dinámica los campos de la entidad a mostrar en la consulta, pudiendo además indicar cuáles por defecto dentro de la propia View. Para hacer esto posible, tuvimos que desarrollar un Plugin custom de Exposed Form, del que trataremos hoy de forma básica y amena en el siguiente tutorial.

PASO 1

Creamos como siempre un módulo custom con los ficheros necesarios para dar de alta a nuestro plugin, al que llamaremos por ejemplo, my_custom_exposed_form.

  • Empezamos creando el fichero que da de alta el módulo my_custom_exposed_form.info.yml
name: My custom Exposed Form
description: Enables a custom Exposed Form plugin for Views
type: module
core: 8.x
package: Views
dependencies:
  - views
  • Creamos el directorio config/schema y dentro el fichero de configuración my_exposed_form.exposed_form.schema.yml, el cuál servirá para que Drupal de de alta a nuestro plugin. Dentro, debemos indicar el identificador del plugin así como el nombre. La información que sigue después se corresponde al formato y al label para el campo extra que proporciona el exposed form Input Required (del que heredará nuestro plugin de ejemplo) y cuyo contenido es mostrado cuando el usuario aún no ha ejecutado la consulta.
views.exposed_form.my_exposed_form:
  type: views_exposed_form
  label: 'My custom exposed form plugin'
  mapping:
    text_input_required:
      type: text
      label: 'Text on demand'
    text_input_required_format:
      type: string
      label: 'Text on demand format'
  • Creamos el directorio src/Plugin/views/exposed_form e incluimos el fichero MyExposedForm.php, que contendrá nuestra nueva clase MyExposedForm la cual heredará a su vez de InputRequired y no de Basic.
<?php

namespace Drupal\my_custom_exposed_form\Plugin\views\exposed_form;

use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Plugin\views\exposed_form\InputRequired;

/**
 * Exposed form plugin that provides a custom exposed form with required input.
 *
 * @ingroup views_exposed_form_plugins
 *
 * @ViewsExposedForm(
 *   id = "my_exposed_form",
 *   title = @Translation("My custom filters"),
 *   help = @Translation("Provides additional functionalities for exposed filters.")
 * )
 */
class MyExposedForm extends InputRequired { 
   ...
}

PASO 2

Momento de construir nuestro plugin.

  • Declaramos la clase y definimos el método buildOptionsForm(), que se encarga de renderizar el formulario y almacenar la configuración de nuestro plugin. Básicamente, se define un array options que contendrá los nuevos campos del formulario que formará parte de nuestra página de configuración del plugin. Por otra parte, la variable existing_settings, contiene los valores de configuración previamente existentes y será usado para asignar los valores por defecto.

    En nuestro ejemplo, la funcionalidad de escoger dinámamente los campos a mostrar solo tiene sentido cuando se está usando un display de campos y por eso se añade esa condición previamente. Será el campo de tipo select default_fields (insertado dentro de un fieldset) el que nos permitirá elegir los campos por defecto a mostrar. Su propiedad #options se cargará con un array de elementos cuya tupla contiene la key y el label de esos campos añadidos previamente en el apartado display de la view.

  /**
   * @inheritdoc
   */
  public function buildOptionsForm(&$form, FormStateInterface $form_state) {

    parent::buildOptionsForm($form, $form_state);

    $options = [];
    $existing_settings = $this->options['my_settings'];

    // Display options only when using fields
    if ($this->view->getDisplay($this->view->current_display)->usesFields()) {
      
      $options['display_options'] = [
        '#type' => 'fieldset',
        '#title' => $this->t('Display options'),
      ];
      
      // Get an array with all the fields of the view (key & label)
      $display_fields = $this->getDisplayFields();

      $options['display_options']['default_fields'] = [
        '#type' => 'select',
        '#multiple' => TRUE,
        '#required' => TRUE,
        '#options' => $display_fields,
        '#title' => $this->t('Default fields to display'),
        '#description' => $this->t('You must select at least one field to display for the view.'),
        '#default_value' => $existing_settings ? $existing_settings['display_options']['default_fields'] : array_keys($display_fields)
      ];
    }

    $form['my_settings'] = $options;
  }

  /**
   * {@inheritdoc}
   */
  protected function getDisplayFields() {
    $fields = [];
    $view_fields = $this->view->getHandlers('field', $this->view->current_display);
    foreach ($view_fields as $key => $value) {
      $fields['col_' . $key] = $value['label'];
    }
    return $fields;
  }
  • Tras habilitar el módulo, nos vamos a la configuración de la view, al apartado Exposed Form situado en Advanced y elegimos el nuevo plugin. Si accedemos a Parámetros, podremos ver el nuevo campo situado al final del formulario listando todos los campos previamente añadidos en el apartado de Display

  • Para validar el formulario o realizar acciones tras su envío, se pueden implementar los métodos validate y submit. Aquí un ejemplo simple de validación.
  /**
   * {@inheritdoc}
   */
  public function validateOptionsForm(&$form, FormStateInterface $form_state) {

    $my_settings = $form_state->getValue(['exposed_form_options', 'my_settings']);

    foreach ($my_settings as $id => $settings) {
      if ($id == 'display_options') {
        if (empty($settings['default_fields'])) {
          $form_state->setError($form['my_settings'][$id], $this->t('You must select at least 1 field.', ['%name' => $id]));
        }
      }
    }

    parent::validateOptionsForm($form, $form_state);
  }
  • LLegados a este punto, es necesario renderizar el formulario expuesto con los campos necesarios que interactuarán con el usuario. Lo haremos mediante el método exposedFormAlter(). En este caso, necesitamos dos campos. El primero es un select list para escoger de forma personalizada los campos a visualizar, mientras que el segundo es un simple checkbox que luego si el usuario marca, hará que se muestren absolutamente todos los campos que se hayan incluido en la view.
/**
   * {@inheritdoc}
   */
  public function exposedFormAlter(&$form, FormStateInterface $form_state) {

    parent::exposedFormAlter($form, $form_state);

    // Get my exposed form settings
    $my_settings = $this->options['my_settings'];

    // Get user input
    $user_options = $form_state->getUserInput();

    // Add a custom class for the exposed form
    $form['#attributes']['class'][] = 'my-custom-exposed-form';

    // Add two exposed custom fields to let user decide the fields of the entity to display
    if ($this->view->getDisplay($this->view->current_display)->usesFields()) {

      // Container
      $form['columns_display'] = [
        '#type' => 'container',
        '#attributes' => [
          'class' => ['columns-display-options']
        ],
      ];

      // Select to choose custom fields, by default, the ones selected on My Exposed Form settings page
      $form['columns_display']['custom_cols'] = [
        '#type' => 'select',
        '#chosen' => TRUE,
        '#multiple' => TRUE,
        '#options' => $this->getDisplayFields(),
        '#title' => $this->t('Choose the columns to display')
      ];

      // Set value
      if (!isset($user_options['custom_cols'])) {
        $form['columns_display']['custom_cols']['#value'] = array_keys($my_settings['display_options']['default_fields']);
      }

      // Or checkbox to display all the fields added on the display section of the view
      $form['columns_display']['all_cols'] = [
        '#type' => 'checkbox',
        '#title' => $this->t('Or display all columns'),
        '#default_value' => 0
      ];

      // Set value
      if (isset($user_options['all_cols'])) {
        $form['columns_display']['all_cols']['#value'] = $user_options['all_cols'];
      }

    }
  }

  • Finalmente hay que implementar la lógica del renderizado de campos. Para ello disponemos del método preRender(). Si el usuario no ha marcado la opción de mostrar todos los campos, los campos a mostrar dependerán de si el input es nulo o no, resultando este último caso en la carga de campos por defecto. Después se hace una comprobación de campos y se eliminan de la view los que no sean necesarios. Comentar también que tenemos a nuestra disposición otros métodos tales como preExecute(), postExecute(), etc.
  /**
   * {@inheritdoc}
   */
  public function preRender($values) {

    if ($this->view->getDisplay($this->view->current_display)->usesFields()) {

      // Get user input
      $user_input = $this->view->getExposedInput();

      // Get My exposed form settings
      $my_settings = $this->options['my_settings'];

      // Get the fields added in the view
      $view_fields = $this->view->getHandlers('field', $this->view->current_display);

      // When all_fields is not enabled, render the fields given by the user input.
      // And when that case is empty, then render the fields of My Exposed Form settings page.
      if (!isset($user_input['all_cols'])) {
        if (isset($user_input['custom_cols'])) {
          $fields_to_render = $user_input['custom_cols'];
        } else {
          $fields_to_render = $my_settings['display_options']['default_fields'];
        }
        foreach ($view_fields as $key => $value) {
          if (!in_array('col_' . $key, $fields_to_render)) {
            unset($this->view->field[$key]);
          }
        }
      }
      // Else, render all
    }

  }


  /**
   * {@inheritdoc}
   */
  public function preExecute() {}


  /**
   * {@inheritdoc}
   */
  public function postExecute() {}

 

Y hasta aquí ha llegado por hoy el tutorial sobre cómo crear un Plugin custom de Exposed Form para Drupal 8, el cual espero os haya resultado cuanto menos interesante. Como siempre, podéis encontrar más información en la documentación oficial (InputRequited Doc). Por supuesto, se aceptan preguntas y sugerencias a través de los comentarios.

Hasta la próxima ;)

Contacte

T'interessen els nostres serveis?

Contacta'ns