This course is still being released! Check back later for more chapters.

Get Notified About this Course!

We will send you messages regarding this course only
and nothing else, we promise.
You can unsubscribe anytime by emailing us at:
privacy@symfonycasts.com
Login to bookmark this video
Buy Access to Course
08.

Organización de los campos del formulario

|

Share this awesome video!

|

Keep on Learning!

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Dediquemos un momento a discutir la organización de los campos de nuestro formulario, en particular el campo Notas que se encuentra justo en medio de todo. Lo ideal sería que los campos obligatorios fueran los primeros, y que los opcionales esperaran pacientemente su turno al final.

Por defecto, Symfony muestra los campos del formulario en el orden en que están definidos dentro del tipo de formulario:

58 lines | src/Form/StarshipPartType.php
// ... lines 1 - 15
class StarshipPartType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('name', null, [
// ... lines 22 - 24
])
->add('price')
->add('notes')
->add('starship', EntityType::class, [
// ... lines 29 - 40
])
->add('createAndAddNew', SubmitType::class, [
// ... lines 43 - 46
])
;
}
// ... lines 50 - 56
}

Sin magia, sin sorpresas. Así que, si quieres un orden diferente, la solución más sencilla, como ya habrás adivinado, es también la menos emocionante: simplemente reordena los campos en el tipo de formulario.

Intentémoslo. Mueve el campo Notas al final, justo antes del botón de envío:

58 lines | src/Form/StarshipPartType.php
// ... lines 1 - 15
class StarshipPartType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('name', null, [
// ... lines 22 - 24
])
->add('price')
->add('starship', EntityType::class, [
// ... lines 28 - 39
])
->add('notes')
->add('createAndAddNew', SubmitType::class, [
// ... lines 43 - 46
])
;
}
// ... lines 50 - 56
}

Actualiza la página y ¡voilà! El campo Notas está ahora al final. ¡Problema resuelto!

La opción de campo de formulario priority

Pero, ¿y si no quieres mover físicamente los campos, especialmente cuando el orden debe cambiar dinámicamente en determinadas condiciones? Ahí es donde la opción priority viene al rescate.

Cada campo de formulario tiene un ajuste priority - el valor por defecto es 0. Si añadimospriority al campo Starship y lo establecemos en 10, Symfony renderizará ese campo antes:

59 lines | src/Form/StarshipPartType.php
// ... lines 1 - 15
class StarshipPartType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
// ... lines 21 - 26
->add('starship', EntityType::class, [
// ... lines 28 - 39
'priority' => 10,
])
// ... lines 42 - 48
;
}
// ... lines 51 - 57
}

Un número mayor significa que se renderiza primero, mientras que un número menor significa que se renderiza después. Incluso puedes asignar una prioridad negativa si quieres que un campo se renderice más cerca del final.

Este truco es superútil cuando el orden de los campos depende de condiciones que no pueden resolverse fácilmente simplemente reordenando tu código... O cuando quieres evitar grandes diferencias Git.

Personalizar la disposición de los campos

Pero, ¿y si ordenar no es suficiente? ¿Y si quieres un diseño personalizado? ¿Podemos poner los campos cortos Nombre y Precio en la misma línea, por ejemplo? ¡La respuesta es sí! Symfony proporciona varias funciones de ayuda para renderizar formularios. Ya conocemos algunas de ellas.

Hasta ahora, hemos confiado en {{ form_widget(form) }} para renderizar todos los campos automáticamente. Pero cuando quieras tener más control, puedes renderizar los campos manualmente, uno a uno.

En lugar de renderizar todo el formulario a la vez, rendericemos el primer campo utilizando form_row() y pasemos a form.starship: seguimos queriendo que ese campo se renderice primero:

// ... lines 1 - 6
{% block body %}
<div class="max-w-4xl mx-auto">
// ... lines 9 - 10
{{ form_start(form) }}
{{ form_row(form.starship) }}
// ... lines 13 - 20
{{ form_end(form) }}
</div>
{% endblock %}

Después, añade un poco de HTML para crear una cuadrícula utilizando las clases CSS de Tailwind:grid, grid-cols-2 y gap-4. Dentro, renderiza de nuevo form_row() paraform.name y form.price:

// ... lines 1 - 6
{% block body %}
<div class="max-w-4xl mx-auto">
// ... lines 9 - 10
{{ form_start(form) }}
{{ form_row(form.starship) }}
<div class="grid grid-cols-2 gap-4">
{{ form_row(form.name) }}
{{ form_row(form.price) }}
</div>
// ... lines 17 - 20
{{ form_end(form) }}
</div>
{% endblock %}

