La reactividad es una característica clave en el desarrollo web moderno que permite que las interfaces de usuario respondan de manera eficiente a los cambios de datos. Vue.js, una popular librería JavaScript, facilita la implementación de esta reactividad con su sencilla y flexible API.

Por otro lado, Thymeleaf es un motor de plantillas de Java para aplicaciones web, ampliamente utilizado en el entorno de Spring Boot.

En este post, explicaremos cómo integrar Vue en proyectos Thymeleaf en modo librería o, lo que es lo mismo, integrar Vue.js en un proyecto sin un bundler (como Webpack o Vite) o con una infraestructura mínima. Esta integración nos permitirá aprovechar la reactividad de Vue.js, así como realizar validaciones de formularios con Vuelidate, todo sin necesidad de una configuración compleja.

Aprenderás a incluir componentes de Vue.js en tus plantillas Thymeleaf y a gestionar datos de manera dinámica, mejorando la experiencia de usuario de tus aplicaciones web.

Principales características

Integrar Vue.js con Thymeleaf en modo librería nos ofrece varios beneficios importantes:

Integrar Vue.js con Thymeleaf en modo librería no solo simplifica el desarrollo, sino que también mejora la reactividad y la eficiencia de las aplicaciones web, ofreciendo una experiencia de usuario más rica y dinámica.

Configuración del proyecto

Si ya tienes un proyecto existente con Thymeleaf y quieres integrar Vue.js importando la dependencia como un fichero o librería de JavaScript , en nuestro caso como trabajaremos con Thymeleaf simplemente añadiremos la importación mediante la etiqueta script.

Primero, asegúrate de que tu proyecto esté configurado correctamente con Spring Boot y Thymeleaf. Luego, simplemente añade el enlace a la biblioteca de Vue.js en tus plantillas Thymeleaf.

<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16"></script>

Para integrar Vue.js primero debes definir el bloque de HTML que se encargará de gestionar la aplicación. Este bloque actúa como el punto de entrada para Vue.js en tu aplicación, por ello es importante identificar el elemento html mediante un id, o bien como un elemento html que sea único en el documento HTML.

<div id="app">
   {{ message }}
</div>

El elemento con id="app" es el contenedor donde Vue.js "hará su magia". La expresión {{ message }} es una interpolación de Vue que se actualizará reactivamente cuando el valor de message cambie.

El siguiente paso será crear una instancia de Vue.js que se encargará de gestionar la reactividad dentro del contenedor definido anteriormente. En el mismo archivo html, añade el siguiente bloque de código </script>:

<script>
    const app = Vue.createApp({
        data() {
            return {
                message: '¡Hola mundo desde Vue!'
            }
        }
    })
    // Montar la aplicación Vue en el elemento con id "app"
    app.mount('#app')
</script>

Es obligatorio que este bloque script esté definido al final de la página, ya que necesita tener definido previamente el elemento HTML sobre el que hacemos referencia para poder inicializar la aplicación Vue.js.

En Vue.js, el bloque “data” es una opción fundamental que se utiliza dentro de la instancia de Vue para definir el estado reactivo de la aplicación.

Integración de datos entre Thymeleaf y Vue.js

La integración de Thymeleaf y Vue.js permite combinar la renderización del lado del servidor con la reactividad del lado del cliente. En este apartado, explicaremos cómo transferir datos desde Thymeleaf a Vue.js para aprovechar al máximo las capacidades combinadas de ambos.

Thymeleaf inicializará los datos que luego serán gestionados de manera reactiva por Vue.js, creando una experiencia de usuario fluida y dinámica en aplicaciones Spring Boot.

<script>
    const app = Vue.createApp({
        data() {
            return {
                hello: '¡Hola mundo desde Vue!',
          vueJavaMsg: '[[${message}]]', // Variable de java convertida a Vue,
  htmlContent: '<strong>Hola, mundo desde Vue!</strong>',
  imageUrl: 'https://www.paradigmadigital.com/assets/img/logo/paradigma-logos/horizontal/paradigma_digital_logo_default.svg',
            }
        }
    })
    app.mount('#app')
