1
\$\begingroup\$

I am working on a small to-do app with Vue (v 2.x.x). I list 5 todos from jsonplaceholder initially.

I can add more todos, delete them or mark them as done.

var app = new Vue({
 el: "#toDoApp",
 data: {
 url: "https://jsonplaceholder.typicode.com/todos?&_limit=5",
 dataIsLoaded: false,
 isValidInput: true,
 todos: [],
 unsolvedTodos: [],
 newTitle: "",
 },
 mounted() {
 axios.get(this.url)
 .then((response) => {
 this.todos = response.data;
 })
 .then(this.getUnsolvedTodos);
 this.dataIsLoaded = true;
 },
 methods: {
 getUnsolvedTodos: function() {
 this.unsolvedTodos = this.todos.filter(todo => {
 return todo.completed === false;
 });
 },
 // toggle todo
 toggleTodo: function(todo) {
 todo.completed = !todo.completed;
 // Update unsolved count
 this.getUnsolvedTodos();
 },
 // delete todo
 deleteTodo: function(id) {
 this.todos = this.todos.filter(todo => todo.id !== id);
 // Update unsolved count
 this.getUnsolvedTodos();
 },
 // validate todo title
 validateInput: function() {
 this.isValidInput = this.newTitle.length >= 3 ? true : false;
 },
 // add todo
 addTodo: function() {
 let lastId = this.todos.length === 0 ? 0 : this.todos[this.todos.length - 1].id;
 const newToDo = {
 id: lastId + 1,
 title: this.newTitle,
 completed: false
 }
 this.validateInput();
 if (this.isValidInput) {
 this.todos.push(newToDo);
 this.newTitle = "";
 }
 // Update unsolved count
 this.getUnsolvedTodos();
 }
 },
 created() {},
 watch: {}
});
@import url("https://fonts.googleapis.com/css2?family=Poppins:wght@200;300;400;500;600;700&display=swap");
* {
 margin: 0;
 padding: 0;
 box-sizing: border-box;
 font-family: "Poppins", sans-serif;
}
.app-wrapper {
 height: 100vh;
 width: 100%;
 display: flex;
 align-items: center;
 justify-content: center;
 background-color: #0093e9;
 background-image: linear-gradient(160deg, #0093e9 0%, #80d0c7 100%);
}
#toDoApp {
 width: 320px;
 height: 320px;
 display: flex;
 flex-direction: column;
 background: #fff;
 box-shadow: rgb(99 99 99 / 20%) 0px 2px 8px 0px;
 overflow: hidden;
 border-radius: 8px;
 position: relative;
}
header {
 padding: 15px 20px;
 background: #efefef;
 border-bottom: 1px solid rgba(0, 0, 0, 0.1);
 font-size: 18px;
 font-weight: 600;
 display: flex;
}
header .title {
 font-size: 18px;
 font-weight: 600;
 color: #323232;
}
header .count {
 margin-left: auto;
 display: inline-block;
 width: 26px;
 line-height: 26px;
 text-align: center;
 border-radius: 3px;
 font-size: 13px;
 background: #c00;
 color: #fff;
}
header .count.zero {
 background: #009688;
}
.todo-list {
 flex: 1;
 overflow-y: auto;
 list-style-type: none;
 padding: 15px 20px 5px 20px;
 color: #4f4f4f;
}
::-webkit-scrollbar {
 width: 5px;
}
/* Track */
::-webkit-scrollbar-track {
 background: #efefef;
}
/* Handle */
::-webkit-scrollbar-thumb {
 background: #d0d0d0;
}
/* Handle on hover */
::-webkit-scrollbar-thumb:hover {
 background: #d0d0d0;
}
.todo-list li {
 position: relative;
 display: flex;
 overflow: hidden;
 align-items: center;
 font-size: 13px;
 padding: 10px;
 cursor: pointer;
 margin-bottom: 7px;
 border: 1px solid rgba(0, 0, 0, 0.07);
 background: #d4e7f7;
 border-radius: 2px;
}
.todo-list li:last-child {
 margin-bottom: 0;
}
.done {
 text-decoration: line-through;
}
input[type="checkbox"] {
 display: block;
 margin-bottom: -1px;
 margin-right: 7px;
}
button {
 border: none;
 background: #c00;
 color: #fff;
 cursor: pointer;
 position: absolute;
 z-index: 1;
 top: -1px;
 bottom: -1px;
 right: -1px;
 font-size: 18px;
 width: 50px;
 transform: translateX(50px);
 transition: transform 200ms;
}
.todo-list li:hover button {
 transform: translateX(0);
}
footer {
 padding: 15px 20px 25px 20px;
 margin-top: auto;
 position: relative;
}
.error {
 position: absolute;
 top: 5px;
 right: 22px;
 font-size: 11px;
 color: #c00;
}
input[type="text"] {
 width: 100%;
 padding: 2px 4px;
 border: none;
 font-size: 16px;
 outline: none;
 border-bottom: 2px solid #b8b8b8;
 transition: border-bottom 200ms ease-in;
}
input[type="text"]:focus {
 border-bottom: 2px solid #dddddd;
}
.loader {
 border: 4px solid #ccc;
 border-top-color: transparent;
 border-radius: 50%;
 width: 50px;
 height: 50px;
 position: absolute;
 top: 50%;
 margin-left: -25px;
 left: 50%;
 margin-top: -25px;
 animation: spin 2s linear infinite;
}
@keyframes spin {
 0% {
 transform: rotate(0deg);
 }
 100% {
 transform: rotate(360deg);
 }
}
<link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.21.1/axios.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.min.js"></script>
<div class="app-wrapper">
 <div id="toDoApp">
 <header>
 <span class="title">My todo list</span>
 <span class="count" v-bind:class="{zero: unsolvedTodos.length === 0}">{{unsolvedTodos.length}}</span>
 </header>
 <ul class="todo-list" v-if="dataIsLoaded">
 <li v-for="todo in todos.slice().reverse()" v-bind:class="{done: todo.completed}" :id="todo.id">
 <input type="checkbox" :checked="todo.completed" @change="toggleTodo(todo)" />
 <span class="title">{{todo.title}}</span>
 <button @click="deleteTodo(todo.id)">
 <i class="fa fa-trash" aria-hidden="true"></i>
 </button>
 </li>
 </ul>
 <div class="loader" v-else></div>
 <footer>
 <form @submit.prevent="addTodo()">
 <input type="text" placeholder="Add new todo..." v-model="newTitle">
 <span class="error" v-if="!isValidInput">Please add at least 3 characters</span>
 </form>
 </footer>
 </div>
</div>

Concerns:

  1. Is the app "crowded" with code? In other words, could it have been written with specifically fewer lines of code?

  2. Is the architecture easy to comprehend?

  3. Is there a better alternative to Axios?

asked May 18, 2021 at 13:52
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

You are doing things in a very procedural way, like how you change TODO-items and then calculate the number of unsolved TODOs. When using Vue, there is a better way to do it, namely using computed properties

methods: {
 toggleTodo: function(todo) {
 todo.completed = !todo.completed;
 },
 deleteTodo: function(id) {
 this.todos = this.todos.filter(todo => todo.id !== id);
 },
},
computed: {
 unsolvedTodos: function() {
 return this.todos.filter(todo => !todo.completed);
 },
 validInput: function() {
 return this.newTitle.length >= 3;
 }
},

Is the architecture easy to comprehend?

Yes, but it's not optimal because of what I mentioned above.

Is there a better alternative to Axios?

No, not in my opinion. Stick with Axios.

answered May 18, 2021 at 17:47
\$\endgroup\$
0

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.