Skip to content

Unidad 9: Manejo de Eventos, Métodos y Modelos en Vue.js

Objetivo: Aprender a manejar eventos, definir métodos y utilizar el enlace de datos bidireccional en Vue.js, aplicándolo en la construcción del proyecto To-Do List.


9.1 Introducción al Manejo de Eventos, Métodos y Modelos en Vue.js

¿Por qué es importante manejar eventos y métodos en Vue.js?

En el desarrollo de aplicaciones interactivas, es esencial capturar y responder a eventos de usuario como clics, envíos de formularios y cambios en los campos de entrada. Vue.js facilita esta gestión con directivas como v-on, el enlace de datos con v-model y la capacidad de definir métodos dentro de los componentes.

Ejemplo de interacción en Vue.js

Ejemplo básico de eventos y métodos en Vue.js

<template>
<div>
    <button @click="mostrarMensaje">Haz clic</button>
    <p>{{ mensaje }}</p>
</div>
</template>

<script>
export default {
data() {
    return {
    mensaje: ""
    };
},
methods: {
    mostrarMensaje() {
    this.mensaje = "¡Botón clickeado!";
    }
}
};
</script>

Explicación:

  • @click="mostrarMensaje" → Escucha el evento de clic y ejecuta el método mostrarMensaje.
  • this.mensaje = "¡Botón clickeado!" → Modifica el estado en respuesta al evento.

9.2 Aplicación al proyecto ToDo

Ya tenemos los datos de muestra y un bucle que toma cada item de datos y lo renderiza dentro ToDoItem de nuestra aplicación. Lo que realmente necesitamos ahora es permitir que nuestros usuarios introduzcan sus propias tareas pendientes en la aplicación. Para ello, necesitaremos un texto <input>, un evento que se active al enviar los datos, un método que se active al enviarlos para añadir los datos y volver a renderizar la lista, y un modelo para controlar los datos. Esto es lo que abordaremos en este artículo.

Crear un Formulario para Añadir Tareas

Ahora tenemos una aplicación que muestra una lista de tareas pendientes. Sin embargo, no podemos actualizarla sin modificar el código manualmente. Vamos a solucionarlo. Crearemos un nuevo componente que nos permita añadir una nueva tarea. Este componente tendrá un campo de texto y un botón. Cuando el usuario introduzca una tarea y haga clic en el botón, la tarea se añadirá a la lista.

Primero creamos un nuevo componente llamado ToDoForm.vue en la carpeta components. Este componente tendrá un campo de texto y un botón. Cuando el usuario introduzca una tarea y haga clic en el botón, la tarea se añadirá a la lista.

El comoponente ToDoForm.vue inicialmente se verá así:

1
2
3
4
5
<template></template>

<script>
  export default {};
</script>

Ahora en el <template> agregamos un formulario HTML que permita al usuario introducir una tarea y un botón para enviar el formulario. El formulario tendrá un campo de texto y un botón. Cuando el usuario introduzca una tarea y haga clic en el botón, la tarea se añadirá a la lista.

Formulario ToDoForm

<template>
    <form>
        <label for="new-todo-input"> Qué necesitas hacer ahora </label>
        <input
            type="text"
            id="new-todo-input"
            name="new-todo"
            autocomplete="off" />
        <button type="submit">Añadir</button>
    </form>
</template>

Ahora necesitamos cargar este nuevo componente en ToDoList.vue. Para ello, importamos el componente ToDoForm y lo añadimos a la lista de componentes.

import ToDoForm from './ToDoForm.vue';

También necesitamos registrar este nuevo componenete, para lo que actualizamos la lista de componentes.

1
2
3
components: {
    ToDoItem, ToDoForm,
}

Finalmente necesitamos añadir este nuevo componente para que sea renderizado dentro de tu aplicación, añadiendo elementos <to-do-form/> dentro del <template> de ToDoList.vue.

template de ToDoList.vue

