En la primera parte de este tutorial os explicaba qué son y cómo poder crear nuestros propios custom handler para webform, así que, en esta parte vamos a ver como poder utilizarlos.

Para eso vamos a usar un ejemplo sencillo que nos servirá para intentar ver, de forma resumida, todo lo que nos ofrece esta funcionalidad.

NUESTRO OBJETIVO

Antes de empezar a programar, vamos a intentar entender que es lo que vamos a realizar y para ello voy a resumir en unas lineas cuál será la problemática y cómo lo vamos a solucionar.

Imaginemos que tener que programar un formulario que tiene, entre otros, dos campos fechas y queremos verificar que una de las fechas sea posterior a la otra. Algo así como haría un sistema de booking clásico.

Al realizar un handler genérico no podemos usar un nombre de fecha en concreto, dicho de otro modo, no podemos hardcodear el nombre de los campos que vamos a utilizar porque desde el punto de vista del handler no sabremos cual es.

Esto significa que vamos a tener que decirle a nuestro handler, de algún modo, cuáles son los campos que queremos comparar.

Y para esto webform handler tiene un método que nos ayudará a definir un formulario de configuración al estilo típico de drupal.

DEFINIENDO NUESTRO MÉTODO DE CONFIGURACIÓN

Para poder definir nuestro formulario vamos a declarar los siguientes métodos:

  • create : Nos permitira crear una instancia única para nuestro handler.
  • defaultConfiguration : Nos servirá para definir cuál serán los valores de nuestro formulario de configuración por defecto.
  • buildConfigurationForm : Creamos el array con los campos de nuestro formulario de configuración.
  • submitConfigurationForm : Nos permitirá guardar la configuración de forma individual por cada declaración del handler.
  • getWebformFields : Este es un método custom que he creado para obtener la lista de todos los campos creados desde la parte «Build» del formulario de webform.
				
					use Drupal\Core\Form\FormStateInterface;
