Hoy vengo con un caso práctico para afianzar los conocimientos que hemos ido viendo a lo largo de estos artículos.

Vamos a crear la típica página web para crear tareas que se suele hacer cuando empiezas con un framework. Siempre recomiendo ir creando ejemplos de este tipo según aprendes cualquier lenguaje porque muchas cosas no las entiendes del todo hasta que no las pones en práctica.

Veamos las historias de usuario de e

🗺️ Hoja de ruta

  • Crear el proyecto de Vue
  • Crear el formulario para crear tareas
  • Mostrar las tareas
  • Hacer que al pulsar las tareas se marquen como completadas
  • Crear estilos para las tareas

Creando el proyecto de Vue

Como vimos anteriormente, la manera más sencilla de crear un proyecto de Vue es utilizando su asistente de línea de comandos llamado Vue CLI 🚧.

Si todavía no lo tienes instalado en tu equipo ejecuta:

npm install -g @vue/cli

Ahora ejecuta el comando para crear el proyecto con el nombre que quieras, en mi caso "tareas".

vue create tareas

Te preguntará si quieres crear el proyecto directamente o si quieres elegir las opciones manualmente. Yo voy a crear el proyecto mediante el método default pero tú puedes seleccionar cada cosa por separado si lo prefieres.

🎉 Listo, proyecto creado y primera tarea completada.

# ✅ Crear el proyecto de Vue

El comando habrá creado una carpeta en el directorio en el que te encontraras en la terminal al realizar ejecutar el comando. Para acceder simplemente ejecuta cd tareas

Te recomiendo que cada vez que termines una tarea, hagas commit con los cambios para tenerlo guardado por si quieres verlos o volver a un punto anterior.

Ahora ejecuta npm run serve dentro del proyecto para comprobar que el proyecto se ha creado correctamente.

Creando el formulario con Vue

Vamos con la siguiente tarea, la de crear el formulario y para ello vamos a seguir un ciclo iterativo, es decir, poco a poco añadimos funcionalidad y por el momento no nos preocupamos por cómo se vea o si es feo o bonito.

De lo que se trata es de que funcione y más adelante mejoraremos el apartado visual.

Lo primero que voy a hacer es borrar completamente el componente HelloWorld.vue que viene por defecto al crear el proyecto.

Dentro de la carpeta /src/components voy a crear un archivo llamado TodoList.vue con la estructura básica de un componente. Puedes ver las partes que tiene un componente en el artículo de Componentes de Vue.

<template></template>

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

<style></style>

Para que todo sea más sencillo en este componente irá toda la lógica de la aplicación.

En posteriores capítulos veremos cómo separar funcionalidad y cómo pasar información de un componente a otro pero por el momento vamos a hacer las cosas sencillas para aprender.

Ante de seguir tenemos que sustituir dentro del componente App.vue el componente de HelloWorld por el que acabamos de crear. Quedaría así:

<template>
  <div id="app">
    <todo-list />
  </div>
</template>

<script>
import TodoList from "./components/TodoList.vue";

export default {
  name: "app",
  components: {
    TodoList,
  },
};
</script>

<style>
#app {
  font-family: "Avenir", Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

Hora de crear el formulario. El formulario va a constar de un elemento <form> y dentro un input para escribir la tarea y un botón submit.

Recogeremos el valor del input y lo guardaremos en una variable gracias al Bindeo del v-model de Vue 🚧 que nos permite tener la variable newTask actualizada con lo que escriba el usuario.

Dentro del archivo TodoList que hemos creado antes por el momento he puesto esto:

<template>
  <form id="app">
    <label class="label" for="task">Nueva tarea: </label>
    <input type="text" v-model="newTask" id="task">
      {{newTask}}
    <input type="submit" value="Crear tarea">
  </form>
</template>

<script>
export default {
  data: () => ({
    newTask: ""
  })
};
</script>

<style>
</style>

Encima del botón de submit he puesto la variable de forma temporal para que veas que se actualiza en tiempo real su valor al escribir sin tener que hacer nada.

Lo que queda para terminar esta tarea es poder almacenar la tarea que escriba el usuario.