<template>
    <div class="container mt-4">
    <h2 class="text-center">Lista de Tareas</h2>
    <to-do-form></to-do-form>
    <ul class="list-group">
        <li class="list-group-item" v-for="item in tareas" :key="item.id">
            <to-do-item
            :label="item.label"
            :done="item.done"
            :id="item.id">
        </to-do-item>
        </li>
    </ul>
    </div>
</template>

Ahora cuando veas tu sitio en ejecución debes ver algo parecido a esto:

ToDoList con formulario

Si lo completas y haces clic en el botón "Agregar", la página enviará el formulario al servidor, pero esto no es lo que buscamos. Lo que queremos es ejecutar un método en el evento submit que agregue la nueva tarea a la lista ToDoItem de datos definida dentro App. Para ello, necesitamos agregar un método a la instancia del componente.

Uso de methods vinculados a eventos en Vue.js

Para que un método esté disponible para el componente ToDoForm, debemos agregarlo al objeto del componente. Esto se realiza dentro de una propiedad methods de nuestro componente, que se ubica en la misma ubicación que data(), props,, etc. Esta propiedad methods contiene cualquier método que necesitemos llamar en nuestro componente. Al referenciarlos, los métodos se ejecutan completamente, por lo que no es recomendable usarlos para mostrar información dentro de la plantilla. Para mostrar datos provenientes de cálculos, se debe usar una propiedad computed, que abordaremos más adelante.

Primero necesitamos agregar un método onSubmit a nuestro componente ToDoForm. Lo utilizaremos para gestionar la acción (evento) de enviar el formulario. Este método se ejecutará cuando el usuario haga clic en el botón "Agregar".

1
2
3
4
5
6
7
export default {
  methods: {
    onSubmit() {
      console.log("form submitted");
    },
  },
};

A continuación, necesitamos vincular el método al controlador de eventos submit de nuestro elemento <form>. De forma similar a cómo Vue usa la sintaxis v-bind para vincular atributos, Vue tiene una directiva especial para la gestión de eventos: v-on. Esta directiva v-on funciona mediante la sintaxis v-on:event="method". Y, al igual que v-bind, también existe una sintaxis abreviada: @event="method". Para mayor consistencia, usaremos la sintaxis abreviada. Añada el controlador submit a su elemento <form> de la siguiente manera:

<form @submit="onSubmit"></form>
Al ejecutar esto, la aplicación sigue enviando los datos al servidor, lo que provoca una actualización. Dado que todo el procesamiento se realiza en el cliente, no hay ningún servidor que gestione el postback. Además, se pierde todo el estado local al actualizar la página. Para evitar que el navegador publique en el servidor, debemos detener la acción predeterminada del evento mientras se propaga por la página (Event.preventDefault() en JavaScript estándar). Vue cuenta con una sintaxis especial llamada modificadores de eventos que gestiona esto directamente en nuestra plantilla. Los modificadores se añaden al final de un evento con un punto, como se muestra a continuación: @event.modifier. Aquí hay una lista de modificadores de eventos:

  • .stop → Detiene la propagación del evento.
  • .prevent → Previene la acción predeterminada del evento.
  • .self → Solo activa el evento si el objetivo es el elemento en el que se ha definido.
  • .{Key} → Activa el evento solo si se presiona la tecla especificada.
  • .native → Escucha el evento en el elemento raíz del componente.
  • .once → Activa el evento solo una vez.
  • .left → Activa el evento solo si se hace clic con el botón izquierdo.
  • .right → Activa el evento solo si se hace clic con el botón derecho.
  • .middle → Activa el evento solo si se hace clic con el botón central.
  • .passive → Equivalente a utilizar el parámetro { passive: true } al crear un detector de eventos en JavaScript básico usando addEventListener().

En este caso, necesitamos usar el modificador .prevent para detener la acción de envío predeterminada del navegador. Añáde .prevent al controlador @submit en tu plantilla de la siguiente manera:

<form @submit.prevent="onSubmit"></form>

