Category: DEV
Comenzamos un nuevo post, en el que veremos como mostrar una ventana modal o de diálogo en nuestro Drupal 9.
En ocasiones nos hemos visto en la situación de tener que mostrar alguna que otra ventana emergente en nuestros desarrollos. Lo más probable que lo usemos para mostrar una información adicional de la página actual en la que nos encontramos, sin tener que redirigir a una nueva página o tener que recargar la misma para mostrar esta información, ya sea un formulario, texto estático o cualquier otro.
Drupal delega esta funcionalidad, para mostrarnos los cuadros o ventanas emergentes, a través de jquery. Creando una petición asíncrona vía ajax para mostrar la ventana emergente. Por lo que nuestro modal o diálogo no será cargado hasta que no activemos el evento que dispara la visualización del mismo.
Dependiendo de nuestro caso de uso puede que nos convenga desarrollar una solución a medida usando los Ajax Callbacks Commands OpenDialogCommand, OpenModalDialogCommand, OpenOffCanvasDialogCommand o puede que nos valga incluyendo la clase ‘use-ajax’ dentro de un enlace.
Como podemos suponer por el listado de los ‘Ajax Callback Command’, existen tres tipos de ventanas emergentes que nos proporciona Drupal:
Diálogo Modal: Este cuadro se superpone por delante de la página. Bloqueando todos los elementos existentes fuera del foco de la ventana modal. Este tipo de diálogo sólo permite una ventana emergente modal al mismo tiempo.
Ventana de diálogo no modal: aparecen y se superpone por encima de la página, pero aún se puede hacer clic en otros elementos de la página. Se pueden mostrar varios cuadros de diálogo al mismo tiempo.
Diálogos Off canvas o diálogo fuera del lienzo: Esté diálogo no es ninguna ventana o pop-up emergente que se superponga por encima de nuestra página. En este caso la ventana se hace lugar tapando o empujando el área de contenido mediante una animación. Un ejemplo muy común son los menús ‘tipo hamburguesa’ que nos podemos encontrar cuando navegamos en dispositivos móviles.
Ahora que tenemos un contexto básico podemos continuar.
Sin duda esta es la forma más rápida y sencilla de implementar una ventana emergente.
Si miramos un poco más de cerca, en ‘web/core/core.libraries.yml’ en la librería ‘core/drupal.ajax’ del core de Drupal, nos encontramos que mapea el javascript ajax.js. Si nos dirigimos a este archivo vemos como dentro de la función ‘Drupal.ajax.bindAjaxLinks’, está listo para cargar las configuraciones de un cuadro de diálogo, siempre que vengan especificada en el elemento, al usar la clase ‘use-ajax’ en los enlaces.
Estas configuraciones la implementaremos a través de distintos atributos que especificamos dentro del elemento HTML ‘’.
Los atributos que podemos añadir son:
data-dialog-type: Aquí especificaremos el tipo de diálogo que deseamos. Admite los valores ‘dialog’ o ‘modal’
data-dialog-options: Podemos especificar distintas opciones de presentación de nuestro modal. Como el ancho, alto, la posición, … Las opciones disponibles son las que nos presenta la api de jquey en su página. Estas opciones las presentaremos en un formato JSON codificado dentro del propio atributo.
data-dialog-renderer: Aquí especificamos el renderizador para el tipo del diálogo. Este atributo es necesario si queremos que el cuadro de diálogo aparezca fuera del lienzo.
Vamos a realizar nuestras pruebas sobre un drupal recién instalado. Para que muestre en un cuadro emergente el formulario de contacto por defecto de drupal, ‘/contact/feedback’ (podría valer cualquier enlace interno válido como ‘node/2’).
Por un lado debemos asegurarnos que nuestro tema o módulo cargue la librería del core de drupal necesaria para la carga de los diálogos, a saber core/drupal.dialog.ajax
. (El tema bartik de administración carga por defecto esta librería, por lo que si estamos logueados y visualizando el tema, no haría falta la carga) Por otro lado vamos a crear un nuevo contenido, de tipo página básica y en el CKEditor escogemos el tipo de formato ‘HTML completo’, mostramos la fuente y podemos empezar.
Para mostrar un modal, añadimos la clase ‘use-ajax’ y el atributo ‘data-dialog-type=”modal” ‘, siendo ‘modal’ nuestro tipo de cuadro emergente.
<p><a class="use-ajax" data-dialog-options="{
"width":"350",
"height":"650",
"position":{
"my":"right top",
"at":"right top"
},
"hide":{
"effect":"slideUp",
"duration":"1000"
},
"show":{
"effect":"slideDown",
"duration":"1000"
}
}" data-dialog-type="modal" href="/contact/feedback">Formulario contacto MODAL</a></p>
Como podemos observar, también hemos introducido algunas opciones en la visualización del modal, con el atributo ‘data-dialog-options’. Para especificar estas opciones tenemos que pasarlas en formato JSON dentro de nuestro atributo. Por lo que es importante utilizar "
en vez de comillas para que se haga la codificación del JSON correctamente a través de nuestro html. En este caso en particular le hemos especificado un ancho y alto, así como establecido una posición, para que se presente en la esquina superior derecha de la pantalla. También hemos especificado para que la animación al mostrar y cerrar el modal tenga un efecto slide. En la api de jquey encontramos toda la configuración que se puede usar.
Si queremos presentar ahora este modal como un diálogo solo nos haría falta cambiar el atributo ‘data-dialog-type’ de ‘modal’ a ‘dialog’:
<p><a class="use-ajax" data-dialog-options="{
"hide":{
"effect":"slideUp",
"duration":"1000"
},
"show":{
"effect":"slideDown",
"duration":"1000"
}
}" data-dialog-type="dialog" href="/contact/feedback">Formulario contacto DIALOG</a></p>
Y por último podemos ver como se comporta el diálogo fuera del lienzo, incluyendo el atributo data-dialog-renderer="off_canvas"
en nuestro enlace. Este atributo sólo se podría usar con el dialog, data-dialog-type="dialog"
. Ya que si lo intentamos usar con el ‘modal’ el enlace daría error y no abriría nuestro cuadro emergente.
<p><a class="use-ajax" data-dialog-options="{
"width":"350",
"height":"650",
"position":{
"my":"right top",
"at":"right top"
},
"hide":{
"effect":"slideUp",
"duration":"1000"
},
"show":{
"effect":"slideDown",
"duration":"1000"
}
}" data-dialog-renderer="off_canvas" data-dialog-type="dialog" href="/contact/feedback">Formulario contacto DIALOG OFF CANVAS</a></p>
Quedando nuestros enlaces de la siguiente manera:
Vamos a dar otro pasito más, y vamos a crear un formulario custom el cual se desplegará sobre un modal. Esto nos permitirá dar más flexibilidad sobre lo que incluimos en el modal.
Lo primero que vamos a realizar, es crear nuestro módulo modal_form, el cual puedes descargar ‘aquí’. Para ello creamos nuestro archivo de configuración modal_form.info.yml
en /web/modules/custom/modal_form
:
name: Modal Form
type: module
description: 'Provides a custom Form in Modal'
package: Custom
core_version_requirement: ^9.2 || ^10
Una vez creado nuestro archivo .info podemos empezar a crear nuestro formulario custom. Para ello creamos nuestra clase ModalForm
que extenderá de FormBase
. En modal_form/src/Form
creamos el archivo ModalForm.php
:
<?php
namespace Drupal\modal_form\Form;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\CloseDialogCommand;
use Drupal\Core\Ajax\RedirectCommand;
use Drupal\Core\Ajax\ReplaceCommand;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Url;
class ModalForm extends FormBase {
/**
* @inheritDoc
*/
public function getFormId(): string {
return 'custom_modal_form';
}
/**
* @inheritDoc
*/
public function buildForm(array $form, FormStateInterface $form_state): array {
$form['name'] = [
'#type' => 'textfield',
'#title' => $this->t('Nombre completo'),
'#placeholder' => $this->t('Nombre completo'),
'#required' => TRUE,
'#default_value' => "",
];
$form['email'] = [
'#type' => 'email',
'#title' => t('Email'),
'#placeholder' => t('Email'),
'#required' => TRUE,
'#default_value' => "",
'#size' => 20,
'#maxlength' => 20,
];
$form['phone'] = [
'#type' => 'tel',
'#title' => $this->t('Teléfono'),
'#placeholder' => $this->t('Teléfono'),
'#required' => TRUE,
'#size' => 10,
];
$form['message'] = [
'#type' => 'textarea',
'#title' => t('Mensaje'),
'#placeholder' => t('Mensaje'),
'#required' => TRUE,
'#default_value' => "",
'#rows' => 4,
'#cols' => 5,
'#resizable' => 'none',
];
$form['actions'] = ['#type' => 'actions'];
$form['actions']['send'] = [
'#type' => 'submit',
'#value' => $this->t('Enviar'),
'#attributes' => [
'class' => [
'use-ajax',
],
],
'#ajax' => [
'callback' => [$this, 'submitModalFormAjax'],
'event' => 'click',
],
];
return $form;
}
/**
* @inheritDoc
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
// TODO: Implement submitForm() method.
}
/**
* @param array $form
* @param \Drupal\Core\Form\FormStateInterface $form_state
*
* @return \Drupal\Core\Ajax\AjaxResponse
*/
public function submitModalFormAjax(array &$form, FormStateInterface $form_state) {
$response = new AjaxResponse();
$response->addCommand(new CloseDialogCommand());
return $response;
}
}
Hemos creado un formulario de contacto básico en el que el usuario tiene que introducir sus datos como Nombre, Teléfono, Email y Mensaje. En este formulario hay que tener en cuenta que en el botón submit del formulario hemos implementado la clase use-ajax
, indicando en su callback ejecute la función submit custom submitModalFormAjax
. Aquí implementaremos la lógica de nuestro submit, en nuestro caso sólo cerramos el modal actual invocando al ajax command de Drupal CloseDialogCommand
, sin realizar ninguna acción más sobre nuestro formulario.
Ahora crearemos nuestra clase Controller, en este caso tendrá dos funciones. Por un lado openModalForm
, que será la responsable de cargar el cuadro de diálogo invocando el ajax command correspondiente, que en este caso hemos usado OpenModalDialogCommand
para abrir el dialogo como un modal. Y por otro lado onlyButtonOpenModal
, que sólo nos servirá a modo de prueba para mostrar una página con un botón que abrirá nuestro modal.
Por lo tanto creamos en modal_form/src/Controller
nuestra clase ModalController.php
<?php
namespace Drupal\modal_form\Controller;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\OpenModalDialogCommand;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Form\FormBuilder;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
class ModalController extends ControllerBase {
/**
* The form builder.
*
* @var \Drupal\Core\Form\FormBuilder
*/
protected $formBuilder;
/**
* The ModalController constructor.
*
* @param \Drupal\Core\Form\FormBuilder $formBuilder
* The form builder.
*/
public function __construct(FormBuilder $formBuilder) {
$this->formBuilder = $formBuilder;
}
/**
* {@inheritdoc}
*
* @param \Symfony\Component\DependencyInjection\ContainerInterface $container
* The Drupal service container.
*
* @return \Drupal\modal_form\Controller\ModalController
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('form_builder')
);
}
/**
* Callback para abrir el form modal.
*/
public function openModalForm(): AjaxResponse {
$response = new AjaxResponse();
// Obtiene el formulario modal usando el form builder.
$modal_form = $this->formBuilder->getForm('Drupal\modal_form\Form\ModalForm');
// Añade un comando AJAX para abrir la ventana modal con el formulario incluido.
$response->addCommand(new OpenModalDialogCommand('Formulario', $modal_form, ['width' => '800']));
return $response;
}
/**
* Botón de muestra.
*/
public function onlyButtonOpenModal(): array
{
$build['show'] = [
'#type' => 'link',
'#title' => $this->t('Mostar modal'),
'#url' => Url::fromRoute('modal_form.modal_controller'),
'#attributes' => [
'class' => ['button', 'use-ajax'],
]
];
// Añadimos la librería necesaria de Drupal para el Modal.
$build['#attached']['library'][] = 'core/drupal.dialog.ajax';
return $build;
}
}
Hay que tener presente tal como, comentábamos anteriormente, que Drupal gestiona los cuadros de diálogos de forma asíncrona a través de AJAX. Por eso es importante que nuestro sistema devuelva una petición AjaxResponse, para mostrar el formulario en una ventana emergente. Y que nuestro enlace o botón que carga el modal tenga la clase ‘use-ajax’ para que el ‘submit’ cargue de forma asíncrona vía ajax. También tendremos que asegurarnos que cargue la librería del core core/drupal.dialog.ajax
, en este caso la adjuntamos al enlace creado.
Ya sólo nos faltaría mapear nuestras rutas del controlador con un path válido en nuestro archivo modal_form.routing.yml.
modal_form.modal_controller:
path: 'modal/new-message'
defaults:
_title: 'New Message'
_controller: '\Drupal\modal_form\Controller\ModalController::openModalForm'
requirements:
_permission: 'access content'
options:
no_cache: TRUE
modal_form.button_modal:
path: 'modal/show-me-the-modal'
defaults:
_title: 'Show me the modal'
_controller: '\Drupal\modal_form\Controller\ModalController::onlyButtonOpenModal'
requirements:
_permission: 'access content'
options:
no_cache: TRUE
Una vez que tenemos todo, podemos limpiar caché e instalar el módulo.
$drush cr
$drush en modal_form
Ahora si nos dirigimos a ‘/modal/show-me-the-modal’ tendremos el siguiente resultado:
Hemos visto cómo crear cuadros de diálogos usando el api de Drupal. Aunque lo que nos provee Drupal por defecto puede servirnos para gran parte de los casos de usos que nos encontremos, también existe la posibilidad de crear nuestros propios Custom Dialog. Pero este paso lo reservo para una nueva entrega, para no alargar en exceso la lectura de este.
¡Hasta la próxima!