Form API (III): Ejemplo de modificación de formulario en Drupal 8

@davidjguru
Drupal & /me
Published in
11 min readOct 14, 2018

Un ejemplo real de intervención sobre un formulario de Drupal 8.

“Crab and lobster traps stacked on top of one another with rope tied to them in Cascais” by Erwan Hesry on Unsplash

Cada vez que cierro un artículo de esta serie sobre formularios en Drupal 8 veo que me he dejado fuera mil cosas por explicar, ejemplos por compartir y demás, así que inmediatamente pienso en chutar la pelota otra vez hacia delante. Es una especie de sensación que aparece justo al terminar, darle al botón de publicar y empezar a pensar “vaya en realidad sobre esto se puede profundizar más” o “mierda, no he puesto este caso que tengo anotado” y cosas así.

Así que en parte convocado por una especie de pequeña y recalcitrante neurosis obsesiva que me impulsa a seguir dándole a la tecla y en parte motivado por el deseo de convertirme en un multimillonario degenerado(perdonen la tautología) gracias a Drupal, voy a abrir una tercera pieza sobre el trabajo diario con formularios dentro del contexto de Drupal 8.

En esta ocasión, me apetecía complementar las entregas anteriores sobre la misma mandanga con un caso real y práctico que sirviese como “asiento real” a los conceptos, métodos y mecánicas que hemos ido comentando. Algo del día a día que venga a dar sentido práctico.

He pensado que manteniendo la línea didáctica que llevamos podría ser algo sencillo, abarcable en un tipo de artículo como este y que mostrase la relativa complejidad que podría alcanzar el tema. Sinceramente, espero haber acertado.

Que ustedes lo disfruten.

Índice: en este artículo
1- Introducción
2- Rebobinamos: Retrospectiva rápida
3- Ahora sí: veamos un ejemplo
4- Repasamos los pasos y los comentamos amistosamente
Este artículo viene de:
Form API(II):Modificando formularios en Drupal 8 mediante form_alter
Este artículo continúa en:Otros artículos de la serie:
Form API(I): Comprender, crear y modificar formularios en Drupal 8
Form API(II): Modificando formularios en Drupal8 mediante form_alter
Otros artículos que podrían interesarte:
Drupal Fast Tips (I) - Using links in Drupal 8
Docker, Docker-Compose and DDEV - Cheatsheet
OpenEuropa - The European Commission in Drupal 8
Development environments for Drupal with DDEV

1- Introducción

En el artículo anterior, vimos algunas claves para realizar intervenciones sobre formularios en Drupal: algunos hooks básicos para modificaciones de elementos, la inclusión de comportamientos dinámicos mediante #states y algunas cuestiones de contexto más o menos interesantes.

En este tercer capítulo, daremos un paso más tomando todos esos fundamentos y concretándolos en un pequeño ejemplo práctico que aunque de poco alcance, creo que puede ser útil para conocer mejor la filosofía detrás de algunos subsistemas de Drupal y probar algunas tácticas útiles en el día a día. Como siempre, estamos usando la Form API de Drupal como un MacGuffing para aprender a pensar en términos Drupalianos y mejorar nuestro Kung-Fú.

En este caso, vamos a asumir ya de partida un par de principios, que como hacemos con la Ciencia de Ficción, nos servirán para conectar mejor entre emisor y receptor: establezcamos nuestro propio pacto de incredulidad, que consta de dos puntos principales:

  1. Interpretaremos siempre que “la tarea” nos llega y no tenemos más contexto de ese proyecto, ni sabemos nada más. Visión tunel sobre el caso específico que repasemos.
  2. En el contexto de Drupal, cada tarea puede resolverse de varias maneras diferentes. Siempre existen caminos diferentes. Por lo general, usaremos la forma más didáctica, es decir, la que puede ser más útil para transmitir cómo funciona algo, a pesar de que esta no sea la más óptima (AKA “puedo hacer esto en una sola línea de código” vs “pero si lo hago en tres o cuatro te muestro más explicitamente que está ocurriendo”).

Si estamos de acuerdo en asumir estas premisas, pasemos a los siguientes apartados.

Realmente: ¿Hay tanto por saber de formularios?

Sí, mucho. Existen muchos recursos directos para operar con formularios y además, otro tipo de recursos para operar conectando la FormAPI con otros recursos y subsistemas del universo de Drupal. Comprender el funcionamiento y la filosofía del subsistema encargado de la gestión de formularios facilita la transición para entender como funciona -por ejemplo- el subsistema encargado de procesar arrays estructurados para convertirlos en HTML renderizable y mostrado en pantalla, esto es, lo que conocemos como la RenderAPI de Drupal+.

Igualmente, los conceptos de Hooks, métodos, clases, Plugins, Widgets, Formatters, Servicios, etc…pueden ser fácilmente aproximados desde el contexto del uso de formularios, con lo que se pueden trabajar dichos elementos dentro del contexto relativamente controlable e intuitivo de los formularios. Como argumentaba en el inicio de esta serie de artículos, la FormAPI puede ser uno de los mejores vectores de entrada al funcionamiento de Drupal.

2- Rebobinamos: Retrospectiva rápida

En esta sección siempre me gusta relacionar lo que vamos a ver con lo que ya hemos visto anteriormente, para situarnos mejor y hacer un poco de retrospectiva acerca de lo que ya tenemos trabajado.

En ese sentido, conocemos los fundamentos de un formulario en Drupal:

y también tenemos experiencia en intervenir sobre un formulario Drupal:

Así que para la siguiente intervención, tenemos suficientes recursos en nuestra caja de herramientas como para afrontarlo exitosamente. Veamos.

3- Ahora sí: veamos un ejemplo

Un buen día nos llega una petición de un proyecto que no conocíamos (es un decir, recordad el pacto de supresión de la incredulidad): existe un formulario que se está mostrando incorrectamente dentro de una plataforma basada en Drupal 8. Ok.

El proyecto en cuestión consiste en una herramienta de matching entre personas usuarias y oferta formativa, para lo cual recopila información personal de los usuarios mediante formularios. Existe un formulario para registrar la situación profesional que no está implementado como se necesita.

Tiene las opciones mezcladas, faltan casos y además los títulos son parte de las mismas opciones. Cuando nos asomamos, vemos esto:

Parece ser que incluye el típico valor por defecto N/A de radiobutton, y además muestra en un único bloque lo que debería mostrar en dos secciones diferenciadas: La de (a)Desempleado y la de (b)Empleado, cada una con sus opciones.

En definitiva, la intervención consiste en pasar de ese cuadro anterior a algo parecido a este mockup:

Lo que necesitamos, en resumidas cuentas, es transformar una estructura actual parecida a esta:

<form name="SituacionProfesional">
<input type=”radio” name=”opt” value=”1”> One<br>
<input type=”radio” name=”opt” value=”2”> Two<br>
<input type=”radio” name=”opt” value=”3”> Three<br>
<input type=”radio” name=”opt” value=”4”> Four<br>
<input type=”radio” name=”opt” value=”5”> Five<br>
</form>

En otra más parecida a esta (que incluirá luego otros atributos y elementos como labels e ids):

<form name="SituacionProfesional">
<fieldset>
<input type=”radio” name=”opt” value=”1” /> One<br>
<input type=”radio” name=”opt” value=”2” /> Two<br>
<input type=”radio” name=”opt” value=”3” /> Three<br>
<input type=”radio” name=”opt” value=”4” /> Four<br>
<input type=”radio” name=”opt” value=”5” /> Five<br> </fieldset>
<fieldset>
<input type=”radio” name=”opt” value=”6” /> Six<br>
<input type=”radio” name=”opt” value=”7” /> Seven<br>
<input type=”radio” name=”opt” value=”8” /> Eight<br>
<input type=”radio” name=”opt” value=”9” /> Nine<br>
<input type=”radio” name=”opt” value=”10” /> Ten<br>
</fieldset>
</form>

Y a modo de resumen, tenemos un esquema de la intervención que debemos acometer:

Portal web basado en Drupal 8
Propósito del Sitio Web:
Relacionar usuarios con cursos a través de solicitudes.
Problema a resolver:
Un formulario de usuario muestra sus opciones unificadas.
Objetivos:
1- Reestructurar el form de Situación Profesional dentro del perfil de usuario para mostrar las opciones de manera separada por bloques de fieldsets diferenciados.
2- Alterar la mecánica del formulario para procesar los valores y construir el envío tal y como lo espera Drupal a partir de procesar la nueva estructura.3- Asegurar la Inserción / Recuperación de valores desde base de datos.

De acuerdo…¿Qué opciones tenemos? en primer lugar, no sé vosotres pero en mi caso al menos -con mi mente sucia- la primera idea que surge es:

1- Ocultar por CSS: Tapar ementos de la vista por CSS puede ser una idea valorable en ciertos escenarios muy determinados y controlados pero no en este caso. Los ScreenReaders (SR) o lectores de pantalla requieren entender bien la semántica de la información mostrada en pantalla para interpretarla correctamente en el caso de una web abierta a la navegación en general.

Interpretando la página sin CSS, lo que obtendríamos sería la misma semántica que pretendemos modificar, lo cual es un problema grave de accesibilidad (además de una opción demasiado churretosa).

2- Atributos Hidden: Podemos ocultar completamente los elementos que no nos interesan mediante el uso de atributos “hidden”+ en CSS, lo cual sigue siendo semánticamente incorrecto. Teniendo en cuenta que tenemos “títulos” mostrados en la lista de elementos originales, sería un cachondeo ocultar radio buttons y no ocultar sus labels que además van referenciando a cada elemento, con lo cual termina siendo una opción bastante churretosa.

En resumen, hay que intervenir a nivel de Hook para modificar el campo, o más bien su “Widget” (aunque todavía no me he parado mucho a comentar los conceptos básicos de la terna formada por Widget, Formatter y Field).
Existen varios Hooks o métodos ofrecidos por Drupal que podrían servirnos pero creo que lo más didáctico podría ser usar el de propósito general, el hook_form_alter(), que sirve para agrupar modificaciones sobre un formulario. Hay otros Hooks más específicos como el hook_field_widget_form_alter() que se dedica específicamente a modificar widgets de campo, pero nos quedaremos ahora con el general. Tenemos que modificar un Widget (esto es, la manera que tiene un campo de ofrecer una interfaz al usuario para que introduzca los datos) y ahora la solución pasa por crear nuestro propio módulo e integrar esos cambios dentro de un método hook_form_alter().

Vamos a seguir paso a paso la mecánica de la solución. Aviso que como solución es algo tosca y no resulta nada depurada, pero creo que así es más visual, más intuitiva. Esto puede resolverse de otras formas más rápidas, pero me interesaba resaltar el proceso, la lógica mental que sigue.

<?php/**
*Implements hook_form_alter().
*/
function alterforms_form_alter(&$form, $form_state, $form_id){ // 1- Comprueba el id del formulario
switch ($form_id){
case "user_situa_form":
$user = \Drupal::currentUser()->id();
// 2- Cargamos un nuevo método de validación para el form
$form['#validate'][] = 'user_situa_form_validate';
// 3- Reconstruimos el formulario como nos han pedido $form['field_situac'] = array(
'#tree' => TRUE
);
$form['field_situac']['desempleado'] = array(
'#type' => 'fieldset',
'#title' => t('DESEMPLEADO'),
'#collapsible' => TRUE,
'#collapsed' => TRUE
);
$form['field_situac']['desempleado']['desempleadouno']= array(
'#type' => 'radio',
'#title' => t('Desempleado opción uno'),
'#default_value' => 0,
'#return_value' => '2',
'#attributes' => array('name' => 'situac', 'checked' => "checked")
);
$form['field_situac']['desempleado']['desempleadodos']= array(
'#type' => 'radio',
'#title' => t('Desempleado opción dos'),
'#default_value' => 0,
'#return_value' => '3',
'#attributes' => array('name' => 'situac')
);
$form['field_situac']['desempleado']['desempleadotres']= array(
'#type' => 'radio',
'#title' => t('Desempleado opción tres'),
'#default_value' => 0,
'#return_value' => '4',
'#attributes' => array('name' => 'situac')
);
$form['field_situac']['desempleado']['desempleadocuatro']= array(
'#type' => 'radio',
'#title' => t('Desempleado opción cuatro'),
'#default_value' => 0,
'#return_value' => '5',
'#attributes' => array('name' => 'situac')
);
$form['field_situac']['activo'] = array(
'#type' => 'fieldset',
'#title' => t('ACTIVO, OCUPADO EN:'),
'#collapsible' => TRUE,
'#collapsed' => TRUE
);
$form['field_situac']['activo']['activouno']= array(
'#type' => 'radio',
'#title' => t('Empleado opción uno'),
'#default_value' => 0,
'#return_value' => '6',
'#attributes' => array('name' => 'situac')
);
$form['field_situac']['activo']['activodos']= array(
'#type' => 'radio',
'#title' => t('Empleado opción dos'),
'#default_value' => 0,
'#return_value' => '7',
'#attributes' => array('name' => 'situac')
);
$form['field_situac']['activo']['activotres']= array(
'#type' => 'radio',
'#title' => t('Empleado opción tres'),
'#default_value' => 0,
'#return_value' => '8',
'#attributes' => array('name' => 'situac')
);
$form['field_situac']['activo']['activocuatro']= array(
'#type' => 'radio',
'#title' => t('Empleado opción cuatro'),
'#default_value' => 0,
'#return_value' => '9',
'#attributes' => array('name' => 'situac')
);
$form['field_situac']['activo']['activocinco']= array(
'#type' => 'radio',
'#title' => t('Empleado opción cinco'),
'#default_value' => 0,
'#return_value' => '10',
'#attributes' => array('name' => 'situac')
);
$form['field_situac']['activo']['activoseis']= array(
'#type' => 'radio',
'#title' => t('Empleado opción seis'),
'#default_value' => 0,
'#return_value' => '11',
'#attributes' => array('name' => 'situac')
);
$form['field_situac']['activo']['activosiete']= array(
'#type' => 'radio',
'#title' => t('Empleado opción siete'),
'#default_value' => 0,
'#return_value' => '1',
'#attributes' => array('name' => 'situac')
);
// 4- Obtenemos el valor del campo desde la base de datos
$result = db_select('user__field_situac', 's')
->condition('s.entity_id', $user, '=')
->fields('s',array('field_situac_target_id'))
->execute()
->fetchField(0);
// 5- Tras la consulta a base de datos, cargamos el valor del campo
switch($result){
case 1:
$form['field_situac']['activo']['activosiete']['#value'] = '1';
break;
case 2:
$form['field_situac']['desempleado']['desempleadouno']['#value'] = '2';
break;
case 3:
$form['field_situac']['desempleado']['desempleadodos']['#value'] = '3';
break;
case 4:
$form['field_situac']['desempleado']['desempleadotres']['#value'] = '4';
break;
case 5:
$form['field_situac']['desempleado']['desempleadocuatro']['#value'] = '5';
break;
case 6:
$form['field_situac']['activo']['activouno']['#value'] = '6';
break;
case 7:
$form['field_situac']['activo']['activodos']['#value'] = '7';
break;
case 8:
$form['field_situac']['activo']['activotres']['#value'] = '8';
break;
case 9:
$form['field_situac']['activo']['activocuatro']['#value'] = '9';
break;
case 10:
$form['field_situac']['activo']['activocinco']['#value'] = '10';
break;
case 11:
$form['field_situac']['activo']['activoseis']['#value'] = '11';
break;
}
}
}
// 6 - Replanteamos la validación pre-submit del formulariofunction user_situa_form_validate($form, $form_state) {
if (!empty($_POST["situac"])){
$sendvalue = $_POST["situac"];
$setting = array(array("target_id" => $sendvalue));
$form_state-> setValue('field_situac', $setting);
}
}