Si intenta enviar el formulario ahora, notará que la página no se recarga. Si abre la consola, podrá ver los resultados que añadimos con console.log() a nuestro método onSubmit().

Vinculación de datos bidireccional con v-model

A continuación, necesitamos una forma de obtener el valor <input> del formulario para que podamos agregar el nuevo elemento pendiente a nuestra lista de datos ToDoItem.

Lo primero que necesitamos es una propiedad data en nuestro formulario para rastrear el valor de la tarea pendiente.

Agragamos data() a nuestro componente ToDoForm y añadimos una propiedad label que rastreará el valor del campo de texto. Podemos dar un valor inicial a la propiedad label como una cadena vacía.

ToDoForm.vue

<script>
export default {
    methods: {
        onSubmit() {
        console.log("form submitted");
        },
    },
    data() {
        return {
        label: "",
        };
    },
};
</script>

Ahora necesitamos una forma de asociar el valor del campo new-todo-input del elemento al campo label. Vue tiene una directiva especial para esto: v-model. v-model se enlaza a la propiedad de datos que se le asigna y la mantiene sincronizada con <input>. v-model funciona con todos los tipos de entrada, incluyendo casillas de verificación, opciones de radio y entradas de selección. Para usar v-model, se agrega un atributo con la estructura a <input> v-model="variable". En nuestro caso, lo añadiríamos a nuestro campo new-todo-input como se muestra a continuación. Haga esto ahora:

1
2
3
4
5
6
<input
  type="text"
  id="new-todo-input"
  name="new-todo"
  autocomplete="off"
  v-model="label" />

Probemos el uso de v-model registrando el valor de los datos enviados en nuestro método onSubmit(). En los componentes, se accede a los atributos de datos mediante la palabra clave this. Por lo tanto, accedemos a nuestro campo label usando this.label.

Actualice su método onSubmit() para que se vea así:

1
2
3
4
5
methods: {
  onSubmit() {
    console.log('Label value: ', this.label);
  }
},

Ahora regresa a la aplicación en ejecución, agrega texto al campo <input> y haz clic en el botón "Agregar". Deberías ver el valor ingresado registrado en tu consola, por ejemplo:

Label value:  Acabar el proyecto

Cambiar el comportamiento de v-model con modificadores

De forma similar a los modificadores de eventos, también podemos agregar modificadores para cambiar el comportamiento de v-model. En nuestro caso, hay dos que vale la pena considerar. El primero, .trim, eliminará los espacios antes o después de la entrada. Podemos agregar el modificador a nuestra declaración v-model de la siguiente manera: v-model.trim="label".

El segundo modificador que debemos considerar se llama .lazy. Este modificador cambia al sincronizar v-model el valor de las entradas de texto. Como se mencionó anteriormente, la sincronización v-model funciona actualizando la variable mediante eventos. Para las entradas de texto, esta sincronización se realiza mediante el evento input. Esto suele significar que Vue sincroniza los datos después de cada pulsación de tecla. El modificador .lazy hace que v-model se use el evento change en su lugar. Esto significa que Vue solo sincronizará los datos cuando la entrada pierda el foco o se envíe el formulario. Para nuestros fines, esto es mucho más razonable, ya que solo necesitamos los datos finales.

Para utilizar tanto el modificador .lazy como el modificador .trim juntos, podemos encadenarlos, por ejemplo v-model.lazy.trim="label".

Actualiza tu atributo v-model a la cadena (chain) como se muestra arriba y vuelve a probar tu aplicación. Por ejemplo, prueba enviando un valor con espacios en blanco al final

Pasar datos a los padres con eventos personalizados

Ya casi podemos añadir nuevas tareas a nuestra lista. Lo siguiente que necesitamos es pasar la tarea recién creada a nuestro componente ToDoList. Para ello, podemos configurar nuestro componente para que ToDoForm emita un evento personalizado que pase los datos y ToDoList lo escuche. Esto funciona de forma muy similar a los eventos nativos en elementos HTML: un componente secundario puede emitir un evento que se escucha mediante v-on.

