Skip to content

Instantly share code, notes, and snippets.

@delenamalan
Created January 25, 2019 16:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save delenamalan/4ed924c8f23127b574d764c429fdd51c to your computer and use it in GitHub Desktop.
Save delenamalan/4ed924c8f23127b574d764c429fdd51c to your computer and use it in GitHub Desktop.
Vue.js 2.0 Todo List βœ… with Local Storage πŸ“¦
<div class="app" id="app">
<form class="form" v-on:submit="addTodo">
<input class="input form__input" v-model="inputVal"/>
<button class="btn form__submit-btn" type="submit">Add</button>
</form>
<transition-group tag="ol" name="list" class="todo-list">
<li
class="todo-list__item"
v-bind:class="{ complete: todo.complete }"
v-bind:key="index"
v-for="(todo, index) in filteredTodos">
<button
class="todo-list__item-content"
v-on:click="toggleTodo(todo)">
{{ todo.text }}
</button>
<button
class="btn todo-list__item-remove"
v-on:click="deleteTodo(index)">
<i class="fa" v-bind:class="[todo.complete ? 'fa-check' : 'fa-times']"></i>
</button>
</li>
</transition-group>
<div class="filters">
<button
class="btn filters__btn filters__btn--all"
v-on:click="filterTodos('all')">
All
</button>
<button
class="btn filters__btn filters__btn--complete"
v-on:click="filterTodos('complete')">
Complete
</button>
<button
class="btn filters__btn filters__btn--incomplete"
v-on:click="filterTodos('incomplete')">
Incomplete
</button>
</div>
</div>
var filters = {
all: function(todos) {
return todos;
},
complete: function(todos) {
return todos.filter(function(todo) {
return todo.complete;
});
},
incomplete: function(todos) {
return todos.filter(function(todo) {
return !todo.complete;
});
}
}
var STORAGE_KEY = 'vue-js-todo-P7oZi9sL'
var todoStorage = {
fetch: function () {
var todos = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]');
return todos;
},
save: function (todos) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(todos));
}
}
var app = new Vue({
el: '#app',
data: {
inputVal: '',
todos: todoStorage.fetch(),
visibility: 'all'
},
watch: {
todos: {
handler: function(todos) {
todoStorage.save(todos);
}
}
},
computed: {
filteredTodos: function () {
return filters[this.visibility](this.todos);
}
},
methods: {
addTodo: function(e) {
e.preventDefault();
if (this.inputVal) {
this.todos.push({
text: this.inputVal,
complete: false
});
}
this.inputVal = '';
},
toggleTodo: function(todo) {
todo.complete = !todo.complete;
},
filterTodos: function(filter) {
this.visibility = filter;
},
deleteTodo: function(index) {
this.todos.splice(index, 1);
}
}
});
<script src="https://unpkg.com/vue@2.0.3/dist/vue.js"></script>
$color-main: #4fc08d;
$font-family: 'Source Sans Pro', sans-serif;
*, *:before, *:after {
box-sizing: border-box;
}
html {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-feature-settings: "liga", "kern";
overflow-y: scroll;
overflow-x: hidden;
height: 100%;
background: linear-gradient(210deg, lighten($color-main, 20%), $color-main);
}
body {
overflow: hidden;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-family: $font-family;
}
button {
background: none;
border: none;
color: inherit;
font-size: inherit;
font-family: inherit;
font-weight: inherit;
&:focus {
outline: none;
}
&:hover {
cursor: pointer;
}
}
.app {
min-height: 50vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
border-radius: 1em;
background: #fff;
overflow: hidden;
box-shadow: 0 0 5px rgba(25,25,25,.25);
}
.btn {
font-size: 14px;
margin: 0 .5em;
border-radius: 2em;
padding: 0.75em 1.5em;
cursor: pointer;
background: none;
color: darken($color-main, 20%);
border: 1px solid;
letter-spacing: 1px;
font-family: $font-family;
color: #4fc08d;
border: $color-main 1px solid;
transition: 250ms ease-out;
&:hover, &:focus {
color: #fff;
background: $color-main;
}
}
.form {
width: 100%;
padding: 1.5rem 1rem 0 1rem;
display: flex;
&__input {
width: 100%;
font-size: 14px;
margin: 0 .5em;
border-radius: 2em;
padding: 0.75em 1.5em;
background: none;
font-family: $font-family;
border: #e3e3e3 1px solid;
transition: border 250ms ease-out;
&:focus {
border: $color-main 1px solid;
outline: none;
}
}
&__input,
&__submit-btn {
}
}
.todo-list {
$block: #{&};
width: 100%;
padding: 0 1rem;
flex: 1;
&__item {
display: flex;
justify-content: space-between;
align-items: center;
padding: .5em;
margin-bottom: .5em;
border-radius: 3px;
transition: 200ms;
color: $color-main;
&:last-child { margin-bottom: 0; }
&.complete {
color: lightgreen;
#{$block}__item-content {
&:after {
background: lightgreen;
}
}
}
}
&__item-content {
position: relative;
&:after {
content: "";
position: absolute;
top: 100%;
left: 0;
right: 0;
height: 1px;
background: $color-main;
transition: 250ms ease-out;
transform-origin: center;
transform: scalex(0);
}
&:hover, &:focus {
&:after {
transform: scalex(1);
}
}
}
&__item-remove {
margin-left: .5em;
background: none;
border: 1px solid;
color: inherit;
padding: 0;
line-height: 1;
width: 2em;
height: 2em;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
font-size: 80%;
}
}
.filters {
width: 100%;
display: flex;
justify-content: space-around;
padding: 0 1rem 1.5rem 1rem;
}
.list-move,
.list-leave-active,
.list-enter-active, {
transition: 500ms cubic-bezier(.87,-.41,.19,1.44);
}
.list-enter,
.list-leave-active {
transform: translate(100%, 0);
opacity: 0;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.6.3/css/font-awesome.min.css" rel="stylesheet" />
<link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro" rel="stylesheet" />

Vue.js 2.0 Todo List βœ… with Local Storage πŸ“¦

Just another todo app, tinkering with Vue.js this time. Writes to local storage for persistent data.

A Pen by Delena Malan on CodePen.

License.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment