This guide will walk you through building a basic To-Do List application using Vue.js 3. By the end of this tutorial, you'll understand how to handle data binding, event handling, and dynamic rendering in Vue.js.
Prerequisites
Basic knowledge of HTML, CSS, and JavaScript.
Node.js and npm installed on your computer.
Vue CLI installed. If not, you can install it by running npm install -g @vue/cli.
Step 1: Setting Up the Project
Create a New Vue.js Project: Open your terminal and run the following command to create a new Vue.js 3 project:
vue create todo-app
Navigate into the project directory:
cd todo-app
Run the Development Server: Start the Vue development server:
npm run serve
This will open the default Vue.js starter template in your browser at http://localhost:8080.
Step 2: Structuring the Application
Clean Up the Default Template: Open src/App.vue and remove the default template, script, and style contents. Replace them with the following code:
<template>
<div id="app">
<h1>Vue.js To-Do List</h1>
<input v-model="newTask" placeholder="Add a new task" @keyup.enter="addTask" />
<button @click="addTask">Add Task</button>
<ul>
<li v-for="(task, index) in tasks" :key="index">
<input type="checkbox" v-model="task.completed" />
<span :class="{ 'completed-task': task.completed }">{{ task.text }}</span>
<button @click="removeTask(index)">Delete</button>
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
newTask: '',
tasks: []
};
},
methods: {
addTask() {
if (this.newTask.trim() !== '') {
this.tasks.push({ text: this.newTask, completed: false });
this.newTask = '';
}
},
removeTask(index) {
this.tasks.splice(index, 1);
}
}
};
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
.completed-task {
text-decoration: line-through;
color: grey;
}
input {
margin-bottom: 10px;
padding: 5px;
}
button {
margin-left: 5px;
padding: 5px;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: flex;
align-items: center;
justify-content: center;
margin: 5px 0;
}
</style>
Step 3: Understanding the Code
Template Section:
<input v-model="newTask" placeholder="Add a new task" @keyup.enter="addTask" />
: This input field is bound to the newTask data property using v-model, enabling two-way data binding. The @keyup.enter="addTask" listens for the Enter key to trigger the addTask method.
<button @click="addTask">Add Task</button>
: This button triggers the addTask method when clicked.
<ul>
: This unordered list uses v-for to loop through the tasks array and render each task. The :key="index" is a unique identifier for each item, required by Vue to track list items efficiently.
<input type="checkbox" v-model="task.completed" />
: A checkbox that toggles the completed status of a task.
<span :class="{ 'completed-task': task.completed }">{{ task.text }}</span>
: The span element displays the task text. The :class directive conditionally applies the completed-task CSS class based on whether the task is completed.
<button @click="removeTask(index)">Delete</button>
: A button to remove the task from the list
Script Section:
data() Method:
newTask: A string that holds the value of the new task to be added.
tasks: An array that holds all tasks, each represented as an object with text and completed properties.
methods Object:
addTask(): This method adds the newTask to the tasks array and then clears the newTask input field.
removeTask(index): This method removes a task from the tasks array based on its index.
Style Section:
Basic CSS to style the to-do list, with a .completed-task class to strike through completed tasks.
Persist Tasks in Local Storage: You can enhance the app by saving tasks in the browser's local storage, so they persist across page reloads.
mounted() {
this.tasks = JSON.parse(localStorage.getItem('tasks')) || [];
},
methods: {
addTask() {
if (this.newTask.trim() !== '') {
this.tasks.push({ text: this.newTask, completed: false });
this.newTask = '';
localStorage.setItem('tasks', JSON.stringify(this.tasks));
}
},
removeTask(index) {
this.tasks.splice(index, 1);
localStorage.setItem('tasks', JSON.stringify(this.tasks));
}
}
Editing Tasks:
<template>
<div id="app">
<h1>Vue.js To-Do List</h1>
<input v-model="newTask" placeholder="Add a new task" @keyup.enter="addTask" />
<button @click="addTask">Add Task</button>
<ul>
<li v-for="(task, index) in tasks" :key="index">
<input type="checkbox" v-model="task.completed" />
<span v-if="!task.isEditing" :class="{ 'completed-task': task.completed }">{{ task.text }}</span>
<input v-else v-model="task.text" @keyup.enter="saveTask(task)" @blur="saveTask(task)" />
<button v-if="!task.isEditing" @click="editTask(task)">Edit</button>
<button @click="removeTask(index)">Delete</button>
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
newTask: '',
tasks: []
};
},
methods: {
addTask() {
if (this.newTask.trim() !== '') {
this.tasks.push({ text: this.newTask, completed: false, isEditing: false });
this.newTask = '';
}
},
removeTask(index) {
this.tasks.splice(index, 1);
},
editTask(task) {
task.isEditing = true;
},
saveTask(task) {
task.isEditing = false;
}
}
};
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
.completed-task {
text-decoration: line-through;
color: grey;
}
input {
margin-bottom: 10px;
padding: 5px;
}
button {
margin-left: 5px;
padding: 5px;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: flex;
align-items: center;
justify-content: center;
margin: 5px 0;
}
</style>
Install Font Awesome
First, include Font Awesome in your project. If you're using a CDN, you can add this link in your HTML file or directly in the
section of your index.html.<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
** Update the Template with Icons
**
Now, let's add some Font Awesome icons for editing, deleting, and completing tasks.
<template>
<div id="app" class="container">
<h1>Vue.js To-Do List</h1>
<div class="input-container">
<input v-model="newTask" placeholder="Add a new task" @keyup.enter="addTask" />
<button @click="addTask" class="btn btn-add"><i class="fas fa-plus"></i></button>
</div>
<ul>
<li v-for="(task, index) in tasks" :key="index" :class="{ completed: task.completed }">
<input type="checkbox" v-model="task.completed" id="checkbox" />
<span v-if="!task.isEditing" class="task-text">{{ task.text }}</span>
<input v-else v-model="task.text" @keyup.enter="saveTask(task)" @blur="saveTask(task)" class="edit-input" />
<div class="actions">
<button v-if="!task.isEditing" @click="editTask(task)" class="btn btn-edit"><i class="fas fa-edit"></i></button>
<button @click="removeTask(index)" class="btn btn-delete"><i class="fas fa-trash-alt"></i></button>
</div>
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
newTask: '',
tasks: []
};
},
methods: {
addTask() {
if (this.newTask.trim() !== '') {
this.tasks.push({ text: this.newTask, completed: false, isEditing: false });
this.newTask = '';
}
},
removeTask(index) {
this.tasks.splice(index, 1);
},
editTask(task) {
task.isEditing = true;
},
saveTask(task) {
task.isEditing = false;
}
}
};
</script>
<style>
body {
background-color: #f4f7f6;
font-family: 'Roboto', sans-serif;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
.container {
background-color: #ffffff;
border-radius: 10px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
padding: 20px;
max-width: 400px;
width: 100%;
}
h1 {
color: #333;
margin-bottom: 20px;
}
.input-container {
display: flex;
margin-bottom: 20px;
}
input {
flex: 1;
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
font-size: 16px;
}
.btn {
background-color: #4caf50;
color: white;
border: none;
padding: 10px;
border-radius: 5px;
cursor: pointer;
margin-left: 5px;
transition: background-color 0.3s;
}
.btn:hover {
background-color: #45a049;
}
.btn-add {
background-color: #28a745;
}
.btn-edit {
background-color: #ffc107;
}
.btn-delete {
background-color: #dc3545;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
padding: 10px;
border-radius: 5px;
background-color: #f8f9fa;
}
li.completed .task-text {
text-decoration: line-through;
color: #888;
}
li:hover {
background-color: #e9ecef;
}
.actions {
display: flex;
}
.actions .btn {
margin-left: 5px;
}
.edit-input {
flex: 1;
margin-right: 10px;
}
</style>
Top comments (0)