A simple todo list built with Vue.js 2.0 that saves your todo items in the LocalStorage.
You can visit it here to actually use it: https://nourabusoud.github.io/vue-todo-list/
<div id="app"> | |
<section class="todo-wrapper"> | |
<h1 class="todo-title">{{ today.day }}<br>{{ today.date }}</h1> | |
<form @keydown.enter.prevent=""> | |
<input type="text" class="input-todo" v-bind:class="{ active: new_todo }" placeholder="Take the garbage out" v-model="new_todo" v-on:keyup.enter="addItem"> | |
<div class="btn btn-add" v-bind:class="{ active: new_todo }" @click="addItem">+</div> | |
</form> | |
<div v-if="pending.length > 0"> | |
<p class="status busy">You have {{ pending.length }} pending item<span v-if="todoList.length>1">s</span></p> | |
<transition-group name="todo-item" tag="ul" class="todo-list"> | |
<li v-for="(item, index) in pending" v-bind:key="item.title"> | |
<input class="todo-checkbox" v-bind:id="'item_' + item.id" v-model="item.done" type="checkbox"> | |
<label v-bind:for="'item_' + item.id"></label> | |
<span class="todo-text">{{ item.title }}</span> | |
<span class="delete" @click="deleteItem(item)"></span> | |
</li> | |
</transition> | |
</div> | |
<transition name="slide-fade"> | |
<p class="status free" v-if="!pending.length" ><img src="https://nourabusoud.github.io/vue-todo-list/images/beer_celebration.svg" alt="celebration">Time to chill! You have no todos.</p> | |
</transition> | |
<div v-if="completed.length > 0 && showComplete"> | |
<p class="status">Completed tasks: {{ completedPercentage }}</p> | |
<transition-group name="todo-item" tag="ul" class="todo-list archived"> | |
<li v-for="(item, index) in completed" v-bind:key="item.title"> | |
<input class="todo-checkbox" v-bind:id="'item_' + item.id" v-model="item.done" type="checkbox"> | |
<label v-bind:for="'item_' + item.id"></label> | |
<span class="todo-text">{{ item.title }}</span> | |
<span class="delete" @click="deleteItem(item)"></span> | |
</li> | |
</transition> | |
</div> | |
<div class="control-buttons"> | |
<div class="btn btn-secondary" v-if="completed.length > 0" @click="toggleShowComplete"><span v-if="!showComplete">Show</span><span v-else>Hide</span> Complete</div> | |
<div class="btn btn-secondary" v-if="todoList.length > 0" @click="clearAll">Clear All</div> | |
</div> | |
</section> | |
</div> | |
<footer> | |
Made with <3 by <a href="https://www.nourabusoud.com/">Nour Soud</a><br> | |
View on <a href="https://github.com/nourabusoud/vue-todo-list">Github</a> | |
</footer> |
new Vue({ | |
el: '#app', | |
data() { | |
return { | |
todoList: [ | |
{"id":0,"title":"Go to codepen and get inspired","done":false}, | |
{"id":1,"title":"Pick a project","done":false}, | |
{"id":4,"title":"Create a new pen","done":true} | |
], | |
new_todo: '', | |
showComplete: false, | |
}; | |
}, | |
computed: {}, | |
mounted() { | |
this.getTodos(); | |
}, | |
watch: { | |
todoList: { | |
handler: function(updatedList) { | |
localStorage.setItem('todo_list', JSON.stringify(updatedList)); | |
}, | |
deep: true | |
} | |
}, | |
computed:{ | |
pending: function() { | |
return this.todoList.filter(function(item) { | |
return !item.done; | |
}) | |
}, | |
completed: function() { | |
return this.todoList.filter(function(item) { | |
return item.done; | |
}); | |
}, | |
completedPercentage: function() { | |
return (Math.floor((this.completed.length / this.todoList.length) * 100)) + "%"; | |
}, | |
today: function() { | |
var weekday = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"]; | |
var today = new Date(); | |
var dd = today.getDate(); | |
var mm = today.getMonth()+1; //January is 0! | |
var yyyy = today.getFullYear(); | |
if(dd<10) { | |
dd = '0'+dd | |
} | |
if(mm<10) { | |
mm = '0'+mm | |
} | |
today = { | |
day: weekday[today.getDay()], | |
date: mm + '-' + dd + '-' + yyyy, | |
} | |
return(today); | |
} | |
}, | |
methods: { | |
// get all todos when loading the page | |
getTodos() { | |
if (localStorage.getItem('todo_list')) { | |
this.todoList = JSON.parse(localStorage.getItem('todo_list')); | |
} | |
}, | |
// add a new item | |
addItem() { | |
// validation check | |
if (this.new_todo) { | |
this.todoList.unshift({ | |
id: this.todoList.length, | |
title: this.new_todo, | |
done: false, | |
}); | |
} | |
// reset new_todo | |
this.new_todo = ''; | |
// save the new item in localstorage | |
return true; | |
}, | |
deleteItem(item) { | |
this.todoList.splice(this.todoList.indexOf(item), 1); | |
}, | |
toggleShowComplete() { | |
this.showComplete = !this.showComplete; | |
}, | |
clearAll() { | |
this.todoList = []; | |
} | |
}, | |
}); |
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.3/vue.min.js"></script> |
*{ | |
box-sizing: border-box; | |
} | |
body{ | |
font-size: 15px; | |
font-family: 'Open Sans', sans-serif; | |
color: #444; | |
background-color: #fefefe; | |
background-image: linear-gradient(#fc6c48 0%, #ef5081 100%); | |
background-repeat: no-repeat; | |
background-size: cover; | |
padding: 50px 20px; | |
margin: 0; | |
min-height: 100vh; | |
position: relative; | |
} | |
.todo-wrapper{ | |
width: 400px; | |
max-width: 100%; | |
min-height: 500px; | |
margin: 20px auto 40px; | |
border: 1px solid #eee; | |
border-radius: 4px; | |
padding: 40px 20px; | |
-webkit-box-shadow: 0 0 15px 0 rgba(0,0,0,0.05); | |
box-shadow: 0 0 15px 0 rgba(0,0,0,0.05); | |
background-color: #f4f7fc; | |
overflow: hidden; | |
position: relative; | |
} | |
.todo-title{ | |
font-size: 1.2em; | |
color: #f65c65; | |
font-weight: normal; | |
} | |
form{ | |
overflow: overlay; | |
} | |
form label{ | |
display: block; | |
text-align: center; | |
font-size: 1.2em; | |
} | |
.btn, input { | |
line-height: 2em; | |
border-radius: 3px; | |
border: 0; | |
display: inline-block; | |
margin: 15px 0; | |
padding: 0.2em 1em; | |
font-size: 1em; | |
} | |
input[type='text'] { | |
border: 1px solid #ddd; | |
min-width: 80%; | |
transition: all ease-in 0.25s; | |
} | |
input:focus{ | |
outline: none; | |
border: 1px solid #a3b1ff; | |
} | |
input::placeholder{ | |
color: rgba(0,0,0,0.3); | |
font-style: italic; | |
} | |
.btn{ | |
text-align: center; | |
font-weight: bold; | |
cursor: pointer; | |
border-width: 1px; | |
border-style: solid; | |
} | |
.btn-add { | |
background: #ddd; | |
color: #fefefe; | |
border-color: #ddd; | |
min-width: 17%; | |
pointer-events: none; | |
transition: all ease-in 0.25s; | |
font-size: 2.2em; | |
line-height: 0.5em; | |
padding: 0.3em 0.3em; | |
float: right; | |
} | |
.btn-add.active{ | |
background: #6664ff; | |
border-color: #6664ff; | |
pointer-events: visible; | |
} | |
.btn-add.active:hover{ | |
background: #4442f6; | |
border-color: #4442f6; | |
} | |
.btn-add:active{ | |
transform: scale(0.95); | |
} | |
.control-buttons{ | |
position: absolute; | |
bottom: 20px; | |
left: 50%; | |
transform: translateX(-50%); | |
width: 100%; | |
text-align: center; | |
} | |
.btn-secondary{ | |
display: inline-block; | |
position: relative; | |
border: 0; | |
padding: 0; | |
margin: 0 10px; | |
} | |
.btn-secondary:after{ | |
position: absolute; | |
content: ''; | |
width: 0; | |
height: 3px; | |
background-color: #f4586e; | |
bottom: 0px; | |
left: 0; | |
transition: all ease-in 0.25s; | |
} | |
.btn-secondary:hover:after{ | |
width: 100%; | |
} | |
ul.todo-list{ | |
padding: 0; | |
margin-bottom: 30px; | |
} | |
ul.todo-list li{ | |
position: relative; | |
list-style-type: none; | |
display: block; | |
margin: 10px 0; | |
background: #e0e8f5; | |
border-radius: 3px; | |
padding-left: 38px; /* custom checkbox width + 16 */ | |
padding-top: 12px; | |
padding-bottom: 12px; | |
padding-right: 49px; /* delete button + 5 */ | |
overflow: hidden; | |
} | |
ul.todo-list.archived li{ | |
background: #fff; | |
} | |
.todo-text{ | |
position: relative; | |
display: inline-block; | |
padding: 0 0.5em; | |
} | |
ul.todo-list li .delete{ | |
position: absolute; | |
height: 100%; | |
top: 50%; | |
right: 0; | |
transform: translateY(-50%); | |
cursor: pointer; | |
opacity: 0; | |
width: 0; | |
background-color: #f56468; | |
color: #fff; | |
transition: all ease-in 0.25s; | |
} | |
ul.todo-list li .delete:after{ | |
position: absolute; | |
content: ''; | |
width: 16px; | |
height: 16px; | |
top: 50%; | |
left: 50%; | |
background-image: url('https://nourabusoud.github.io/vue-todo-list/images/trash.svg'); | |
background-repeat: no-repeat; | |
background-position: center; | |
background-size: contain; | |
transform: translate(-50%, -50%) scale(0.5); | |
transition: all ease-in 0.25s; | |
} | |
ul.todo-list li:hover .delete{ | |
width: 44px; | |
opacity: 1; | |
} | |
ul.todo-list li:hover .delete:after{ | |
transform: translate(-50%, -50%) scale(1); | |
} | |
.todo-checkbox{ | |
position: absolute; | |
opacity: 0; | |
display: none; | |
} | |
.todo-checkbox + label { | |
position: absolute; | |
cursor: pointer; | |
left: 10px; | |
top: 50%; | |
transform: translateY(-50%); | |
width: 22px; | |
height: 22px; | |
border-radius: 2px; | |
border: 1px solid #cfdcec; | |
background-color: #fff; | |
} | |
.todo-checkbox:checked + label:after{ | |
position: absolute; | |
content: ''; | |
top: 30%; | |
left: 50%; | |
height: 3px; | |
width: 6px; | |
border: solid #fc6c48; | |
border-width: 0 0 2px 2px; | |
transform-origin: center center; | |
transform: rotate(-45deg) translate(-50%, -50%); | |
} | |
.todo-checkbox:checked + label:after{ | |
display: block; | |
} | |
.todo-checkbox:checked ~ .todo-text{ | |
color: #888; | |
text-decoration: line-through | |
} | |
.status.free{ | |
font-weight: bold; | |
text-align: center; | |
margin: 40px 0; | |
} | |
.status.free img{ | |
display: block; | |
width: 50px; | |
margin: 10px auto; | |
vertical-align: middle; | |
} | |
.todo-item-enter-active, .todo-item-leave-active { | |
transition: opacity ease 0.25s, transform ease-in-out 0.3s; | |
transform-origin: left center; | |
} | |
/* .todo-item-leave-active below version 2.1.8 */ | |
.todo-item-enter, .todo-item-leave-to { | |
opacity: 0; | |
transform: translateX(100%); | |
} | |
.slide-fade-enter-active, .slide-fade-leave-active { | |
transition: all 0.3s ease; | |
} | |
/* .slide-fade-leave-active below version 2.1.8 */ | |
.slide-fade-enter, .slide-fade-leave-to { | |
transform: scale(1.1); | |
opacity: 0; | |
} | |
/* Footer */ | |
footer{ | |
position: absolute; | |
width: 100%; | |
text-align: center; | |
color: #fff; | |
bottom: 20px; | |
left: 0; | |
} | |
footer a{ | |
color: #fff; | |
} |
A simple todo list built with Vue.js 2.0 that saves your todo items in the LocalStorage.
You can visit it here to actually use it: https://nourabusoud.github.io/vue-todo-list/