En el controlador onSubmit de eventos de nuestro ToDoForm, agreguemos un tevento todo-added. Los eventos personalizados se emiten así: this.$emit("event-name"). Es importante saber que los controladores de eventos distinguen entre mayúsculas y minúsculas y no pueden incluir espacios. Las plantillas de Vue también se convierten a minúsculas, lo que significa que no pueden escuchar eventos nombrados con mayúsculas.

Ahora vamos a realizar las modificaciones necesarias.

Primero, reemplazar console.log() en el método onSubmit() con this.$emit("todo-added").

1
2
3
4
5
methods: {
  onSubmit() {
    this.$emit("todo-added");
  }
},

A continuación, en ToDoList.vue vuelva a agregar una propiedad methods a su objeto de componente que contenga un método addToDo(), como se muestra a continuación. Por ahora, este método solo puede escibir el texto To-do added en la consola.

ToDoList.vue script

export default {
    name: "ToDoList",
    components: {
        ToDoItem,
        ToDoForm,
    },
data() {
        return {
        ToDoItems: [
            { id: "1", label: "Learn Vue", done: false },
            { id: "2", label: "Create a Vue project with the CLI", done: true },
            { id: "3", label: "Have fun", done: true },
            { id: "4", label: "Create a to-do list", done: false },
            ],
        };
    },
    methods: {
        addToDo() {
        console.log("To-do added");
        },
    },
};

A continuación, agregue un detector de eventos para el evento todo-added a , que llama al método addToDo() cuando se activa el evento. Abreviando @, el detector se vería así @todo-added="addToDo":

<to-do-form @todo-added="addToDo"></to-do-form>

Al enviar tu ToDoForm, deberías ver el registro de consola del método addToDo(). Esto es correcto, pero aún no estamos devolviendo ningún dato al componente ToDoList.vue. Podemos hacerlo pasando argumentos adicionales a la función this.$emit() en el componente ToDoForm. En este caso, al ejecutar el evento, queremos pasar los datos label junto con él. Esto se logra incluyendo los datos que se desean pasar como otro parámetro en el método $emit(): this.$emit("todo-added", this.label) Esto es similar a cómo los eventos nativos de JavaScript incluyen datos, excepto que los eventos personalizados de Vue no incluyen ningún objeto de evento por defecto. Esto significa que el evento emitido coincidirá directamente con el objeto que se envíe. En nuestro caso, nuestro objeto de evento será simplemente una cadena. Actualice su méotodo onSubmit() de la siguiente manera:

1
2
3
4
5
methods: {
  onSubmit() {
    this.$emit("todo-added", this.label);
  }
},

Para poder recoger realmente estos datos dentro de ToDoList.vue, necesitamos agregar un parámetro a nuestro método addToDo() que incluya el label del nuevo elemento pendiente.

Regrese ToDoList.vue y actualice esto ahora:

1
2
3
4
5
methods: {
  addToDo(toDoLabel) {
    console.log('To-do added:', toDoLabel);
  }
}
Si vuelves a probar tu formulario, verás que el texto que introduzcas se registra en tu consola al enviarlo. Vue pasa automáticamente los argumentos después del nombre del evento this.$emit() a tu controlador de eventos.

Actualización de la lista de tareas

Ahora que tenemos los datos disponibles ToDoForm en ToDoList.vue, necesitamos agregar un elemento que los represente al array ToDoItems. Esto se puede hacer insertando un nuevo objeto de tarea pendiente en el array que contiene los nuevos datos.

Actualiamos el método addToDo() en ToDoList.vue para que añada un nuevo objeto de tarea pendiente al array ToDoItems. Para hacer esto, necesitamos crear un nuevo objeto con un id único y el label que se pasa a través del evento personalizado.