No te olvides de los campos restantes antes del botón codificado: simplemente añadeform_rest() y pasa la variable form para renderizar los campos que aún no hayamos renderizado:

// ... lines 1 - 6
{% block body %}
<div class="max-w-4xl mx-auto">
// ... lines 9 - 10
{{ form_start(form) }}
{{ form_row(form.starship) }}
<div class="grid grid-cols-2 gap-4">
{{ form_row(form.name) }}
{{ form_row(form.price) }}
</div>
{{ form_rest(form) }}
<button type="submit" class="text-white bg-green-700 hover:bg-green-800 rounded-lg px-5 py-2.5 me-2 mb-2 cursor-pointer">Create and close</button>
{{ form_end(form) }}
</div>
{% endblock %}

Actualiza la página y verás que los campos Nombre y Precio están ahora felizmente uno junto al otro.

Alinear los botones de envío en la misma línea

Pero espera, ¿podemos hacer lo mismo con los botones? ¡Claro que sí! De vuelta a la plantilla, representa el campo Notas con form_row() debajo de la cuadrícula, pasando form.notes. A continuación, justo antes del botón codificado, representa form_row() de nuevo pasando form.createAndAddNew:

// ... lines 1 - 6
{% block body %}
<div class="max-w-4xl mx-auto">
// ... lines 9 - 10
{{ form_start(form) }}
// ... line 12
<div class="grid grid-cols-2 gap-4">
// ... lines 14 - 15
</div>
{{ form_row(form.notes) }}
{{ form_row(form.createAndAddNew) }}
<button type="submit" class="text-white bg-green-700 hover:bg-green-800 rounded-lg px-5 py-2.5 me-2 mb-2 cursor-pointer">Create and close</button>
// ... lines 22 - 27
{{ form_end(form) }}
</div>
{% endblock %}

Esta función form_end() renderiza los campos restantes y cierra la etiqueta del formulario. Para elegir dónde queremos renderizar los campos restantes, podemos utilizar form_rest(form). Añadiré un comentario para explicarlo:

Cualquier cosa que quieras añadir después de que se hayan mostrado todos los campos, pero antes de del cierre </form>

// ... lines 1 - 6
{% block body %}
<div class="max-w-4xl mx-auto">
// ... lines 9 - 10
{{ form_start(form) }}
// ... lines 12 - 17
{{ form_row(form.notes) }}
{{ form_row(form.createAndAddNew) }}
<button type="submit" class="text-white bg-green-700 hover:bg-green-800 rounded-lg px-5 py-2.5 me-2 mb-2 cursor-pointer">Create and close</button>
{{ form_rest(form) }}
{#
Anything you want to add after all fields are rendered
but before the closing </form>
#}
{{ form_end(form) }}
</div>
{% endblock %}

Visualización de campos y gestión de errores

Vuelve a actualizar la página Hmm, los botones siguen sin estar alineados. Si inspeccionas el HTML, verás por qué: form_row() muestra un contenedor completo, que incluye el campo real, su etiqueta y también los errores, si los hay. Todo ello se envuelve con esta etiqueta div.

Normalmente, eso es estupendo para los campos, pero no en nuestro caso. Queremos deshacernos de esa envoltura extra div y renderizar sólo el campo en sí, el botón en este caso. ¿La solución? En Symfony, los campos de formulario, es decir, los elementos HTML input,button, select, se denominan widgets. Así que, para renderizar sólo el elemento botón, sustituye form_row() para el botón por form_widget():

// ... lines 1 - 6
{% block body %}
<div class="max-w-4xl mx-auto">
// ... lines 9 - 10
{{ form_start(form) }}
// ... lines 12 - 19
{{ form_widget(form.createAndAddNew) }}
<button type="submit" class="text-white bg-green-700 hover:bg-green-800 rounded-lg px-5 py-2.5 me-2 mb-2 cursor-pointer">Create and close</button>
// ... lines 22 - 27
{{ form_end(form) }}
</div>
{% endblock %}

Actualiza de nuevo y... ¡bien! Por fin los botones están alineados.

Puedes renderizar manualmente todos los componentes de un form_row() de esta forma.form_widget() renderiza el elemento campo, form_label() renderiza el elemento etiqueta, form_errors() renderiza una lista de errores (si los hay), yform_help() renderiza el texto de ayuda (si lo hay).

Mostrar los errores globales del formulario que faltan

Pero ten cuidado: hemos introducido un problema. Corrompe de nuevo el token CSRF... y envía el formulario... Vemos los errores de nombre y precio, pero no el mensaje de error CSRF esperado.

En la barra de herramientas de depuración web, la pestaña del validador muestra 2 errores, pero la pestaña del formulario muestra 3. ¿Qué ocurre? Abre el panel del validador. Vale, vemos nuestros errores de nombre y precio.

Ahora ve a la pestaña formularios. Aquí vemos 3 errores: los 2 errores de campo y el error CSRF adjunto al propio formulario. Éste es un error de validación a nivel de formulario, por eso no aparece en el panel del validador.

En nuestra plantilla, cuando antes utilizábamos form_widget(form), Symfony renderizaba automáticamente los errores a nivel de formulario, o globales, por nosotros. Pero ahora que estamos renderizando los campos manualmente, tenemos que acordarnos de renderizar esos errores globales también manualmente. Así que, justo después de form_start(), añade form_errors(form):

// ... lines 1 - 6
{% block body %}
<div class="max-w-4xl mx-auto">
// ... lines 9 - 10
{{ form_start(form) }}
{{ form_errors(form) }}
// ... lines 13 - 29
{{ form_end(form) }}
</div>
{% endblock %}

Vale, vamos a intentarlo de nuevo. De nuevo en el navegador, actualiza la página... corrompe el token CSRF... y envía.

¡Genial! Vuelven todos los errores esperados.

Cómo hacer que el formulario sea más fácil de usar

Para que el formulario sea más fácil de usar y ayudar a los usuarios a evitar errores de validación por adelantado, puedes añadir sugerencias directamente al campo utilizando una opción especial dehelp. Por ejemplo, digamos a los usuarios que las piezas gratuitas no están permitidas añadiendo una opción help al campo Precio con un mensaje significativo:

¡No permitimos piezas gratuitas! Por favor, fija un precio

61 lines | src/Form/StarshipPartType.php
// ... lines 1 - 15
class StarshipPartType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
// ... lines 21 - 25
->add('price', null, [
'help' => 'We don\'t allow free parts! Set up a price',
])
// ... lines 29 - 50
;
}
// ... lines 53 - 59
}

