Plugins y extensiones recomendadas
- Instalar plugins y extensiones de Visual Studio Code
Creacion de Proyecto
- Limpiar App.vue y borrar componente HelloWorld.vue
1
2
3
4
5
6
7
8
9
10
<template>
Hello World!
</template>
<script>
export default {
name: 'App',
}
</script>
- Agregar CDN de BootStrap y de Font Awesome a public/index.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css" integrity="sha512-KfkfwYDsLkIlwQp6LFnl8zNdLGxu9YAA1QvwINks4PhcElQSvqcyVLLD9aMhXd13uQjoXtEKNosOWaZqXgel0g==" crossorigin="anonymous"
referrerpolicy="no-referrer" />
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-0evHe/X+R7YkIZDRvuzKMRqM+OrBnVFBL6DOitfPri4tjfHxaWutUpFmBp4vmVor" crossorigin="anonymous">
<title>
<%= htmlWebpackPlugin.options.title %>
</title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
Componente TodoApp.vue
- Crear componentes TodoApp.vue (componente principal)
1
2
3
4
5
6
7
8
9
10
<template>
<h1>ToDos</h1>
</template>
<script>
export default {
}
</script>
- Llamar componente TodoApp.vue a App.vue. Agregar div con class container
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<div class="container">
<todo-app />
</div>
</template>
<script>
import TodoApp from './components/TodoApp.vue'
export default {
components: { TodoApp },
name: 'App',
}
</script>
- Revisar App en navegador
Aplicacion de Vue en el navegador
Componente TodoFormulario.vue
- Crear Componente TodoFormulario.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<form>
<input
type="text"
class="form-control my-3"
placeholder="Ingresar ToDo">
</form>
</template>
<script>
export default {
}
</script>
- Importar componente TodFormulario.vue a Todoapp-vue
1
2
3
4
5
6
7
8
9
10
11
12
<template>
<h1>ToDos</h1>
<todo-formulario />
</template>
<script>
import TodoFormulario from './TodoFormulario.vue'
export default {
components: { TodoFormulario },
}
</script>
- Revisar App en el navegador
Aplicacion de Vue en el navegador
- Utilizando composition API crearemos un arreglo en el componente padre TodoApp que podra ser accedido por sus componentes hijos. Creando una constante ref y enviandola a traves de un provide.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<h1>ToDos</h1>
<todo-formulario />
</template>
<script>
import { provide, ref } from 'vue'
import TodoFormulario from './TodoFormulario.vue'
export default {
components: { TodoFormulario },
setup(){
const todos = ref([])
provide('todos', todos)
}
}
</script>
- Se configura TodoFormulario.vue para recibir data del padre a traves de inject
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<form>
<input
type="text"
class="form-control my-3"
placeholder="Ingresar ToDo">
</form>
</template>
<script>
import { inject } from '@vue/runtime-core'
export default {
setup(){
const todos = inject('todos')
console.log(todos.value)
}
}
</script>
Inspeccionar consola en el navegador y ver tambien Vuetools
Crear funcion agregarToDo en TodoFormulario.vue y vincularlo con el template, verificar funcionamiento en consola del navegador
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<form @submit.prevent="agregarToDo">
<input
type="text"
class="form-control my-3"
placeholder="Ingresar ToDo">
</form>
</template>
<script>
import { inject } from '@vue/runtime-core'
export default {
setup(){
const todos = inject('todos')
const agregarToDo = () => {
console.log('agregaste un TODO')
}
return{agregarToDo}
}
}
</script>
- Agregar en TodoFormulario.vue un v-model para enviar datos del input a la funcion agregarToDo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<template>
<form @submit.prevent="agregarToDo">
<input
type="text"
class="form-control my-3"
placeholder="Ingresar ToDo"
v-model="texto">
</form>
</template>
<script>
import { inject, ref } from '@vue/runtime-core'
export default {
setup(){
const todos = inject('todos')
const texto = ref('')
const agregarToDo = () => {
console.log(texto.value)
}
return{agregarToDo, texto}
}
}
</script>
- Construir objeto todo (singular) que contenga los datos a utilizar: id, texto, estado.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<template>
<form @submit.prevent="agregarToDo">
<input
type="text"
class="form-control my-3"
placeholder="Ingresar ToDo"
v-model="texto">
</form>
</template>
<script>
import { inject, ref } from '@vue/runtime-core'
export default {
setup(){
const todos = inject('todos')
const texto = ref('')
const agregarToDo = () => {
const todo = {
descripcion: texto.value,
estado: false,
id: Date.now()
}
console.log(todo)
}
return{agregarToDo, texto}
}
}
</script>
- Se crean validaciones para texto, vacio y tambien se limpia el texto del formulario
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<template>
<form @submit.prevent="agregarToDo">
<input
type="text"
class="form-control my-3"
placeholder="Ingresar ToDo"
v-model.trim="texto">
</form>
</template>
<script>
import { inject, ref } from '@vue/runtime-core'
export default {
setup(){
const todos = inject('todos')
const texto = ref('')
const agregarToDo = () => {
if(texto.value === ''){
console.log('vacio')
return
}
const todo = {
descripcion: texto.value,
estado: false,
id: Date.now()
}
texto.value = ''
console.log(todo)
}
return{agregarToDo, texto}
}
}
</script>
- A traves del metodo push empujamos o enviamos el objeto todo al arreglo todos que se almacenara en el componente padre TodoApp.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<template>
<form @submit.prevent="agregarToDo">
<input
type="text"
class="form-control my-3"
placeholder="Ingresar ToDo"
v-model.trim="texto">
</form>
</template>
<script>
import { inject, ref } from '@vue/runtime-core'
export default {
setup(){
const todos = inject('todos')
const texto = ref('')
const agregarToDo = () => {
if(texto.value === ''){
console.log('vacio')
return
}
const todo = {
descripcion: texto.value,
estado: false,
id: Date.now()
}
todos.value.push(todo)
console.log(todos.value)
texto.value = ''
//console.log(todo)
}
return{agregarToDo, texto}
}
}
</script>
- Verificar arreglo en la consola del navegador
- Agregar watchEffect a TodoApp.vue para esperar el retorno de los todo individuales. Comentar console.log(todo.value) de TodoFormulario.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<h1>ToDos</h1>
<todo-formulario />
</template>
<script>
import { provide, ref, watchEffect } from 'vue'
import TodoFormulario from './TodoFormulario.vue'
export default {
components: { TodoFormulario },
setup(){
const todos = ref([])
provide('todos', todos)
watchEffect(() => {
console.log(todos.value.length)
console.log(todos.value)
})
}
}
</script>
Componente TodoListado.vue
- Crear componente TodoListado.vue, que mostrara el contenido del arreglo con los todos e importarlo a TodoApp.vue
1
2
3
4
5
6
7
8
9
<template>
Listado de TODOS
</template>
<script>
export default {
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<h1>ToDos</h1>
<todo-formulario />
<todo-listado />
</template>
<script>
import { provide, ref, watchEffect } from 'vue'
import TodoFormulario from './TodoFormulario.vue'
import TodoListado from './TodoListado.vue'
export default {
components: { TodoFormulario, TodoListado },
setup(){
const todos = ref([])
provide('todos', todos)
watchEffect(() => {
console.log(todos.value.length)
console.log(todos.value)
})
}
}
</script>
- Verificar en el navegador con la consola
Componente TodoItem.vue
- Modificar TodoListado.vue para contener un list group como el ejemplo de List group BootStrap, donde el ul quedara en TodoListado.vue y el li en un nuevo componente llamado TodoItem.vue (el cual estara dentro de TodoListado)
1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<ul class="list-group">
<todo-item />
</ul>
</template>
<script>
import TodoItem from './TodoItem.vue'
export default {
components: { TodoItem },
}
</script>
1
2
3
4
5
6
7
8
9
<template>
<li class="list-group-item">Cras justo odio</li>
</template>
<script>
export default {
}
</script>
- Recorrer el arreglo todo disponible en el componente padre con un v-for dentro del componente TodoListado.vue. Se manda a traves de un prop el valor del todo a TodoItem.vue. Vue props
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<ul class="list-group">
<todo-item
v-for="todo in todos" :key="todo.id"
:todo="todo"
/>
</ul>
</template>
<script>
import { inject } from '@vue/runtime-core'
import TodoItem from './TodoItem.vue'
export default {
components: { TodoItem },
setup(){
const todos = inject('todos')
return {todos}
}
}
</script>
- Se modifica TodoItem.vue para recibir el prop, agregando estilos para cada item en el listado
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<li class="list-group-item d-flex justify-content-between">
<span role="button">
{ { todo.descripcion } }
</span>
<span role="button">
<i class="fas fa-times"></i>
</span>
</li>
</template>
<script>
export default {
props: {
todo: {
type: Object,
required: true
}
}
}
</script>
Verificar la app en el navegador Aplicacion de Vue en el navegador
Agregar funcionabilidad de borrar item utilizar el metodo filter. Filter JS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<template>
<li class="list-group-item d-flex justify-content-between">
<span role="button">
{ { todo.descripcion } }
</span>
<span role="button" @click="borrar(todo.id)">
<i class="fas fa-times"></i>
</span>
</li>
</template>
<script>
import { inject } from '@vue/runtime-core'
export default {
props: {
todo: {
type: Object,
required: true
}
},
setup() {
const todos = inject('todos')
const borrar = id => {
todos.value = todos.value.filter(item => item.id !== id)
}
return {borrar}
}
}
</script>
- Agregar funcionalidad de cambiar estado completado, tachado es true, sin completar es false. Podemos utilizar el metodo map. Map JS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<template>
<li class="list-group-item d-flex justify-content-between">
<span role="button" @click="completado(todo.id)">
{ { todo.descripcion } }
</span>
<span role="button" @click="borrar(todo.id)">
<i class="fas fa-times"></i>
</span>
</li>
</template>
<script>
import { inject } from '@vue/runtime-core'
export default {
props: {
todo: {
type: Object,
required: true
}
},
setup() {
const todos = inject('todos')
const borrar = id => {
todos.value = todos.value.filter(item => item.id !== id)
}
const completado = id => {
todos.value = todos.value.map(item => {
if(item.id === id){
item.estado = true
}
return item
})
}
return { borrar, completado }
}
}
</script>
- Agregamos que al cambiar estado tache el texto del item. Ademas modificamos para que cambie entre estado al dar click nuevamente al item.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<template>
<li class="list-group-item d-flex justify-content-between">
<span role="button" @click="completado(todo.id)" :class="{ 'tachado': todo.estado }">
{ { todo.descripcion } }
</span>
<span role="button" @click="borrar(todo.id)">
<i class="fas fa-times"></i>
</span>
</li>
</template>
<script>
import { inject } from '@vue/runtime-core'
export default {
props: {
todo: {
type: Object,
required: true
}
},
setup() {
const todos = inject('todos')
const borrar = id => {
todos.value = todos.value.filter(item => item.id !== id)
}
const completado = id => {
todos.value = todos.value.map(item => {
if (item.id === id) {
/*if (item.estado == false) {
item.estado = true
} else {
item.estado = false
}*/
item.estado = !item.estado
}
return item
})
}
return { borrar, completado }
}
}
</script>
<style>
.tachado {
text-decoration: line-through;
}
</style>
Componente TodoFooter.vue
- Crear componente TodoFooter.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<li class="list-group-item d-flex justify-content-between">
<span role="button">
## Pendientes
</span>
<span role="button">
Eliminar completados
</span>
</li>
</template>
<script>
export default {
}
</script>
- Agregar el componente TodoFooter.vue al componente TodoListado.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template>
<ul class="list-group">
<todo-item
v-for="todo in todos" :key="todo.id"
:todo="todo"
/>
<todo-footer />
</ul>
</template>
<script>
import { inject } from '@vue/runtime-core'
import TodoItem from './TodoItem.vue'
import TodoFooter from './TodoFooter.vue'
export default {
components: { TodoItem, TodoFooter },
setup(){
const todos = inject('todos')
return {todos}
}
}
</script>
Verificar la app en el navegador Aplicacion de Vue en el navegador
Agregar funcionalidad para que el footer sea dinamico. Debe mostrarse cuando hay items en la lista y ocultarse cuando no existan.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<template>
<ul class="list-group">
<todo-item
v-for="todo in todos"
:key="todo.id"
:todo="todo" />
<li
v-if="todos.length == 0"
class="list-group-item">
No hay ToDos
</li>
<todo-footer
v-if="todos.length !== 0" />
</ul>
</template>
<script>
import { inject } from '@vue/runtime-core'
import TodoItem from './TodoItem.vue'
import TodoFooter from './TodoFooter.vue'
export default {
components: { TodoItem, TodoFooter },
setup() {
const todos = inject('todos')
return { todos }
}
}
</script>
- Se implementa en TodoFooter.vue un funcion de tipo computed para el conteo de items pendientes (estado == false)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<template>
<li class="list-group-item d-flex justify-content-between">
<span role="button">
{ { contarPendientes } } Pendientes
</span>
<span role="button">
Eliminar completados
</span>
</li>
</template>
<script>
import { computed, inject } from '@vue/runtime-core'
export default {
setup(){
const todos = inject('todos')
const contarPendientes = computed(() => {
return todos.value.filter(item => item.estado === false).length
})
return { contarPendientes }
}
}
</script>
- Se implementa en TodoFooter.vue un funcion para Eliminar items completados (estado == true) pero al utilizar filter le solicitamos crear un nuevo array solo con los elementos pendientes o que esten en false.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<template>
<li class="list-group-item d-flex justify-content-between">
<span role="button">
{ { contarPendientes } } Pendientes
</span>
<span role="button" @click="eliminarCompletados">
Eliminar completados
</span>
</li>
</template>
<script>
import { computed, inject } from '@vue/runtime-core'
export default {
setup() {
const todos = inject('todos')
const contarPendientes = computed(() => {
return todos.value.filter(item => item.estado === false).length
})
const eliminarCompletados = () => {
todos.value = todos.value.filter(item => item.estado === false)
}
return { contarPendientes, eliminarCompletados }
}
}
</script>
Componente TodoFiltro.vue
- Se crea un nuevo componente llamado TodoFiltro.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<div class="btn-group mt-3 d-flex" role="group" aria-label="Filtro">
<button type="button" class="btn">Pendientes</button>
<button type="button" class="btn">Todos</button>
<button type="button" class="btn">Completados</button>
</div>
</template>
<script>
export default {
};
</script>
- Este nuevo componente se utilizara dentro de TodoListado.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<template>
<ul class="list-group">
<todo-item
v-for="todo in todos"
:key="todo.id"
:todo="todo" />
<li
v-if="todos.length == 0"
class="list-group-item">
No hay ToDos
</li>
<todo-footer
v-if="todos.length !== 0" />
</ul>
<todo-filtro />
</template>
<script>
import { inject } from '@vue/runtime-core'
import TodoItem from './TodoItem.vue'
import TodoFooter from './TodoFooter.vue'
import TodoFiltro from './TodoFiltro.vue'
export default {
components: { TodoItem, TodoFooter, TodoFiltro },
setup() {
const todos = inject('todos')
return { todos }
}
}
</script>
Verificar la app en el navegador Aplicacion de Vue en el navegador
En el componente TodoListado.vue creamos una constante que enviaremos por ref al componente TodoFiltro. Dicha referencia nos servira para recorrer el arreglo y poder enviar variables de estado al filtro
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<template>
<ul class="list-group">
<todo-item
v-for="todo in todos"
:key="todo.id"
:todo="todo" />
<li
v-if="todos.length == 0"
class="list-group-item">
No hay ToDos
</li>
<todo-footer
v-if="todos.length" />
</ul>
<todo-filtro />
</template>
<script>
import { computed, inject, provide, ref } from '@vue/runtime-core'
import TodoItem from './TodoItem.vue'
import TodoFooter from './TodoFooter.vue'
import TodoFiltro from './TodoFiltro.vue'
export default {
components: { TodoItem, TodoFooter, TodoFiltro },
setup() {
const todosApp = inject('todos')
const estado = ref('all')
const todos = computed(() => {
if(estado.value === 'all'){
return todosApp.value
}
if(estado.value === 'pendientes'){
return todosApp.value.filter(item => item.estado === false)
}
if(estado.value === 'completados'){
return todosApp.value.filter(item => item.estado === true)
}
})
provide('estado', estado)
return { todos }
}
}
</script>
- Modificamos el componente TodoFiltro para que pueda acceder a los estados y poder filtrar adecuadamente con los botones de filtro
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<template>
<div class="btn-group mt-3 d-flex" role="group" aria-label="Filtro">
<button
type="button"
class="btn"
@click="filtro('pendientes')"
:class="estado === 'pendientes' ? 'btn-success' : 'btn-dark'"
>Pendientes</button>
<button
type="button"
class="btn"
@click="filtro('all')"
:class="estado === 'all' ? 'btn-success' : 'btn-dark'"
>Todos</button>
<button
type="button"
class="btn"
@click="filtro('completados')"
:class="estado === 'completados' ? 'btn-success' : 'btn-dark'"
>Completados</button>
</div>
</template>
<script>
import { inject } from "vue";
export default {
setup() {
const estado = inject("estado");
const filtro = (valor) => {
estado.value = valor;
};
return { filtro, estado };
},
};
</script>
- Finalmente utilizamos LocalStorage para guardar la data en el navegador. Pro lo que editamos el componente TodoApp.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<template>
<h1>ToDos</h1>
<todo-formulario />
<todo-listado />
</template>
<script>
import { provide, ref, watchEffect } from 'vue'
import TodoFormulario from './TodoFormulario.vue'
import TodoListado from './TodoListado.vue'
export default {
components: { TodoFormulario, TodoListado },
setup() {
const todos = ref([])
provide('todos', todos)
if (localStorage.getItem('todos')) {
todos.value = JSON.parse(localStorage.getItem('todos'))
}
watchEffect(() => {
localStorage.setItem('todos', JSON.stringify(todos.value))
//console.log(todos.value.length)
//console.log(todos.value)
})
}
}
</script>
Estilos CSS
- App.vue CSS
1
2
3
4
5
6
7
8
9
10
11
12
13
<style>
body {
background-image: url(./assets/bg-desktop-dark.jpg);
background-repeat: no-repeat;
background-size: 100% 200px;
background-color: #222222
}
.container {
margin-top: 70px;
max-width: 700px;
}
</style>
- TodoApp.vue CSS
1
2
3
4
5
<style>
h1 {
color: #ffffff;
}
</style>
- TodoFormulario.vue CSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<style>
input[type="text"] {
font-family: sans-serif;
font-size: 18px;
font-weight: 400;
line-height: 12px;
color: #d8d8d8;
border: none;
flex-grow: 1;
background-color: #25273d;
box-sizing: border-box;
height: 48px;
}
input[type="text"]:focus {
outline: none;
color: #d8d8d8;
background-color: #25273d;
}
</style>
- TodoItem.vue CSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<style>
.tachado {
text-decoration: line-through;
}
.list-group-item {
min-width: 300px;
width: 100%;
background-color: #25273d;
border-radius: 5px;
color: #d8d8d8;
}
.fa-times {
color: red;
}
</style>
Verificar la app en el navegador Aplicacion de Vue en el navegador
Finalmente ejecutamos el comando npm run build. La aplicacion quedara lista para ser distribuida en la carpeta dist del proyecto
1
npm run build