4- Repasamos los pasos y los comentamos amistosamente

// 1- Comprueba el id del formulario

Normalmente en un form_alter se agrupan diversos cambios y modificaciones. La manera de segmentarlas y organizarlas para que se ejecuten solo en el caso del formulario al que van dirigidas es discriminando el contexto de su aplicación en base al id del formulario.

// 2- Cargamos un nuevo método de validación para el form

Vamos a realizar una re-estructuración del formulario, lo que implica que modificaremos la forma en la que está articulada la respuesta final que envía al submit, normalmente formada como un array anidado. Así que le asignamos al formulario una nueva función de validación que se encargue de modificar la salida de valores para adaptarla mejor.

// 3- Reconstruimos el formulario como nos han pedido

A partir de ahora el formulario se “pintará” justo como nosotres declaremos aquí. Como en Drupal no operamos directamente sobre HTML, sino más bien sobre una forma particular de “transpilación” (no es muy exacto pero resulta intuitivo) que consiste en definir componentes ya declarados en la Form API mediante arrays anidados que luego el sistema de Render se encargará de construir como HTML. Es el momento de describir que componentes necesitamos mostrar.

// 4- Obtenemos el valor del campo desde la base de datos

Bueno, esta parte no está muy fina en cuanto a que podría extraerse el valor del campo a través de la clase base \Drupal llamando a datos del user, pero lo importante es ver que para mostrar el campo al inicio, se lanza una consulta a la base de datos para ver que valor tiene.

// 5- Tras la consulta a la base de datos, cargamos el valor del campo

Luego tomamos ese valor devuelto desde la base de datos y lo cargamos en el campo mediante un switch().

// 6 -Replanteamos la validación pre-submit del formulario

Como decíamos anteriormente, hay que reformar la salida del formulario para que Drupal no note nada y se procese de la manera justa en la que lo espera para chutarlo al submit.

Finalmente, si todo ha ido bien hemos conseguido puentear todo el proceso y tener la estructura formada como nos habían solicitado, viéndose ahora el formulario inicial como se esperaba:

Vista final del formulario renderizado

--

--

@davidjguru
Drupal & /me

I’m @davidjguru this is my off-broadway channel, speaking about Drupal like in https://www.therussianlullaby.com or https://davidjguru.github.io but in spanish.