use Drupal\webform\Plugin\WebformHandlerBase;
use Drupal\webform\WebformSubmissionInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
				
			
				
					    /**
     * The token manager.
     *
     * @var \Drupal\webform\WebformTokenManagerInterface
     */
    protected $tokenManager;

    /**
     * {@inheritdoc}
     */
    public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
        $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
        $instance->tokenManager = $container->get('webform.token_manager');
        return $instance;
    }
    
    /**
     * {@inheritdoc}
     */
    public function defaultConfiguration() {
        return [
            'date_ini' => '',
            'date_end' => '',
            'comparison' => '',
            'error_message' => '',
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function buildConfigurationForm(array $form, FormStateInterface $form_state) {

        $webform_fields = $this->getWebformFields(); // Metodo custom que nos permite recoger todos los campos creados en el formulario

        // Message.
        $form['fields_fieldset'] = [
            '#type' => 'details',
            '#title' => $this->t('Matching Fields'),
            '#open' => FALSE,
        ];


        $form['fields_fieldset']['date_ini'] = [
            '#type' => 'select',
            '#title' => $this->t('Date one'),
            '#default_value' => $this->configuration['date_ini'],
            '#options' => $webform_fields,
            '#empty_option' => $this->t('-- Select one field --'),
            '#required' => TRUE,
        ];

        $form['fields_fieldset']['comparison'] = [
            '#type' => 'select',
            '#title' => $this->t('Comparison'),
            '#default_value' => $this->configuration['comparison'],
            '#empty_option' => $this->t('-- Select one field --'),
            '#options' => [
                '==' => "==",
                '<' => "<",
                '<=' => "<=",
                '>=' => ">=",
                '>' => ">",
            ],
            '#required' => TRUE,
        ];

        $form['fields_fieldset']['date_end'] = [
            '#type' => 'select',
            '#title' => $this->t('Date two'),
            '#default_value' => $this->configuration['date_end'],
            '#empty_option' => $this->t('-- Select one field --'),
            '#options' => $webform_fields,
            '#required' => TRUE,
        ];

        $form['config_fieldset']['error_message'] = array(
            '#type' => 'textfield',
            '#title' => t('Error message'),
            '#default_value' => $this->configuration['error_message'],
            '#size' => 60,
            '#maxlength' => 128,
            '#required' => TRUE,
        );

        return $this->setSettingsParents($form);
    }
    
    /**
     * {@inheritdoc}
     */
    public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
        parent::submitConfigurationForm($form, $form_state);
        $this->applyFormStateToConfiguration($form_state);
    }
    
    private function getWebformFields() {

        foreach ($this->getWebform()->getElementsDecoded() as $key => $fieldEncode) {
            $fields[$key] = $fieldEncode["#title"];
        }
        return $fields;
    }
				
			

Como podréis ver en el código de arriba, he creado un campo llamado «comparison» y otro llamado «error_message», esto se debe a que como es un handler generico, podremos definir tanto el operador de comparación que queremos usar, como el mensaje de error en caso de que el usuario ponga fechas incorrectas.

Otro punto que podréis detectar, es como en ningún momento hemos definido ningún campos del formulario del usuario en concreto si no que hemos creado un sistema en el cual mediante la configuración del handler podremos definir qué campos usar.

APLICANDO NUESTRA LÓGICA DE COMPARACIÓN

Una vez, hemos definido nuestra configuración vamos a aplicar nuestra lógica que nos ayudará a comparar  los campos seleccionados y actuar en consecuencia.

Y para esto, vamos a usar el método «validateForm», el cual nos permitirá obtener los datos rellenados por el usuario en el formulario y aplicar nuestra lógica de validación.

				
					/**
     * {@inheritdoc}
     */
    public function validateForm(array &$form, FormStateInterface $form_state, WebformSubmissionInterface $webform_submission) {

        parent::validateForm($form, $form_state, $webform_submission);

        if (!$form_state->hasAnyErrors()) {

            $date1 = $form_state->getValue($this->configuration['date_ini']);
            $date2 = $form_state->getValue($this->configuration['date_end']);
            $comparison = $this->configuration['comparison'];
            $message = $this->configuration['error_message'];

            if ($date1 && $date2) {
                $result = TRUE;

                switch ($comparison) {
                    case "==":
                        $result = ($date1 == $date2) ? TRUE : FALSE;
                        break;
                    case "<=":
                        $result = ($date1 <= $date2) ? TRUE : FALSE;
                        break;
                    case "<":
                        $result = ($date1 < $date2) ? TRUE : FALSE;
                        break;
                    case ">=":
                        $result = ($date1 >= $date2) ? TRUE : FALSE;
                        break;
                    case ">":
                        $result = ($date1 > $date2) ? TRUE : FALSE;
                        break;
                }

                if ($result == FALSE) {
                    $form_state->setErrorByName($this->configuration['date_end'], $message);
                }
            }

        }
    }
				
			

AÑADIENDO NUESTRO HANDLER

Ahora que ya tenemos todo nuestro código listo, probaremos a añadir nuestro handler al formulario que queramos, eso si, aseguraos primero que el formulario contiene al menos dos campos de tipo fecha.

Ahora sí, ya estamos listos para añadir nuestro handler custom que nos permita comparar las fechas introducidas.

Si nos dirigimos al formulario al cual hemos añadido nuestro handler y rellenamos el formulario con datos erróneos, podremos ver como el formulario llama a nuestro handler y nuestro código lanza el mensaje de error que pusimos en la configuración del handler casi como por arte de magia.

TRADUCIENDO NUESTRO MENSAJE DE ERROR

Como no podría ser de otra forma, al trabajar con idiomas diferentes al inglés o incluso al crear páginas multi-idiomas, nos encontramos con el tópico de siempre que es el de poder traducir incluso los errores.

Pero podéis estar tranquilos, porque también os traigo la solucion a este problema, bueno, en realidad webform trae una solución a este problema, pero tratad de no perderos porque este paso puede resultar algo lioso.

Por defecto, webform trae un sistema de traducción de formularios, el cual nos permite traducir prácticamente cualquier configuración del formulario, pero para los handlers custom tendremos que decirle explícitamente que campos de nuestro handler queremos que sean traducibles.

Para poder decirle a webform que tenemos un campo de nuestra configuración que queremos que sea traducible vamos a tener que crear el archivo MI_MODULO.schema.yml dentro de nuestro módulo custom. 

En nuestro ejemplo quedaria así:
«handler_custom» >> «config» >> «schema» >> «handler_custom.schema.yml»

				
					webform.handler.handler_custom:
  type: mapping
  label: 'Date Validation'
  mapping:
    error_message:
      label: Message
      type: label
				
			

Es importante que entendáis bien como funciona este archivo porque puede parecer algo complejo, básicamente solo teneis que cambiar «handler_custom» por el nombre de vuestro handler (el campo de anotación «id» que usasteis en la definición de la clase/handler ).

Tambien deberéis modificar «error_message» por el nombre del campo que hayas definido en vuestra configuración.

El label no es mas que el título que saldrá en la parte de la traducción para que sepais que estais traduciendo.

Una vez creado el archivo, si todo fue correctamente y habéis limpiado caches, deberiais de ver vuestro campo de configuración listo para ser traducido.

COMPOSICION FINAL DE NUESTRO HANDLER

A Continuación os dejo la clase completa de nuestro handler para que os sea mas fácil de ver todo junto.

				
					namespace Drupal\handler_custom\Plugin\WebformHandler;

use Drupal\Core\Form\FormStateInterface;
use Drupal\webform\Plugin\WebformHandlerBase;
use Drupal\webform\WebformSubmissionInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;


/**
 * Webform Date Validation handler.
 *
 * @WebformHandler(
 *   id = "handler_custom",
 *   label = @Translation("My awesome custom handler"),
 *   category = @Translation("Custom"),
 *   description = @Translation("Example of custom handler."),
 *   cardinality = \Drupal\webform\Plugin\WebformHandlerInterface::CARDINALITY_UNLIMITED,
 *   results = \Drupal\webform\Plugin\WebformHandlerInterface::RESULTS_PROCESSED,
 *   submission = \Drupal\webform\Plugin\WebformHandlerInterface::SUBMISSION_OPTIONAL,
 * )
 */
class HandlerCustomHandler extends WebformHandlerBase {
    
    /**
     * The token manager.
     *
     * @var \Drupal\webform\WebformTokenManagerInterface
     */
    protected $tokenManager;

    /**
     * {@inheritdoc}
     */
    public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
        $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
        $instance->tokenManager = $container->get('webform.token_manager');
        return $instance;
    }

    /**
     * {@inheritdoc}
     */
    public function defaultConfiguration() {
        return [
            'date_ini' => '',
            'date_end' => '',
            'comparison' => '',
            'error_message' => '',
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function buildConfigurationForm(array $form, FormStateInterface $form_state) {

        $webform_fields = $this->getWebformFields();

        // Message.
        $form['fields_fieldset'] = [
            '#type' => 'details',
            '#title' => $this->t('Matching Fields'),
            '#open' => FALSE,
        ];


        $form['fields_fieldset']['date_ini'] = [
            '#type' => 'select',
            '#title' => $this->t('Date one'),
            '#default_value' => $this->configuration['date_ini'],
            '#options' => $webform_fields,
            '#empty_option' => $this->t('-- Select one field --'),
            '#required' => TRUE,
        ];

        $form['fields_fieldset']['comparison'] = [
            '#type' => 'select',
            '#title' => $this->t('Comparison'),
            '#default_value' => $this->configuration['comparison'],
            '#empty_option' => $this->t('-- Select one field --'),
            '#options' => [
                '==' => "==",
                '<' => "<",
                '<=' => "<=",
                '>=' => ">=",
                '>' => ">",
            ],
            '#required' => TRUE,
        ];

        $form['fields_fieldset']['date_end'] = [
            '#type' => 'select',
            '#title' => $this->t('Date two'),
            '#default_value' => $this->configuration['date_end'],
            '#empty_option' => $this->t('-- Select one field --'),
            '#options' => $webform_fields,
            '#required' => TRUE,
        ];

        $form['config_fieldset']['error_message'] = array(
            '#type' => 'textfield',
            '#title' => t('Error message'),
            '#default_value' => $this->configuration['error_message'],
            '#size' => 60,
            '#maxlength' => 128,
            '#required' => TRUE,
        );

        return $this->setSettingsParents($form);
    }

    private function getWebformFields() {

        foreach ($this->getWebform()->getElementsDecoded() as $key => $fieldEncode) {
            $fields[$key] = $fieldEncode["#title"];
        }
        return $fields;
    }

    /**
     * {@inheritdoc}
     */
    public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
        parent::submitConfigurationForm($form, $form_state);
        $this->applyFormStateToConfiguration($form_state);
    }

    /**
     * {@inheritdoc}
     */
    public function validateForm(array &$form, FormStateInterface $form_state, WebformSubmissionInterface $webform_submission) {

        parent::validateForm($form, $form_state, $webform_submission);

        if (!$form_state->hasAnyErrors()) {

            $date1 = $form_state->getValue($this->configuration['date_ini']);
            $date2 = $form_state->getValue($this->configuration['date_end']);
            $comparison = $this->configuration['comparison'];
            $message = $this->configuration['error_message'];

            if ($date1 && $date2) {
                $result = TRUE;

                switch ($comparison) {
                    case "==":
                        $result = ($date1 == $date2) ? TRUE : FALSE;
                        break;
                    case "<=":
                        $result = ($date1 <= $date2) ? TRUE : FALSE;
                        break;
                    case "<":
                        $result = ($date1 < $date2) ? TRUE : FALSE;
                        break;
                    case ">=":
                        $result = ($date1 >= $date2) ? TRUE : FALSE;
                        break;
                    case ">":
                        $result = ($date1 > $date2) ? TRUE : FALSE;
                        break;
                }

                if ($result == FALSE) {
                    $form_state->setErrorByName($this->configuration['date_end'], $message);
                }
            }

        }
    }

}


				
			

CONCLUSION

Como habéis podido ver, no es tan difícil el crear un handler con webform, con apenas unas líneas hemos creado todo un sistema de validación de fechas que se puede reutilizar tantas veces como queramos en tantos formularios como queramos.

Creo que esta funcionalidad aporta una gran mejora a la hora de cómo trabajar con formularios que requieren una tratamiento especial.

Espero que os haya gustado y os sirva y no olvidéis compartir y comentar!