Cuando actualices el formulario, verás este útil mensaje en un sutil texto gris debajo del campo.

Añadir atributos de campo de formulario en la plantilla

¿Recuerdas cuando añadimos clases CSS al botón Enviar en el tipo de formulario? Eso funciona, pero no es lo ideal - los diseñadores probablemente no quieran tocar tu código PHP, y puede que ni siquiera sepan lo que es un tipo de formulario, o cómo funciona en una aplicación Symfony. Por lo tanto, las decisiones de estilo realmente no pertenecen ahí.

En su lugar, traslademos esos estilos a la plantilla para que nuestros diseñadores puedan cambiarlos fácilmente. Lo comentaré en el tipo de formulario. Copia la larga línea de clases CSS, y ve a la plantilla. Para la llamada a form_widget(form.createAndAddNew), añade un segundo argumento, un hash de opciones.

Aquí puedes pasar las mismas opciones que pasas en el tipo de formulario. Así, queremos una opciónattr establecida en otro hash. Dentro, añade la opción class... y pega las clases CSS que copiamos antes:

// ... lines 1 - 6
{% block body %}
<div class="max-w-4xl mx-auto">
// ... lines 9 - 10
{{ form_start(form) }}
// ... lines 12 - 21
{{ form_widget(form.createAndAddNew, {
'attr': {
'class': 'text-white bg-blue-700 hover:bg-blue-800 rounded-lg px-5 py-2.5 me-2 mb-2 cursor-pointer',
},
}) }}
// ... lines 27 - 33
{{ form_end(form) }}
</div>
{% endblock %}

Cuando actualices la página, verás el mismo estilo, pero con una separación más limpia. Recuerda que las opciones definidas en las plantillas Twig sobrescriben todo lo establecido en el tipo de formulario.

Resaltar los campos obligatorios del formulario con CSS

Los mensajes de ayuda son geniales, pero a veces quieres que los campos obligatorios destaquen visualmente. Añadamos un pequeño truco CSS para mostrar un asterisco rojo junto a cada campo obligatorio de nuestro formulario. Abre assets/styles/app.css y al final, copia/pega el siguiente fragmento del script de abajo:

10 lines | assets/styles/app.css
// ... lines 1 - 6
input, textarea, select {
background-color: inherit;
}

Actualiza el formulario... ¡Genial! Ahora todos los campos obligatorios tienen un bonito asterisco rojo. No sólo para este formulario, sino para todos los formularios de nuestra aplicación. ¡Genial!

Y así de fácil, has pasado de un formulario predeterminado de Symfony a un diseño de formulario totalmente personalizado, fácil de diseñar y a prueba de errores. No está mal para un capítulo, ¿verdad?

A continuación, vamos a acelerar la creación de formularios para diferentes operaciones CRUD en nuestras entidades aprovechando MakerBundle de nuevo. ¡Permanece atento!