Para ello vamos a crear en el data una variable para almacenar todas las tareas (por defecto se crea vacío) y un método conectado el submit para que al darle al botón se guarde en el array (echa un ojo a Escuchar eventos nativos en Vue.

Al evento submit le he puesto el modificador prevent para que no se recargue toda la web al darle al botón de crear tarea.

Vamos a ello:

<template>
  <form id="app" @submit.prevent="createTask">
    <label class="label" for="task">Nueva tarea: </label>
    <input type="text" v-model="newTask" id="task" />
    <input type="submit" value="Crear tarea" />
  </form>
</template>

<script>
export default {
  data: () => ({
    newTask: "",
    tasks: [],
  }),
  methods: {
    createTask() {
      let task = {
        text: this.newTask,
        completed: false,
      };
      this.tasks.push(task);
      this.newTask = "";
      console.log(this.tasks);
    },
  },
};
</script>

<style></style>

Dentro del método que llamamos de createTask lo que hago es crear una variable local llamada task en la que defino la estructura que tendrán todas las tasks.

Cada tarea será un objeto con dos parámetros, "texto" que será un string con la descripción de la tarea (lo que el usuario mete en el input) y "completed", un booleano para saber si está completada (por defecto a false).

Como las propiedades del data son reactivas lo único que tenemos que hacer es meter dentro del parámetro text de la task el valor de la variable newTask que tendrá el valor introducido por el usuario.

Luego lo que se hace es meter la tarea dentro del array de tareas definido en la sección data y limpiar el valor de newTask para que cada vez que se cree una el input se limpie.

🎉 Pues listo, otra tarea completada, hora de hacer commit y pasar a la siguiente.

# ✅ Crear el formulario para crear tareas

Mostrar las tareas creadas

Mostrar las tareas no debería que ser muy complicado, solo tenemos que mostrar el array con la tareas (usando los Bucles v-for en Vue en la vista) y definir una clase dependiendo de si la tarea se ha completado o no. Vamos con ello.

Para pintar las tareas lo hacemos con un v-for:

<template>
  <form id="app" @submit.prevent="createTask">
    <label class="label" for="task">Nueva tarea:</label>
    <input type="text" v-model="newTask" id="task" />
    <input type="submit" value="Crear tarea" />
    <ul>
      <li v-for="(task, i) in tasks" :key="'task' + i">{{ task.text }}</li>
    </ul>
  </form>
</template>

Recuerda añadir el parámetro único key para que vue pueda identificar cada uno de los elementos del bucle.

Por último dentro del bucle accedemos al texto de la tarea accediendo a un propiedad text (o el que hayas definido al crear tareas).

Vamos ahora con los estilos para que el usuario sepa si la tarea está completada o no.

Gracias a Atributos, clases y estilos dinámicos en VueJS podemos hacer que se añada una clase cuando la tarea esté completada:

<template>
  <form id="app" @submit.prevent="createTask">
    <label class="label" for="task">Nueva tarea:</label>
    <input type="text" v-model="newTask" id="task" />
    <input type="submit" value="Crear tarea" />
  </form>
  <ul>
    <li
      v-for="(task, i) in tasks"
      :key="'task' + i"
      :class="{ completed: task.completed }"
    >
      {{ task.text }}
    </li>
  </ul>
</template>

<script>
export default {
  data: () => ({
    newTask: "",
    tasks: [],
  }),
  methods: {
    createTask() {
      let task = {
        text: this.newTask,
        completed: true,
      };
      this.tasks.push(task);
      this.newTask = "";
      console.log(this.tasks);
    },
  },
};
</script>
<style scoped>
.completed {
  text-decoration: line-through;
  color: grey;
}
</style>

La clase completed se añadirá dependiendo de si el objeto task que está pintando el bucle tiene la propiedad completed a true o false.

Para que veas que funciona he puesto en el método de crear tareas que por defecto las ponga con el parámetro completed a true para que todas las tareas se creen ya completadas.

Por último he añadido estilos a la clase completed. En mi caso he hecho que se pinte el texto tachado y las letras de color gris más claro para que se diferencie más, pero tu puedes poner los estilos que quieras.

🎉 Tarea completada.

✅ Mostrar las tareas

Completando tareas

Toca hacer que las tareas se completen. En mi caso he decidido que se completen al pulsar sobre ellas.

Para este ejemplo lo voy hacer con los Eventos de click en Vue 🚧 pero lo correcto y accesible sería crear un botón al lado de cada tarea.

<template>
  <form id="app" @submit.prevent="createTask">
    <label class="label" for="task">Nueva tarea:</label>
    <input type="text" v-model="newTask" id="task" />
    <input type="submit" value="Crear tarea" />
  </form>
  <ul>
    <li
      v-for="(task, i) in tasks"
      :key="'task' + i"
      :class="{ completed: task.completed }"
      @click="completeTask(task.text)"
    >
      {{ task.text }}
    </li>
  </ul>
</template>

<script>
export default {
  data: () => ({
    newTask: "",
    tasks: [],
  }),
  methods: {
    createTask() {
      let task = {
        text: this.newTask,
        completed: true,
      };
      this.tasks.push(task);
      this.newTask = "";
      console.log(this.tasks);
    },
    completeTask(taskText) {
      for (let i = 0; i < this.tasks.length; i++) {
        let task = this.tasks[i];
        if (taskText === task.text) {
          task.completed = !task.completed;
        }
      }
    },
  },
};
</script>
<style scoped>
.completed {
  text-decoration: line-through;
  color: grey;
}
</style>

Veamos lo que he cambiado.

En primer lugar, he llamado al evento @click dentro de cada tarea en la vista. El evento de click llama a la función completeTask pasando el texto de la tarea (lo mejor hubiera sido asignar a cada tarea un id y pasar a este función el id de la tarea a completar).

Ya en la función, simplemente lo que hago es recorrer todas las tareas en busca de la tarea que ha pulsado el usuario. Si se encuentra la tarea hace que su parámetro completed se invierta, de tal forma que si no se ha completado se complete y viceversa.

DISCLAIMER. Sí, se que esta solución no es ni de lejos la más óptima y correcta, pero he optado por esto para que sea sencillo de entender. Por ejemplo no hace falta recorrer toda la lista para encontrar una tarea, Puedes añadir un break cuando la encuentre para que el bucle no continúe o puedes usar la función find de javascript.

🎉 Otra tarea más completada, falta una.

✅ Hacer que al pulsar las tareas se marquen como completadas

Estilos

Ya solo queda añadir los estilos CSS que quieras, a tu gusto.

Aquí no hay mucho que explicar, yo he optado por añadir estos. Te dejo como ha quedado todo el componente:

<template>
  <div class="task-list">
    <h1>{{tasks.length}} Tasks</h1>
    <form class="form" @submit.prevent="createTask">
      <label class="label" for="task">Nueva tarea:</label>
      <input class="input" type="text" v-model="newTask" id="task" />
      <input class="button" type="submit" value="Crear tarea" />
    </form>
    <ul class="list">
      <li
        class="task"
        v-for="(task, i) in tasks"
        :key="'task' + i"
        :class="{completed: task.completed}"
        @click="completeTask(task.text)"
      >{{task.text}}</li>
    </ul>
  </div>
</template>

<script>
export default {
  data: () => ({
    newTask: "",
    tasks: []
  }),
  methods: {
    createTask() {
      let task = {
        text: this.newTask,
        completed: false
      };
      this.tasks.push(task);
      this.newTask = "";
      console.log(this.tasks);
    },
    completeTask(taskText) {
      for (let i = 0; i < this.tasks.length; i++) {
        let task = this.tasks[i];
        if (taskText === task.text) {
          task.completed = !task.completed;
        }
      }
    }
  }
};
</script>
<style scoped>
.task-list {
  width: 800px;
  max-width: 100%;
  margin: 0px auto;
}
.form {
  background: white;
  border-radius: 12px;
  padding: 30px;
  box-shadow: 0px 10px 22px -1px rgba(0,0,0,0.25);
  margin-top: 10px;
}
.label {
  display: block;
  margin-bottom: 10px;
}
.input {
  height: 35px;
}
.button {
  margin-left: 20px;
  height: 35px;
  border: none;
  border-radius: 5px;
  box-shadow: 0 1px 4px rgba(0, 0, 0, .2);
  background-color: #2ecc71;
  color: #ecf0f1;
  cursor: pointer
}
.list {
  margin-top: 40px;
}
.task {
  cursor: pointer;
  margin: 10px 0;
}
.completed {
  text-decoration: line-through;
  color: lightgrey;
}
</style>

Así quedaría todo:

El diseño lo he hecho en muy rápido en 2 minutos, no me lo tomes en cuenta.

Pues todo listo, te dejo el proyecto subido a Codesanbox para que puedas jugar con él:

Proyecto completo en codesabox

Te dejo un par de ejercicios como deberes por si quieres experimentar por tu cuenta:

  • Botón dentro de cada tarea para poder eliminarlas completamente
  • Botón encima de la lista de tareas para poder marcar o desmarcar todas como completadas

Conclusiones

Espero de verdad que te haya gustado este ejemplo. Yo creo que este tipo de ejercicios vienen muy bien cuando estás aprendiendo porque no es solo teoría y se ven las cosas de otra forma.

No te quedes solo con este ejemplo, con lo que ya sabes deberías ser ya capaz de hacer cosas bastantes interesantes. Te animo a que intentes crear otros proyectos con lo que ya sabes para probar tus conocimientos.

En los siguientes episodios seguiremos investigando más funcionalidades de Vue para que puedas hacer cosas más increíbles.

Lo siguiente interesante para aprender es la comunicación entre componentes con los Props en Vue y los Eventos entre componentes en Vue