Quasar Tutorial: Create a ToDo List with Adding, Deleting, and Dragging Capabilities
Making the most of Quasar components is simple once you understand the basics. Templates, slots, and models allow for almost limitless ability to customize your components. In my experience, all you really need to get started is an example of what works. In this article, we’ll create a todo component with the ability to add tasks, remove items, and rearrange the current list.
This list will be surrounded by a q-card component and will feature some custom styling classes. At the top of the card, we’ll have the q-input where a user will enter a new todo item. To add this item, press the enter key. V-model is basically the variable in which the entered text will be stored. Autofocus makes it so that the cursor is entered on the input field on page render, and clearable allows for the input text to be deleted with one button click.
<template>
<q-card flat bordered class="bg-grey-1 my-list">
<q-card-section>
<q-input
v-model="todo"
@keyup.enter="addTodo"
type="text"
label="ToDo List"
clearable
autofocus
/>
</q-card-section>
</q-card>
Next up we want to actually show the todo items. For this example, the list of todo tasks will be in a variable called items. A q-field is what I used because in other iterations of this I had to add some verification and error handling, so feel free to try quasar components other than q-field for this section if needed.
This is where things get a lot trickier. Within the q-field we add a template and draggable component which is what will render all the items with the ability to rearrange them by dragging. We have a handle icon that will be held down to actually perform the rearranging and drag-class/ghost-class that give the dragged item a highlight. Within the span is where we actually display the name of the task. Right above it we define the handle icon to drag an item, and below the span we have a section with a trash can to delete the task from the list.
The completed template:
<template>
<q-card flat bordered class="bg-grey-1 my-list">
<q-card-section>
<q-input
v-model="todo"
@keyup.enter="addTodo"
type="text"
label="ToDo List"
clearable
autofocus
/>
</q-card-section>
<q-card-section class="items-body">
<q-scroll-area style="height: 225px;">
<q-field
borderless
:model-value="items"
>
<template v-slot:control>
<draggable
:list="items"
item-key="id"
class="list-group"
handle=".handle"
@start="dragging = true"
@end="endDrag"
v-bind="dragOptions"
drag-class="drag"
ghost-class="ghost"
>
<template #item="{element}">
<q-item v-bind:style="dragging ?
'border:none;' : '' "
class="items-body-content"
dense
key="element">
<q-item-section class="item-span">
<q-icon class="fa fa-align-justify handle"/>
<span class="text">{{ element.task }}</span>
</q-item-section>
<q-item-section side>
<q-icon name="delete"
float-right
@click="removeToDo(element.id)"
/>
</q-item-section>
</q-item>
</template>
</draggable>
</template>
</q-field>
</q-scroll-area>
</q-card-section>
</q-card>
<template>
And the accompanying script
<script setup>
import { defineComponent, ref } from 'vue';
import draggable from 'vuedraggable';
import { uid } from 'quasar';
defineComponent(draggable);
const items = ref([
{ id: uid(), task: 'Task 1' },
{ id: uid(), task: 'Task 2' },
{ id: uid(), task: 'Task 3' },
// Add more tasks as needed
]);
// Reactive data
const todo = ref('September 23 ToDos');
const dragging = ref(false);
// Computed data
const dragOptions = {
animation: 200,
group: 'description',
disabled: false,
ghostClass: 'ghost',
};
// Methods
function endDrag() {
dragging.value = false;
}
function removeToDo(id) {
const index = items.value.findIndex((item) => item.id === id);
if (index !== -1) {
items.value.splice(index, 1);
}
}
function addTodo() {
const newItem = { id: uid(), task: todo.value };
items.value.push(newItem);
todo.value = ''; // reset item details
}
</script>
With optional styling
<style>
.my-list{
width: 350px;
margin: 10px;
}
.items-head p{
padding: 5px 20px;
margin: 10px;
color: #0B5AA2;
font-weight: bold;
font-size: 20px;
}
.items-body {
padding: 10px;
margin: 10px;
display: grid;
grid-gap: 10px;
}
.items-body-content {
padding: 10px 0 10px 10px;
grid-gap: 10px;
border-radius: 7px;
width: 280px;
}
.items-body-content:not(:active):hover {
border-radius: 15px;
border: 1px solid #0B5AA2;
}
.item-span {
display: block;
grid-template-rows: 1fr;
grid-template-columns: repeat(2, 1fr);
padding-right: 10px;
}
.handle {
padding-right: 15px;
}
.ghost {
opacity: 0.7;
background-color: #0B5AA2;
}
</style>
If you are new to customizing your vue or quasar components, I would highly recommend taking this example in bits and pieces, or only extracting parts that are relevant to you.
Happy coding!