</script>

En el ejemplo anterior podemos ver que dentro del bloque “data” hemos definido dos elementos o variables que tendremos accesibles dentro de nuestra aplicación de Vue.js, la peculiaridad de message que es una variable expuesta a nuestro html mediante Thymeleaf.

La variable “message” está definida en nuestro controlador de Java como una variable común:

    @GetMapping("/")
    public String formExample(Model model) {
        model.addAttribute("message", "Hola mundo desde Java");
        return "form"; // View
    }

De esta manera añadimos nuevas formas de renderizar nuestras variables en nuestro html:

<div>
<label>
Input con valor por variable de vue:
              <input type="text" v-model="hello">
</label>
       {{ hello }}
       <label>
               Input con valor de java renderizador por Vue:
              <input name="blabla" type="text" v-model="vueJavaMsg">
        </label>
        <p v-text="vueJavaMsg">
      <div v-html="htmlContent"></div>
    <img :src="imageUrl" alt="Imagen cargada mediante Vue">
</div>

En el ejemplo anterior podemos ver cómo utilizamos tanto las variables definidas en Thymeleaf de la forma clásica, y nos ayudamos de las directivas de Vue.js en el navegador para asignar valores. Sin olvidarnos del resto de directivas de Vue.js que pueden ser muy útiles para nuestros desarrollos v-if, v-show, v-for, v-on, etc.

Por ejemplo, y para poner en práctica un poco más este tipo de funcionalidad, vamos a recibir una variable de nuestro backend con un valor booleano, que utilizaremos para asignar en valor de un input de tipo checkbox, que, a su vez, mostrará u ocultará un elemento html dependiendo del valor asignado.

...
// Controlador Java
model.addAttribute("isElementChecked", false);
...
// Template
<div>
<label>
   Input con valor de java renderizado por Vue:
   <input type="checkbox" v-model="isChecked">
</label>
El checkbox tiene un valor {{ isChecked }}
<div v-if="isChecked">
   v-if reactivo que se muestra con el valor de isChecked: {{ isChecked }}
</div>
</div>
...
<script>
    const app = Vue.createApp({
        data() {
            return {
                isChecked: [[${isElementChecked}]],
            }
        }
    })
    app.mount('#app')
</script>

Con este ejemplo podemos ver el poder de la reactividad, ya que conseguimos mostrar u ocultar un elemento html añadiendo un simple v-if y teniendo registrada la variable en nuestra aplicación de Vue.js. Por el contrario, si quisiéramos realizar este desarrollo de la forma tradicional en JavaScript, tendríamos que registrar evento click del elemento html, leer su valor y, en función del valor, añadir o quitar una clase que muestre el elemento o lo oculte.

Validación de formularios con Vuelidate

Vuelidate es una poderosa biblioteca de validación para Vue.js que facilita la gestión y verificación de datos en formularios de manera reactiva. La integración de Vuelidate en tu proyecto Vue permite crear formularios robustos con validaciones consistentes y reutilizables, mejorando la experiencia del usuario al proporcionar retroalimentación inmediata sobre la validez de los datos ingresados.

Para poder utilizar Vuelidate en nuestro proyecto tendremos que incluir la librería en nuestra plantilla de Thymeleaf. Vuelidate 2 no incluye validadores integrados, pero los proporciona a través del paquete secundario @vuelidate/validators, que contiene un conjunto completo de validadores y herramientas auxiliares que puedes importar y utilizar fácilmente.

<script src="https://cdn.jsdelivr.net/npm/@vuelidate/core@next"></script>
<script src="https://cdn.jsdelivr.net/npm/@vuelidate/validators@next"></script>

También tendremos que modificar la instancia de nuestra aplicación Vue, añadiendo los siguientes bloques:

const validationMixin = vuelidate.validationMixin;
Vue.use(vuelidate.default);

// Validadores disponibles
const {required, email, between, sameAs, helpers, minLength } = validators;

const app = new Vue({
el: 'form',
       mixins: [validationMixin],
       validations: {
               name: {required, minLength: minLength(5)},
              email: {required, email},
       },
       data: () => ({
               name: '',
              email: '[[${userEmail}]]', // Variable de java convertida a VUE,
}),
});

