I'm starting to learn javascript and I have a simple todo application where I want to be able to drag and drop the different todo's created. A simple way to do this was with SortableJS library, but it doesn't work the way i want to. After implementing the simple sortable function, when dragging on a todo, it grabs the whole todo-list instead of a single todo
I think the issue is because I dynamically create the html, but I'm kinda stuck and would appreciate any suggestions.
//Selectors
const todoInput = document.querySelector(".todos-input"); //input for adding a todo
const todoButton = document.querySelector(".todos-button"); //add todo-button
const todoList = document.querySelector(".todos-list"); //the todo-list
//Event listeners
todoButton.addEventListener("click", addTodo);
todoList.addEventListener("click", deleteTodo);
todoList.addEventListener("click", completeTodo);
//Functions
function addTodo(event) {
//prevent form from submitting
event.preventDefault();
//create a div for the todos-list
const todoDiv = document.createElement("div");
//add classlist for styling
todoDiv.classList.add("todo");
//Create LI
const newTodo = document.createElement("li");
//output the value from the add-todo field
if (todoInput.value != "") {
newTodo.innerText = todoInput.value;
} else {
return false;
}
//classlist for styling
newTodo.classList.add("todo-item");
//append child to div
todoDiv.appendChild(newTodo);
//complete button
const completedButton = document.createElement("button");
completedButton.innerHTML = '<i class="fas fa-check"><i/>';
completedButton.classList.add("completed-btn");
todoDiv.appendChild(completedButton);
//delete button
const deletedButton = document.createElement("button");
deletedButton.innerHTML = '<i class="fas fa-trash"><i/>';
deletedButton.classList.add("deleted-btn");
todoDiv.appendChild(deletedButton);
//drag button
const dragButton = document.createElement("button");
dragButton.innerHTML = '<i class="icon fa fa-bars"><i/>';
dragButton.classList.add("drag-btn");
dragButton.classList.add("handle");
todoDiv.appendChild(dragButton);
//append div to list of todos
todoList.appendChild(todoDiv);
//clear input field after adding a new todo
todoInput.value = "";
//DRAG AND DROP
const dragArea = document.querySelector('.todos-section');
new Sortable(dragArea, {
animation: 300
});
}
//deleting todo
function deleteTodo(e) {
//grab the item, whatever we are clicking on
const item = e.target;
//delete todo
if (item.classList[0] === "deleted-btn") {
//grab the parent element of the item, which is the todolist element in this case
const todo = item.parentElement;
//remove the todo
todo.remove();
}
}
//completing todo
function completeTodo(e) {
//grab the item, whatever we are clicking on
const item = e.target;
//complete todo
if (item.classList[0] === "completed-btn") {
const todo = item.parentElement;
//use the toggle because if the element has a class, then the classList.toggle method
//behaves like classList.remove and the class is removed from the element.
//And if the element does not have the specified class
//then classList.toggle, just like classList.add, adds this class to the element.
//So it basically does the add/remove operation for us depending on the state.
todo.classList.toggle("completed-todo");
}
}
/*Apply to all elements*/
* {
box-sizing: border-box;
list-style-type: none;
margin: 0;
padding: 0;
}
body {
font-family: "Merriweather Sans", sans-serif;
background: rgba(216, 206, 206, 0.787);
}
.wrapper {
display: flex;
position: relative;
}
/* Add todos-section */
.todos-bar {
position: fixed;
top: 5%;
left: 50%;
font-size: 17px;
border: 0;
transform: translate(-50%, -50%);
padding-left: 100px;
}
.todos-bar input {
width: 600px;
height: 50px;
border: 0px;
outline: none;
font-size: 20px;
padding-left: 20px;
border-radius: 5px;
}
.todos-bar button {
position: fixed;
background: rgba(20, 33, 93, 0.952);
color: white;
font-size: 20px;
border: 0;
outline: none;
height: 50px;
padding: 10px 20px;
right: 0px;
border-radius: 0px 5px 5px 0px;
cursor: pointer;
}
.todos-bar button:hover {
background: rgb(43, 54, 73);
}
/* Todos section */
.todos-section {
display: flex;
position: fixed;
top: 15%;
left: 37%;
}
.todos-list {
width: 600px;
}
.todo {
margin: 1.5rem;
background: white;
color: black;
font-size: 1.5rem;
display: flex;
justify-content: space-between;
align-items: center;
border-radius: 5px;
padding-left: 0.5rem;
margin: 15px;
transition: all 0.5s ease;
}
.todo li {
flex: 1;
}
.todo-item {
padding: 0rem 0.5rem;
padding-left: 2.5rem;
}
.deleted-btn,
.completed-btn {
background: rgb(248, 56, 56);
color: white;
border: none;
padding: 1rem;
cursor: pointer;
font-size: 1rem;
}
.completed-btn {
background: green;
}
.deleted-btn {
border-radius: 0px 5px 5px 0px;
}
.drag-btn {
display: block;
position: absolute;
background: white;
border: 2px solid white;
}
.fa-bars {
padding: 5px;
margin: 2px;
cursor: pointer;
}
.fa-trash,
.fa-check {
pointer-events: none;
}
.completed-todo {
text-decoration: line-through;
opacity: 0.5;
}
<head>
<link rel="stylesheet" href="style.css">
<script src="https://kit.fontawesome.com/47440aba67.js" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Sortable/1.14.0/Sortable.min.js"></script>
</head>
<body>
<div class="wrapper">
<!--ADD TODO-->
<div class="todos-bar">
<input type="text" class="todos-input" placeholder="Add to list...">
<button class="todos-button" type="submit"><i class="fas fa-plus"></i></button>
</div>
<!--TODO LIST-->
<div class="todos-section">
<ul class="todos-list"></ul>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
According to the documentation,
You can use any element for the list and its elements, not just ul/li
What you've implemented actually fits in this description since there is a ul
with div
tags inside. However, you are not referencing the right element in the dragArea
, because it should be the direct parent (.todos-list
) of your desired draggable children.
So, change it to .todos-list
and in addition pass handle
property to Sortable
constructor to reference the icon
in which you want to drag.
const dragArea = document.querySelector('.todos-list');
new Sortable(dragArea, {
animation: 300,
handle: '.fa-bars'
})
Working example