Hola, espero que hasta aquí estés disfrutando el curso, recuerda que para dominar un lenguaje o framework de programación debes practicar. Justamente por esto he creado este material para ti, puedes hacerlo en tu entorno local o puedes hacerlo en alguna herramienta como codepen.io. La idea es que practiques lo que has aprendido hasta este punto del curso, te invito a que revises el código de tus compañeros y que te animes a dar feedback así todos podrán ir creciendo.
Manipulación del DOM con Vue.js
Este ejercicio consiste en practicar la funcionalidad de renderizado declarativo que provee Vue.js, para eso vamos a crear una pequeña aplicación web que nos permita hacer seguimiento de tareas utilizando el local storage del Browser. Así vamos a reforzar los conceptos básicos que nos ofrece Vue.js para manipular e interactuar con el DOM.
Ejercicio:
Crear dentro de data una propiedad "name" de tipo String y una propiedad "tasks" de tipo de Array.
Agregar una expresión para mostrar el valor de name y utilizar la directiva apropiada para para mostrar en una lista cada uno de los elementos dentro de task. Cada "task" es un objeto con una propiedad "title" y otra "time". Agreguemos las expresiones necesarias para que en cada tarea podamos visualizar ambas propiedades.
Agregar funcionalidad para crear una nueva tarea:
Vamos a necesitar una nueva propiedad llamada "newTask" que sea un Object. Dentro de este objeto también agregamos una propiedad "tilte" de tipo String y una propiedad "time" de tipo Number. Recuerda inicializar las propiedades con valores default.
Vamos a crear un método llamado "addTask" que agregue una nueva tarea al array "tasks". Una vez agregada también va a reiniciar los valores dentro de "newTaks". Ten en cuenta que antes de agregar la propiedad debemos chequear con los valores de "newTask.title" y "newTask.time" existan (sean distintos de "falsy"). Por otro lado es importante que cada elemento nuevo que agreguemos al array de "tasks" sea un objeto nuevo y no la instancia de "newTask".
Vamos a agregar el HTML, para esto necesitamos dos "inputs" y un "button". También debemos agregar las directivas correspondientes para enlazar el código con la vista.
Creamos también una funcionalidad para cancelar, para eso debemos crear un método llamado "cancel" que simplemente reinicie los valores de las propiedades de newTask. Recordemos agregar un button de HTML donde enlazar este nuevo método.
Es momento de saber cuantas horas llevamos trabajadas, para eso vamos a crear una computed property llamada "totalTime" donde se recorran todas las tareas y se calculo el total del tiempo trabajado. También vamos agregar un elemento HTML con la expresión necesaria para visualizar la propiedad.
Debemos integrar la app con el local storage del browser. Dentro del metodo "addTask", guardamos toda la lista de tareas en dicho storage usando este metodo: "localStorage.setItem('tasks', JSON.stringify(this.tasks))".
Guardando las tareas en el browser podemos persistir la información aunque estemos cerrando o refrescando la página. Además, al momento de crearse el componente, debemos leer esta información para poder cargar la lista de tareas. Para eso dentro del hook "created", escribimos el siguiente código: "this.tasks = JSON.parse(localStorage.getItem('tasks')) || []"
Lo último que nos queda es poder eliminar las tareas que ya no queremos. Para eso vamos a crear un método que se llame "removeTask". Este método debe recibir por parámetro el indice de la tarea y podemos utilizar ese indice (en conjunto con el método "splice" de Array) para eliminar el elemento. Recordemos que tendremos que agregar un botón por cada tarea y cada uno de estos se encarga de llamar al método "removeTask" enviando por parámetro el indice correspondiente. Recordemos invocar la funcionalidad que ya pusimos en "addTask", para actualizar el local storage del Browser.
Por último vamos a mejorar la UI, cuando no haya tareas podemos mostrar un mensaje que indica que no hay ninguna tarea cargada y por otro lado ocultar el lista vacía.
Si en algún punto del ejercicio te sientes perdido, te dejo la versión que yo hice para que puedas consultarla en cualquier momento: https://codepen.io/ianaya89/pen/NgEeVO
Esta muy cool te quedo muy bonito 😃, te felicito! ahora me gustaría mucho saber cómo fue tu experiencia con éste proyecto, cuéntanos que problemas encontraste en el proceso y cómo lograste resolverlos. También te animo a que veas la solución de tus compañeros y les dejes feedback así todos podemos aprender y crecer.
Gracias @yesikita, me gusta mucho la metodologia de este curso, los ejercicios de cada tema son un verdadero plus. Para resolver el ejercicio, me sirvió mucho las soluciones de los compañeros y un poco de: Read-Search-Ask.
Comparto mi Ejercicio hice deploy con Now, se que no tiene buen estilo aunque usé bootstrap xD, pero es funcional. Aquí el repositorio
Muy bueno 👏👏👏
@juanvc123 Si tiene buen estilo 😄 ademas resolviste todo lo que propuso el profe, ya tenias alguna experiencia antes con otros frameworks? que te ha parecido Vue.js?
Les comparto el proyecto realizado:
DEMO:
Codigo en GitHub:
Hola @cmarsiglia, me gusta mucho lo que hiciste en este proyecto, que tal la experiencia? has aprendido mucho? que dudas tienes?, ya viste las soluciones de tus compañeros?, seria buenísimo que les dejes un aporte, quizá alguna mejora que harías en su código o solo diles lo bueno que esta su proyecto.
Gracias @yesikita, excelente la experiencia, la verdad he ampliado mis conocimientos con cada una de las clases vistas hasta el momento, cada concepto aprendido lo aplico a mi proyecto personal.
Aqui tengo el mio, le agregue la función para actualizar el nombre 😃
👏👏👏
Excelente Cristofer!
Les comparto mis solución ya integrada con Bulma.
Muy bueno 👏
Hola!! Te quedó muy bien 😊, solo que tu sitio siempre me llama Alberto Fragoso 😦, crees que podrías hacer que ese nombre pueda cambiar poniendo un campo de texto para ingresarlo?. También recuerda ver las soluciones de tus compañeros y dejarles feedback así pueden mejorar sus proyectos y todos aprendemos mas!
Mi versión
Código
Buena practica!
Hola a todos, realicé el ejercicio con unas cuantas funciones extras de crud.
-->
Screenshot:
TaskManager.vue
/* eslint-disable vue/html-self-closing */<template lang="pug"> #app
.container.section.p-0 div.mb-6<div :class="messageClass" v-if="message"><button class="delete" aria-label="delete" @click="message=false"></button>|{{messageText}}</div> p.title.is-large.is-info.mt-6.mb-0|TaskManager p.has-text-right
button.button.is-small.is-primary(@click="modalClass='modal is-active'; clear()")|<i class="fa fa-plus" aria-hidden="true"></i><strong>New task</strong> button.button.is-small.is-danger.ml-4(@click="deleteSelected")|<i class="fa fa-trash" aria-hidden="true"></i><strong class="ml-2">Delete selected tasks</strong> table.table.is-bordered.is-hoverable.mt-2.is-fullwidth(v-show="tasks.length > 0") thead
tr
th
input(type="checkbox" v-model="selectedAll") th Name th Description th Time th Ops.tbodytr(v-for="(task, index) in tasks") td
input(type="checkbox" v-model="selected":value="index") td {{task.name}} td {{task.description}} td {{task.time}} hrs
td
a.button.is-danger.is-small.mr-2(@click="deleteTask(index)")<i class="fa fa-times" aria-hidden="true"></i> a.button.is-info.is-small(@click="editTask(task, index)")<i class="fa fa-edit" aria-hidden="true"></i> tfoot
tr
td.has-text-right(colspan="3") strong TotalTime td.has-text-left(colspan="2")|{{getTotalTime}} hrs
div(v-if="!tasks.length") p.message.is-info.p-4.mt-4 strong No hay tareas aún
div
<div :class="modalClass"><div class="modal-background"></div><div class="modal-content"> div.my-4<div :class="messageClass" v-if="messageInDialog"><button class="delete" aria-label="delete" @click="messageInDialog=false"></button>|{{messageText}}</div>.card.p-5.card-header.title.has-text-right
span(v-if="editTaskIndex == -1")Create a newtaskspan(v-else)Edit task
.card-content
.content.field input.input(type="text" v-model="aTask.name" placeholder="Task name").field textarea.textarea(v-model="aTask.description" placeholder="Task Description").field.has-text-left
input.input(type="number" v-model="aTask.time" placeholder="Time" style="width:120px").field(v-if="editTaskIndex == -1") a.button.is-primary.is-medium(@click="addTask")Save task
a.button.is-secondary.is-medium.ml-3(@click="modalClass='modal'")Close.field(v-else) a.button.is-primary.is-medium(@click="updateTask")Update task
</div><button class="modal-close is-large" @click="modalClass='modal'" aria-label="close"></button></div></template><script>exportdefault{name:"Tasks",data(){return{selected:[],editTaskIndex:-1,messageText:"The task was successfully saved.",messageClass:"notification is-success",message:false,messageInDialog:false,aTask:{name:"",time:0,description:""},totalTime:0,modalClass:"modal",tasks:[]};},computed:{getTotalTime(key ="time"){returnthis.tasks.reduce((a, b)=>+a ++b.time,0);},selectedAll:{set(val){this.selected=[];if(val){for(let i =0; i <this.tasks.length; i++){this.selected.push(i);}}},get(){returnthis.selected.length===this.tasks.length;}}},watch:{},created(){this.tasks=JSON.parse(localStorage.getItem("tasks"))||[];},mounted(){console.log("App Mounted");},methods:{deleteSelected(){const sel =this.selected.length;if(sel <=0){this.showMessage("notification is-warning",true,"You haven't selected any task to be deleted");return;}if(sel >0){for(let i =0; i <=(sel); i++){// Delete selected itemsthis.tasks.splice(i, sel);}localStorage.setItem("tasks",JSON.stringify(this.tasks));this.showMessage("notification is-info",true, sel +" task(s) deleted successfully");this.selected=[];}},updateTask(){if(this.aTask.name.length>0&&!isNaN(this.aTask.time)){this.tasks[this.editTaskIndex]={name:this.aTask.name,time:this.aTask.time,description:this.aTask.description};localStorage.setItem("tasks",JSON.stringify(this.tasks));this.showMessage("notification is-success",true,"The task '"+this.tasks[this.editTaskIndex].name+"' was edited");this.editTaskIndex=-1;this.modalClass="modal";setTimeout(()=>{this.message=false;},3000);}else{this.showMessage("notification is-danger",true,"Please review you information",true);}},editTask(task, index){this.modalClass="modal is-active";this.aTask= task;this.editTaskIndex= index;},showMessage(messageClass ="notification is-info", show =true, messageText, inDialog =false){this.messageText= messageText;this.messageClass= messageClass;if(inDialog){this.messageInDialog=true;}else{this.message= show;}setTimeout(()=>{!inDialog ?this.message=false:this.messageInDialog=false;},3000);},deleteTask(index){const taskName =this.tasks[index].name;this.tasks.splice(index,1);localStorage.setItem("tasks",JSON.stringify(this.tasks));this.showMessage("notification is-info",true,`The task ${taskName} was deleted`);},clear(){this.aTask.name="";this.aTask.time=0;this.aTask.description="";this.editTaskIndex=-1;},addTask(){if(this.aTask.name.length>0&&!isNaN(this.aTask.time)){if(this.aTask.time>0){const newTask ={name:this.aTask.name,time:this.aTask.time,description:this.aTask.description};this.tasks.push(newTask);localStorage.setItem("tasks",JSON.stringify(this.tasks));this.modalClass="modal";this.showMessage("notification is-success",true,"The task was successfully saved");this.clear();}else{this.showMessage("notification is-danger",true,"Data for time must be a positive number",true);}}else{this.showMessage("notification is-danger",true,"Please review you information",true);// this.modalClass = "modal";setTimeout(()=>{this.message=false;},3000);}}}};</script>
main.scss
@import'../../node_modules/bulma/bulma.sass';
Reutilizando el ejercicio del Curso básico de Vue y agregando las funcionalidades de este ejercicio:
CodePen Link
Reto Completado!!
Código:
Dejo mi código:
<template><divid="app"><divclass="tareas"><inputtype="text"name=""id="taskTitle"placeholder="Tarea"><inputtype="number"name=""id="taskTime"placeholder="Horas"min="1"step="1"><button@click="addTask">Agregar tarea</button></div><sectionv-if="tasks.length > 0"><divclass="listaTareas"><ul><liv-for="task, index in tasks":key="index"> {{ task.title }} -> {{ task.time }} horas
<button:key="index"@click="deleteTask"> Borrar tarea {{ task.title }}
</button></li></ul></div><divclass="tiempo"><p>Tiempo total: {{ totalTime }} horas</p></div></section><sectionv-else><h2>No hay tareas por el momento</h2></section></div></template><script>exportdefault{data(){return{tasks:[],newTask:{title:'',time:0}};},methods:{addTask(){const title =document.getElementById('taskTitle').value;const time =parseInt(document.getElementById('taskTime').value);if(title !==''&& time !==''){this.newTask.title= title;this.newTask.time= time;this.tasks.push(this.newTask);this.newTask={title:'',time:0};}else{alert('La tarea y el tiempo deben tener un valor');}localStorage.setItem('tasks',JSON.stringify(this.tasks));},deleteTask(task){const index =this.tasks.indexOf(task);this.tasks.splice(index,1);localStorage.setItem('tasks',JSON.stringify(this.tasks));}},computed:{totalTime(){let total =0;this.tasks.forEach(task=>{ total +=parseInt(task.time);});return total;}},created(){const tasks =localStorage.getItem('tasks');if(tasks){this.tasks=JSON.parse(tasks)||[];}}};</script><stylelang="scss"></style>
Comparto mi resolucion:
Muy bueno Ivan, me gusto mucho el uso de reduce para resolver la computed. Te recomiendo usar const en aquellas variables que sabes que no mutaran su valor. Esta excelente!
Gracias Ignacio, buen consejo! Excelente curso hasta ahora. Muy buena la idea de ir resolviendo desafios al final de cada leccion, que ponga en practica lo aprendido, y sobre todo que no sea algo muy basico, sino que requiera un poco de esfuerzo el resolverlo.
Excelente ejercicio para la manipulación del DOM, Sirve mucho para adquirir destreza en VUE.
GitHub Pages:
Repositorio:
Justo esa es la intención Fersot, que bueno que te hayan gustado =)
Este es mi prototipo 100% funcional ademas le añadí un plus con las actividades completada y una barra de progreso para saber como vamos.
Por cierto aquí esta el repositorio. Click aquí
HTML
<template lang="pug"> #app
.columns.column.column h1 Tareas y horas
br
input.input.is-8(type="text" v-model="title" name="title" placeholder="title") input.input.is-8(type="text" v-model="time" name="time" placeholder="time") input.button.link(type="submit" name="submit" @click="addTask(title,time)")p(v-if="totalTime!==0"){{totalTime}}div(v-for="(task,i) in tasks") ul
li {{task.title}}-{{task.time}} button.delete.is-medium(@click="removeTask(i)") br
.column</template>
JS
<script>exportdefault{name:'app',data(){return{title:'',time:0,tasks:[]}},computed:{totalTime(){let total =0this.tasks.map(function(sum){ total += sum.time})return total
}},created(){this.tasks=JSON.parse(localStorage.getItem('tasks'))||[]},methods:{addTask(title, time){if(title, time){const newTask ={ title,time:parseInt(time)}this.tasks.push(newTask);}else(alert('debe llenar todos los campos'))localStorage.setItem('tasks',JSON.stringify(this.tasks))this.cancel()},cancel(){this.title='',this.time=''},removeTask(i){let remove =this.tasks.splice(i,1);localStorage.setItem('tasks',JSON.stringify(this.tasks))return remove
}}}</script>
Mi frontend no es muy bueno, pero aquí va, sigo usando bulma, no me gustó que no tiene clases como tan practicas en comparación de bootstrap para listas y hay que desarrollar mas del lado del css, pero bueno, la funcionalidad está.
<template lang="pug"> #app
section.section nav.nav.has-shadow
.container.field label.labelTítulo de la tarea
.control input.input.is-large(type="text", placeholder="Título de la tarea" v-model="title").field label.labelTiempo de trabajo
.control input.input.is-large(type="text", placeholder="Tiempo trabajado" v-model="time").field.is-grouped.is-grouped-right
.control a.button.is-info.is-large(@click="addTask")AgregarTarea a.button.is-danger.is-large(@click="deleteTask")×EliminarLista completa
br
br
h2 {{ timeWork }} p
small {{ numberTask }}.container.ul.li(v-for="(t, index) in tasks"){{ t.title}}-{{ t.time}} a.button.is-danger(@click="deleteThisTask(index)")× p.error small {{ mensajeError }}</template><script>const tasks =[]exportdefault{name:'app',created(){this.tasks=JSON.parse(localStorage.getItem(tasks))||[]},data(){return{title:'',time:'',tasks:[],mensajeError:'',indexList:''}},computed:{numberTask(){return`Tareas Pendientes: ${this.tasks.length}`},timeWork(){let horasTrabajadas =0for(let item ofthis.tasks){ horasTrabajadas +=Number(item.time)}return`Hasta el momento hemos trabajado ${horasTrabajadas} hrs.`}},methods:{addTask(){if(this.title===''||this.time===''){this.mensajeError='Debe indicar los dos elementos título y horario'}else{if(!/^([0-9])*$/.test(this.time)){this.mensajeError='El tiempo debe ser un número'}else{this.tasks.push({title:this.title,time:this.time})this.title=''this.time=''this.mensajeError=''localStorage.setItem(tasks,JSON.stringify(this.tasks))}}},deleteTask(){this.tasks=[]localStorage.setItem(tasks,JSON.stringify(this.tasks))},deleteThisTask(index){this.tasks.splice(index,1)localStorage.setItem(tasks,JSON.stringify(this.tasks))}}}</script><style lang="scss"> @import'./scss/main.scss';.results{ margin-top: 15px;}.error{color: red; font-weight: bolder;}.button{ margin-right: 10px;}</style>
Repo :
DEMO :
Tarde pero seguro…
Originalmente lo hice con html porque no me gusta pug pero lo converti a pug para ustedes
.conteiner nav.nav.justify-content-center.p-3.mb-2.bg-info.text-white
a.nav-link.text-dark(href='#')Home.conteiner|{{ name }}.conteiner.col-12.d-flex.justify-content-center
.form-group
label(for='')Name input.form-control(type='text' placeholder='Add Name' v-model='title') small.form-text.text-muted Add the newTitleName.d-flex.justify-content-center
.form-group
label(for='')Time input.form-control(type='text' placeholder='Add time' v-model='time') small.form-text.text-muted Add the newtimeName button.btn.btn-primary(type='button' v-on:click='addTask()' data-toggle='modal' data-target='#exampleModal')|Add button.btn.btn-danger(type='button' v-on:click='restartTask()' data-toggle='modal' data-target='#exampleModal')| cancel
table.table thead
tr
th Title th Time tbody
tr(v-for='(item, index) in task':key='item')td(scope='row'){{ item.title}}td(scope='row'){{ item.time}}td(scope='row') button.btn.btn-danger(type='button' v-on:click='remove(index)' data-toggle='modal' data-target='#exampleModal')|× p.text-success.totalTime{{ totalTime }} #exampleModal.modal.fade(v-if='showMessage' tabindex='-1' role='dialog' aria-labelledby='exampleModalLabel' aria-hidden='true').modal-dialog(role='document').modal-content
.modal-header
h5.modal-title Modal title
button.close(type='button' data-dismiss='modal' aria-label='Close')span(aria-hidden='true')×.modal-body
p {{ message }}```
<template><v-container><h2>Administrador de Tareas</h2><h4>{{ name }}</h4><v-form style="max-width: 30vw"class="mb-4"><v-container><v-row><v-col lg="12"><v-text-field label="Titulo" required v-model="newTask.title"></v-text-field></v-col><v-col lg="12"><v-text-field
type="number" value="0" required
v-model="newTask.time"></v-text-field></v-col><v-btn color="info"class="mr-4" @click="addTask">Guardar</v-btn><v-btn color="error" @click="clearTask">Cancelar</v-btn></v-row></v-container></v-form><h4>Lista de Tareas</h4><p v-if="tasks.length === 0">Aun no hay tareas cargadas.</p><ol v-else><li v-for="(task, index) in tasks":key="index">{{ task.title}}-{{ task.time}}<span @click="removeTask(index)">❌</span></li><p class="mt-4">TiempoTrabajado:<span>{{ totalTime }}</span></p></ol></v-container></template><script>exportdefault{name:"TaskList",data(){return{name:"José Jorge",tasks:[],newTask:{title:"",time:0,},};},created(){this.tasks=JSON.parse(localStorage.getItem("tasks"))||[];},computed:{totalTime(){let sum =0;this.tasks.forEach((item)=>{ sum +=parseInt(item.time);});return sum;},},methods:{clearTask(){this.newTask.title="";this.newTask.time=0;},addTask(){if(this.newTask.title!==""&&this.newTask.time!==0){this.tasks.push({title:this.newTask.title,time:this.newTask.time});localStorage.setItem("tasks",JSON.stringify(this.tasks));this.clearTask();}},removeTask(indice){this.tasks.splice(indice);localStorage.setItem("tasks",JSON.stringify(this.tasks));},},};</script>```\<template> \<v-container> \<h2>Administrador de Tareas\</h2> \<h4>{{ name }}\</h4> \<v-form style="max-width: 30vw"class="mb-4"> \<v-container> \<v-row> \<v-col lg="12"> \<v-text-field label="Titulo" required v-model="newTask.title"> \</v-text-field> \</v-col> \<v-col lg="12"> \<v-text-field type="number" value="0" required v-model="newTask.time"> \</v-text-field> \</v-col> \<v-btn color="info"class="mr-4" @click="addTask">Guardar \</v-btn> \<v-btn color="error" @click="clearTask">Cancelar \</v-btn> \</v-row> \</v-container> \</v-form> \<h4>Lista de Tareas\</h4> \<p v-if="tasks.length === 0">Aun no hay tareas cargadas.\</p> \<ol v-else> \<li v-for="(task, index) in tasks":key="index">{{ task.title}}-{{ task.time}} \<span @click="removeTask(index)">❌\</span> \</li> \<p class="mt-4">TiempoTrabajado: \<span>{{ totalTime }}\</span> \</p> \</ol> \</v-container>\</template>\<script>exportdefault{ name:"TaskList",data(){return{ name:"José Jorge", tasks: \[], newTask:{ title:"", time:0,},};},created(){this.tasks=JSON.parse(localStorage.getItem("tasks"))|| \[];}, computed:{totalTime(){let sum =0;this.tasks.forEach((item)=>{ sum +=parseInt(item.time);});return sum;},}, methods:{clearTask(){this.newTask.title="";this.newTask.time=0;},addTask(){if(this.newTask.title!==""&&this.newTask.time!==0){this.tasks.push({title:this.newTask.title,time:this.newTask.time});localStorage.setItem("tasks",JSON.stringify(this.tasks));this.clearTask();}},removeTask(indice){this.tasks.splice(indice);localStorage.setItem("tasks",JSON.stringify(this.tasks));},},};\</script>