Como podemos ver en el ejemplo anterior, hemos añadido dos campos nuevos a nuestro formulario:

Estos campos tendrán que estar declarados tanto en el bloque data, como en el bloque validations.

Los valores de required, minLength, email… se extraen de la propia librería de Vuelidate mediante la variable validators. Existen varias opciones disponibles que podemos consultar en la documentación oficial.

También podemos validar ciertos campos con expresiones regulares o funciones como en el siguiente ejemplo:

const isValidPass = helpers.regex('isValidPass', /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[\.]*)(?=.{8,200})/);

const validateChecked = (value) => value;

Ahora ya solo nos falta lo más importante: ¿cómo validamos un formulario? Para validar un formulario tendremos que añadir a nuestros elementos html de tipo input el siguiente código:

<form>
 <h2>Formulario con validaciones</h2>
<div class="form-element">
               <label for="name">Nombre</label>
              <input id="name" name="name"
v-model="$v.name.$model"
:class="{'error-input': $v.name.$error && $v.name.$dirty}"
                     @blur="$v.name.$touch()">
                  <p v-if="$v.name.$required && $v.name.$dirty" class="invalid">El campo es requerido
</div>
<div class="form-element">
<label for="surname">Apellido</label>
<input id="surname" name="surname"
v-model="$v.surname.$model"
:class="{'error-input': $v.surname.$error && $v.surname.$dirty}"
@blur="$v.surname.$touch()">
<p v-if="$v.surname.$error && $v.surname.$dirty" class="invalid">El campo es requerido
</div>
</form>

Como podemos observar, hemos añadido varios atributos a nuestras etiquetas input. Vamos a ver a continuación qué hace cada uno de ellos:

Diferentes maneras de validar

Como hemos visto en el apartado anterior, podemos validar un campo de manera unitaria utilizando el evento blur del campo. Esto podría aplicarse a otros eventos dependiendo de la casuística de nuestra aplicación como, por ejemplo, input, change, focus, keyUp, keyDown, paste, etc.

También podríamos validar el formulario completamente ejecutando el siguiente fragmento de código:

this.$v.$touch();

De esta manera, podemos ejecutar la validación de forma programática sin necesidad de pasar por ninguno de los campos del formulario.

Integración de componentes personalizados

Para integrar un componente personalizado en tu aplicación Thymeleaf y Vue.js, sigue estos pasos básicos. Esto te permitirá crear un componente que puede ser reutilizado en diferentes partes de tu aplicación, mejorando la modularidad y mantenibilidad del código.

Lo primero de todo será definir el componente. En el siguiente ejemplo podemos ver un componente muy sencillo, pero recuerda que puede ser mucho más complejo.
Podemos definir nuestro componente dentro de nuestro script de Thymeleaf, pero la reutilización no tendría mucho sentido.

<script>
    Vue.component('mi-componente', {
        template: '<div>¡Hola! Soy un componente Vue.js</div>'
    });
</script>

Para poder reutilizar el componente sin tener que declararlo una y otra vez, lo ideal es definir el componente en un fichero JavaScript independiente e importar el script en nuestra plantilla de Thymeleaf.

// Componente my-counter.js
Vue.component('mi-contador, {
    data() {
        return { count: 0 }
    },
    methods: {
      addCount() {
        this.count++;
      },
    },
    template: `<div>Count is: {{ count }} <button type="button" @click="addCount">+1</button></div>`
})

De esta manera solo tendremos que importar el componente en la vista donde lo utilizaremos y añadir el html correspondiente.

// Importación del componente
<script th:src="@{/js/components/my-counter.js}"></script>

// Uso del componente
<div id="app">
<mi-contador></mi-contador>
</div>

Estandarización de formularios

Todo lo que hemos visto con anterioridad nos sirve para poder utilizar Vue.js en nuestros proyectos de Thymeleaf, pero ¿es necesario repetir tanto código para cada plantilla? ¿podríamos simplificar ciertas partes? La respuesta es , podemos estandarizar la parte de validación de formularios siempre y cuando sigamos unas reglas a la hora de crearlos.

