I refactored the code to be able to create multiple instances of a To Do List by encapusalting the logic in a class. The concept of the To Do List is that you can drag a To Do Item from one column to another and it will change color and place, depending on which column it is dragged to.
Each list has a instance of the TaskManager class to handle the individual eventListeners and querySelectors.
The problem is that now when i use "this" keyword in deleteButtonClick or taskClick method i get "this.getAttribute is not a function" And when i drag the To Do Item it changes color but not place.
Before "this" was references the item clicked, but now i am not sure what it is refrencing.
"use strict";
class TaskManager {
constructor(identifier) {
this.identifier = identifier;
this.addTaskText = "Add";
this.updateTaskText = "Update";
this.TodoContent = document.querySelector(`.${this.identifier}`);
this.toDoList = this.TodoContent.querySelector(".toDoList");
this.draggedTask = {};
this.toDoListHeight = this.toDoList.offsetHeight;
this.ongoingListHeight = 0;
this.doneListHeight = 0;
document.addEventListener("DOMContentLoaded", () => {
const currentTask = this.TodoContent.querySelector(".currentTask");
currentTask.setAttribute("currentid", "");
currentTask.setAttribute("lastid", "0");
});
this.TodoContent.querySelector(".addTask").addEventListener("click", () => this.addTask());
this.TodoContent.querySelector(".btnOk").addEventListener("click", () => this.handleTaskButton());
this.TodoContent.querySelector(".btnCancel").addEventListener("click", () => this.cancelTaskEdition());
this.TodoContent.querySelector(".txtTask").addEventListener("keyup", (e) =>
e.code === "Escape" ? this.cancelTaskEdition() : true
);
this.TodoContent.querySelector(".showHelp").addEventListener("click", () => this.showHelp());
const btnClose = this.TodoContent.querySelector(".btnClose");
btnClose.addEventListener("click", () => this.hideHelp());
btnClose.addEventListener("keyup", (e) => (e.code === "Escape" ? this.hideHelp() : true));
this.TodoContent.querySelectorAll(".listColumn").forEach((list) => {
list.addEventListener("dragover", (e) => e.preventDefault());
});
this.TodoContent.querySelector(".toDoList").addEventListener("drop", () => this.dropTask("toDo"));
this.TodoContent.querySelector(".ongoingList").addEventListener("drop", () => this.dropTask("ongoing"));
this.TodoContent.querySelector(".doneList").addEventListener("drop", () => this.dropTask("done"));
}
addTask() {
const currentTask = this.TodoContent.querySelector(".currentTask");
const txtTask = this.TodoContent.querySelector(".txtTask");
const newID = parseInt(currentTask.getAttribute("lastid")) + 1;
txtTask.value = "";
this.TodoContent.querySelector(".btnOk").value = this.addTaskText;
currentTask.setAttribute("currentid", newID);
currentTask.style.display = "block";
txtTask.focus();
}
taskClick() {
const currentTask = this.TodoContent.querySelector(".currentTask");
const txtTask = this.TodoContent.querySelector(".txtTask");
const ID = parseInt(this.getAttribute("taskId"));
this.TodoContent.querySelector(".btnOk").value = this.updateTaskText;
txtTask.value = this.innerText;
currentTask.setAttribute("currentid", ID);
currentTask.style.display = "block";
txtTask.focus();
}
deleteButtonClick(e) {
e.stopPropagation();
const taskHeight = this.parentElement.offsetHeight + 10;
const currentListName = this.parentElement.parentElement.id;
this.parentElement.remove();
switch (currentListName) {
case "toDoList":
this.toDoListHeight -= taskHeight;
break;
case "ongoingList":
this.ongoingListHeight -= taskHeight;
break;
case "doneList":
this.doneListHeight -= taskHeight;
break;
}
this.resizeLists();
}
handleTaskButton() {
const taskText = this.TodoContent.querySelector(".txtTask");
const currentTask = this.TodoContent.querySelector(".currentTask");
if (taskText.value.trim() === "") {
taskText.focus();
return false;
}
if (this.TodoContent.querySelector(".btnOk").value === this.addTaskText) {
this.addTaskToList();
} else {
this.updateTask();
}
taskText.value = "";
currentTask.style.display = "none";
}
addTaskToList() {
const task = document.createElement("div");
const taskText = this.TodoContent.querySelector(".txtTask");
const currentTask = this.TodoContent.querySelector(".currentTask");
const newID = parseInt(currentTask.getAttribute("currentid")) + 1;
task.classList.add("task");
task.classList.add("toDo");
task.innerText = taskText.value;
task.setAttribute("taskId", currentTask.getAttribute("currentid"));
currentTask.setAttribute("lastid", newID);
task.addEventListener("click", () => this.taskClick());
task.setAttribute("draggable", "true");
task.addEventListener("dragstart", (e) => this.dragStart(e));
task.prepend(this.deleteButton());
this.toDoList.prepend(task);
this.toDoListHeight += task.offsetHeight + 10;
this.resizeLists();
}
updateTask() {
const taskText = this.TodoContent.querySelector(".txtTask");
const currentTask = this.TodoContent.querySelector(".currentTask");
const task = this.TodoContent.querySelector('div.task[taskid="' + currentTask.getAttribute("currentid") + '"]');
const previousHeight = task.offsetHeight;
const currentListName = task.parentNode.id;
task.innerText = taskText.value;
task.prepend(this.deleteButton());
switch (currentListName) {
case "toDoList":
this.toDoListHeight = this.toDoListHeight - previousHeight + task.offsetHeight;
break;
case "ongoingList":
this.ongoingListHeight = this.ongoingListHeight - previousHeight + task.offsetHeight;
break;
case "doneList":
this.doneListHeight = this.doneListHeight - previousHeight + task.offsetHeight;
}
this.resizeLists();
}
cancelTaskEdition() {
const currentTask = this.TodoContent.querySelector(".currentTask");
currentTask.setAttribute("currentid", "0");
currentTask.style.display = "none";
}
showHelp() {
this.TodoContent.querySelector(".help").style.display = "block";
this.TodoContent.querySelector(".btnClose").focus();
}
hideHelp() {
this.TodoContent.querySelector(".help").style.display = "none";
}
dragStart(e) {
e.dataTransfer.setData("text/plain", null);
this.draggedTask = e.target;
}
dropTask(listName) {
const taskList = this.draggedTask.parentNode.id;
if (taskList !== listName + "List") {
const taskHeight = this.draggedTask.offsetHeight + 10;
this.draggedTask.parentNode.removeChild(this.draggedTask);
switch (taskList) {
case "toDoList":
this.toDoListHeight -= taskHeight;
break;
case "ongoingList":
this.ongoingListHeight -= taskHeight;
break;
case "doneList":
this.doneListHeight -= taskHeight;
break;
}
this.draggedTask.classList.remove("toDo", "ongoing", "done");
this.draggedTask.classList.add(listName);
this.toDoList.appendChild(this.draggedTask);
switch (listName) {
case "toDo":
this.toDoListHeight += taskHeight;
break;
case "ongoing":
this.ongoingListHeight += taskHeight;
break;
case "done":
this.doneListHeight += taskHeight;
break;
}
this.resizeLists();
}
}
deleteButton() {
const deleteButton = document.createElement("a");
deleteButton.classList.add("deleteButton");
deleteButton.addEventListener("click", (e) => this.deleteButtonClick(e));
return deleteButton;
}
resizeLists() {
const higherListHeight = Math.max(this.toDoListHeight, this.ongoingListHeight, this.doneListHeight);
this.TodoContent.querySelectorAll(".listColumn").forEach((list) => {
list.style.height = higherListHeight + "px";
});
this.TodoContent.querySelector(".listContent").style.height = higherListHeight + 20 + "px";
}
}
class TaskManagerManager {
constructor() {
this.taskManagers = [];
//ensure incrementing id
this.countArray = [];
}
count() {
return this.countArray.length;
}
createTaskManager(identifier) {
this.identifier = identifier;
console.log(this.identifier);
const taskManager = new TaskManager(this.identifier);
this.taskManagers.push(taskManager);
this.countArray.push(0);
return taskManager;
}
}
const taskManagerManager = new TaskManagerManager();
const taskManager1 = taskManagerManager.createTaskManager("contentWrapper");
document.querySelector("#new_todo").addEventListener("click", createList);
async function createList() {
const newId = await newList();
console.log(newId);
const taskManager = taskManagerManager.createTaskManager(newId);
async function newList() {
const cloneContainer = document.querySelector(".clone");
const clone = cloneContainer.cloneNode(true);
const elementsToRemove = clone.querySelectorAll(`[taskid]`);
elementsToRemove.forEach((element) => {
element.parentNode.removeChild(element);
});
const todoContent = clone.querySelector(".contentWrapper");
const newId = "todoContent" + (taskManagerManager.count() + 1);
todoContent.classList.add(newId);
console.log(newId);
// Create TaskManager after updating the id
cloneContainer.parentNode.appendChild(clone);
return newId;
}
}
@import url("https://fonts.googleapis.com/css2?family=Raleway:wght@400&display=swap");
:root {
--colorTextSecondary: floralwhite;
--colorPrimary: blue;
--colorSecondary: white;
--colorTertiary: yellow;
--colorToDo: lightsalmon;
--colorOngoing: palegoldenrod;
--colorDone: lightgreen;
--imgDelete: url(../img/trash.png);
}
* {
margin: 0;
box-sizing: border-box;
}
body {
font-family: "Raleway", Arial, Helvetica, sans-serif;
}
header {
padding: 0.5rem 0 0.5rem 0.5rem;
color: var(--colorTextSecondary);
background-color: var(--colorPrimary);
}
header > h1 {
text-indent: 2rem;
}
main {
padding: 0.5rem;
line-height: 1.4rem;
}
footer {
position: fixed;
bottom: 0;
width: 100%;
padding: 0.25rem 0 0.25rem 0;
color: var(--colorTextSecondary);
background-color: var(--colorPrimary);
text-align: center;
}
header.listHeaders {
border-radius: 0.5rem 0.5rem 0 0;
color: var(--colorPrimary);
background-color: var(--colorTertiary);
font-weight: bold;
}
div.toDoHeader {
float: left;
width: 33%;
}
div.ongoingHeader {
float: left;
width: 33%;
}
div.toDoList {
float: left;
width: 33%;
}
div.ongoingList {
float: left;
width: 33%;
}
div.doneList {
float: left;
width: 33%;
}
main.listContent {
min-height: 40px;
padding-right: 0;
overflow: auto;
}
.clone {
border-radius: 0.5rem 0.5rem 0.5rem 0.5rem;
background-color: var(--colorSecondary);
}
a {
color: var(--colorPrimary);
background-color: var(--colorTextSecondary);
text-align: center;
border-radius: 0.25rem;
}
a:hover {
cursor: pointer;
background-color: var(--colorTertiary);
}
.buttons {
display: flex;
padding: 10px;
justify-content: space-between;
}
.buttons a {
margin-right: 10px;
}
#new_todo {
display: inline-block;
width: 25%;
border-radius: 0.25rem;
line-height: 2rem;
}
a.addTask {
display: inline-block;
width: 25%;
border-radius: 0.25rem;
font-size: 3rem;
line-height: 2rem;
}
a.save {
display: inline-block;
width: 25%;
border-radius: 0.25rem;
font-size: 1rem;
line-height: 2rem;
font-weight: 600;
color: rgb(171, 21, 21);
}
a.showHelp {
display: block;
float: right;
margin-right: 1rem;
padding: 0.5rem;
font-size: 1.2rem;
}
a.deleteButton {
display: block;
float: right;
width: 1.5rem;
height: 1.5rem;
margin-left: 0.25rem;
background-image: var(--imgDelete);
}
div.modal {
display: none;
position: fixed;
z-index: 1;
top: 3.5rem;
border: solid thick var(--colorPrimary);
border-radius: 0.5rem;
padding: 0.25rem;
width: 80%;
height: 80%;
left: 10%;
background-color: var(--colorTextSecondary);
}
@media (min-width: 768px) {
div.task {
width: 60%;
left: 20%;
}
}
@media (max-height: 480px) {
div.task {
height: 60%;
}
}
div.help {
left: 20%;
width: 60%;
height: auto;
}
p.helpText {
margin-bottom: 0.5rem;
}
textarea.txtTask {
width: 99%;
height: 92%;
resize: none;
overflow: auto;
border-color: var(--colorSecondary);
}
input[type="button"] {
padding: 0.25rem 1rem;
font-size: 1rem;
font-weight: bold;
border: none;
border-radius: 0.25rem;
cursor: pointer;
}
div.buttons {
padding-right: 2rem;
text-align: right;
}
div.task {
border: thin solid var(--colorPrimary);
border-radius: 0.25rem;
margin-bottom: 0.5rem;
padding: 0.25rem;
width: 95%;
max-height: 10rem;
overflow: auto;
word-wrap: break-word;
cursor: pointer;
}
div.toDo {
background-color: var(--colorToDo);
}
div.ongoing {
background-color: var(--colorOngoing);
}
div.done {
background-color: var(--colorDone);
}
<!DOCTYPE html>
<html lang="en">
<head>
<title>To Do List</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="author" content="Arturo Mora-Rioja" />
<meta name="description" content="A simple to do list with three possible states: to do, ongoing, done" />
<link rel="stylesheet" href="css/styles.css" />
<script src="js/script.js" defer></script>
</head>
<body>
<header>
<h1>To Do List</h1>
<a id="new_todo">New list</a>
</header>
<div class="clone">
<div class="contentWrapper">
<a class="showHelp">Help</a>
<div>
<header class="listHeaders">
<div class="toDoHeader listHeader">To do</div>
<div class="ongoingHeader listHeader">Ongoing</div>
<div class="doneHeader listHeader">Done</div>
</header>
<main class="listContent">
<div class="toDoList listColumn"></div>
<div class="ongoingList listColumn"></div>
<div class="doneList listColumn"></div>
</main>
<div class="currentTask modal">
<textarea class="txtTask"></textarea><br />
<div class="buttons">
<input type="button" class="btnOk" value="Add" />
<input type="button" class="btnCancel" value="Cancel" />
</div>
</div>
<div class="buttons"><a class="addTask">+</a>
</div>
<div class="help modal">
<p class="helpText">
Add a new "to do" task by pressing the plus ("+") button.<br />
Move a task from one area to another ("to do", "ongoing", "done") by dragging and dropping it.
</p>
<div class="buttons">
<input type="button" class="btnClose" value="Close" />
</div>
</div>
</div>
</main>
<footer>footer</footer>
</body>
</html>