1
2
3
addToDo(toDoLabel) {
  this.ToDoItems.push({id: "todo-" + nanoid(), label: toDoLabel, done: false});
}

Intente probar su formulario nuevamente y debería ver que los nuevos elementos de tareas pendientes se agregan al final de la lista.

Hagamos una mejora adicional antes de continuar. Si envía el formulario con la entrada vacía, los elementos de la lista de tareas pendientes sin texto se añadirán a la lista. Para solucionarlo, podemos evitar que el evento "todo-added" se active cuando el nombre esté vacío. Dado que el modificador .trim ya está recortando el nombre, solo necesitamos comprobar si la cadena está vacía. Regresa a tu componente ToDoForm y actualiza el método onSubmit() como se indica a continuación. Si el valor de la etiqueta está vacío, no se emitirá el evento todo-added.

1
2
3
4
5
6
onSubmit() {
  if (this.label === "") {
    return;
  }
  this.$emit('todo-added', this.label);
}

Prueba el formulario e nuevo. Ahora no debes poder añadir elementos vacios a la lista de tareas.

Uso de v:model para actualizar un valor de entrada

Hay un aspecto más que corregir en nuestro componente ToDoForm: después de enviarlo, el <input> aún contiene el valor anterior. Pero esto es fácil de solucionar: como estamos usando v-modelpara enlazar los datos a <input> en ToDoForm, si configuramos el parámetro name como una cadena vacía, la entrada también se actualizará.

Actualice el método onSubmit() de su componente ToDoForm a esto:

1
2
3
4
5
6
7
onSubmit() {
  if (this.label === "") {
    return;
  }
  this.$emit('todo-added', this.label);
  this.label = "";
}

Ahora, cuando haga clic en el botón "Agregar", la "nueva entrada de tarea pendiente" se borrará automáticamente.

Resumen

Excelente. ¡Ya podemos añadir tareas a nuestro formulario! Nuestra aplicación empieza a ser interactiva, pero un problema es que hasta ahora hemos ignorado por completo su diseño. En el próximo artículo, nos centraremos en solucionar esto, analizando las diferentes maneras en que Vue ofrece estilos para componentes.

9.4 Tarea Práctica: Implementación en el Proyecto del Blog

Objetivo: Implementar un formulario en el Blog que permita agregar una nueva entrada utilizando v-model y methods.

Pasos a seguir:

1️⃣ Agregar un campo de entrada para el título y el contenido de la nueva entrada en BlogList.vue.
2️⃣ Usar v-model para capturar la entrada del usuario.
3️⃣ Crear un método agregarEntrada que agregue una nueva entrada a la lista.
4️⃣ Mostrar la nueva entrada en la lista de BlogPost.vue.


🔒 Solución del Blog (oculta inicialmente)

Código de BlogList.vue

Código de BlogList.vue
<template>
<div class="container mt-4">
    <h2 class="text-center">Lista de Entradas</h2>
    <input v-model="nuevaEntrada.titulo" placeholder="Título" />
    <textarea v-model="nuevaEntrada.contenido" placeholder="Contenido"></textarea>
    <button @click="agregarEntrada">Agregar Entrada</button>
    <BlogPost v-for="(post, index) in posts" :key="index" :titulo="post.titulo" :contenido="post.contenido" />
</div>
</template>
<script>
import BlogPost from './BlogPost.vue';
export default {
components: { BlogPost },
data() {
    return {
    posts: [],
    nuevaEntrada: { titulo: "", contenido: "" }
    };
},
methods: {
    agregarEntrada() {
    if (this.nuevaEntrada.titulo.trim() && this.nuevaEntrada.contenido.trim()) {
        this.posts.push({ ...this.nuevaEntrada });
        this.nuevaEntrada.titulo = "";
        this.nuevaEntrada.contenido = "";
    }
    }
}
};
</script>

Este ejercicio permitirá a los alumnos aplicar v-model, v-on y methods en su proyecto del Blog para gestionar entradas dinámicamente.