Podemos usar un único fichero JavaScript donde se define y configura la instancia de Vue, de esa manera solamente tendremos que importarlo en aquellas plantillas donde tengamos formularios con validaciones.

// FormValidator.js
const validationMixin = vuelidate.validationMixin;

Vue.use(vuelidate.default);

const app = new Vue({
    el: 'form',
    mixins: [validationMixin],
    validations: globalValidations,
    data: () => (globalData),
});

Como podemos observar en el fragmento de código anterior, siempre ejecutaremos Vue para los elementos form dentro de nuestra aplicación.

Utilizaremos globalData y globalValidations para definir los datos y validaciones de la plantilla, solo tendremos que definir estas constantes dentro de nuestra plantilla.

<script>
const { required, email, sameAs, helpers, minLength, between } = window.VuelidateValidators;

        // Validaciones custom
        const validateChecked = (value) => value;
        const isValidPass = helpers.regex('isValidPass', /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[\.]*)(?=.{8,200})/);

        // Definir globalData para los datos que se usaran en la reactividad de Vue
        const globalData = Vue.reactive({
            name: '',
            surname: '',
            age: '',
            email: '',
            company: '[[${company}]]', // Variable de java convertida a VUE
            pass: '',
            repeatPass: '',
            terms: false,
        });

        // Definir globalValidations para las validaciones
        let globalValidations = {
            name: {required, minLength: minLength(5)},
            surname: {required},
            age: { required, between: between(16, 65)},
            email: {required, email},
            company: {},
            pass: {required, isValidPass},
            repeatPass: {required, sameAsPassword: sameAs('pass')},
            terms: {validateChecked},
        }
    </script>

<script th:src="@{/js/FormValidator.js}"></script>

De esta manera definiremos primero las constantes globalData y globalValidations antes de realizar la instancia de Vue.js por lo que, cuando carguemos el script FormValidator.js, estas variables ya estarán definidas.

Así que solamente nos tendremos que preocupar de definir el formulario html y definir las constantes de globalData y globalValidations correspondientes a nuestro formulario.

Integración de componentes personalizados

Para integrar un componente personalizado en tu aplicación Thymeleaf y Vue.js, sigue estos pasos básicos. Esto te permitirá crear un componente que puede ser reutilizado en diferentes partes de tu aplicación, mejorando la modularidad y mantenibilidad del código.

Lo primero de todo será definir el componente:

<script>
    Vue.component('mi-componente', {
        template: '<div>¡Hola! Soy un componente Vue.js</div>'
    });
</script>

Al igual que hemos hecho antes, definimos el componente en un fichero JavaScript y lo importamos en nuestra plantilla de Thymeleaf.

// Componente my-counter.js
Vue.component('mi-contador, {
    data() {
        return { count: 0 }
    },
    methods: {
      addCount() {
        this.count++;
      },
    },
    template: `<div>Count is: {{ count }} <button type="button" @click="addCount">+1</button></div>`
})

De esta manera solo tendremos que importar el componente en la vista donde lo utilizaremos y añadir el html correspondiente.

// Importación del componente
<script th:src="@{/js/components/my-counter.js}"></script>

// Uso del componente
<div id="app">
<mi-contador></mi-contador>
</div>

Conclusión

Integrar Vue.js en un proyecto basado en Thymeleaf es una excelente manera de aprovechar la reactividad de Vue en el frontend mientras se mantiene la robustez del backend con Spring Boot. Al utilizar Vue como una librería, puedes añadir dinámicas interactivas y validaciones avanzadas como las que ofrece Vuelidate, todo sin modificar significativamente la estructura de tu aplicación existente.

Definir componentes personalizados permite reutilizar y modularizar tu código, mejorando tanto la organización como la mantenibilidad de la aplicación. Al seguir estos pasos, lograrás una aplicación moderna, eficiente y fácil de escalar, combinando lo mejor de las tecnologías de frontend y backend.

Cuéntanos qué te parece.

Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.

Suscríbete