Hey everyone the previous related post is here.
The project is still the same so I will jump into the issue and code.
Issue
The issue this time is that users should be able to have multiple task lists and be able to switch between and view them.
Code
This PR was a PAIN. This is the type of stuff you would do with components but pure js as I said is hard. Anyways the first thing I had to do was modify the index html because we use Bootstrap and my implementation was going to use Cards which need bootstrap built javascript libraries. The required line was
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p"
crossorigin="anonymous"
></script>
The next thing I started thinking about was how dynamically create a task list. What I needed to do was extract all the html code into a createTaskList function that would append the following html to the dom and also register all the buttons and etc with eventListeners. I use an incrementing taskListId to keep the eventListeners unique. The other major differnce is I switched from a sole array to an object that contains an array property of tasks. I had to add an if else though because on page reload the tasks would not be fetched from localStorage correctly so I had to target reloads with registering the eventlistener on document as such
document.addEventListener('DOMContentLoaded', addTaskList);
we then also set renderedproperty to false which tells the init function to rerender all the tasks only if it was a page reload.
function addTaskList(event) {
//If it is page load just render the collapse stuff
if (event.target == document) {
for (let list in tasklists) {
tasklists[list].rendered = false;
const html = `
<div>
<div class="card">
<div class="card-header" id="heading${list}">
<h5 class="mb-0">
<button
class="btn btn-link"
type="button"
data-bs-toggle="collapse"
data-bs-target="#collapse${list}"
aria-expanded="true"
aria-controls="collapse${list}"
data-indexNum=${list}
>
${tasklists[list].name}
</button>
</h5>
</div>
<div id="collapse${list}" class="collapse" aria-labelledby="heading${list}" data-bs-parent="#accordionTaskList">
<div class="card-body">
<div class="form-floating" id="filter-wrapper${list}">
<input type="text" name="filter${list}" id="filter${list}" class="form-control" placeholder="filter" />
<label for="filter${list}">Search for task</label>
</div>
<ol class="list-group list-group-numbered mt-3 mb-3" id="tasklist${list}"></ol>
<a href="#" class="btn btn-sm btn-outline-danger" id="clear-tasks${list}">Clear tasks</a>
<a href="#" class="btn btn-sm btn-outline-danger" id="clear-comp-tasks${list}">Clear completed tasks</a>
<hr />
<form id="task-form${list}" class="d-flex">
<div class="form-floating d-flex flex-fill">
<input type="text" name="task" id="task${list}" class="form-control me-3" placeholder="New Task" />
<label for="task${list}">Name new task</label>
</div>
<input type="submit" class="btn btn-success" value="Add new task" />
</form>
</div>
</div>
</div>
</div>
</div>
`;
let doc = new DOMParser().parseFromString(html.trim(), 'text/html');
let taskListNode = doc.body.querySelector('div');
accordian.appendChild(taskListNode);
const taskform = accordian.querySelector(`#task-form${list}`);
const tasklist = accordian.querySelector(`#tasklist${list}`);
const clearTasks = accordian.querySelector(`#clear-tasks${list}`);
const clearCompTasks = accordian.querySelector(`#clear-comp-tasks${list}`);
const filter = accordian.querySelector(`#filter${list}`);
taskListNode.querySelector('button').addEventListener('click', (event) => {
selected = event.target.dataset.indexnum;
Tasklist.init();
});
taskform.addEventListener('click', Tasklist.add);
tasklist.addEventListener('click', Tasklist.remove);
tasklist.addEventListener('mouseup', Tasklist.complete);
clearTasks.addEventListener('click', Tasklist.deleteAll);
clearCompTasks.addEventListener('click', Tasklist.deleteAllCompleted);
filter.addEventListener('keyup', Tasklist.filter);
}
} else {
event.preventDefault();
const taskListName = taskListInput.value.trim();
const html = `
<div>
<div class="card">
<div class="card-header" id="heading${tasklistId}">
<h5 class="mb-0">
<button
class="btn btn-link"
type="button"
data-bs-toggle="collapse"
data-bs-target="#collapse${tasklistId}"
aria-expanded="true"
aria-controls="collapse${tasklistId}"
data-indexNum=${tasklistId}
>
${taskListName}
</button>
</h5>
</div>
<div id="collapse${tasklistId}" class="collapse" aria-labelledby="heading${tasklistId}" data-bs-parent="#accordionTaskList">
<div class="card-body">
<div class="form-floating" id="filter-wrapper${tasklistId}">
<input type="text" name="filter${tasklistId}" id="filter${tasklistId}" class="form-control" placeholder="filter" />
<label for="filter${tasklistId}">Search for task</label>
</div>
<ol class="list-group list-group-numbered mt-3 mb-3" id="tasklist${tasklistId}"></ol>
<a href="#" class="btn btn-sm btn-outline-danger" id="clear-tasks${tasklistId}">Clear tasks</a>
<a href="#" class="btn btn-sm btn-outline-danger" id="clear-comp-tasks${tasklistId}">Clear completed tasks</a>
<hr />
<form id="task-form${tasklistId}" class="d-flex">
<div class="form-floating d-flex flex-fill">
<input type="text" name="task" id="task${tasklistId}" class="form-control me-3" placeholder="New Task" />
<label for="task${tasklistId}">Name new task</label>
</div>
<input type="submit" class="btn btn-success" value="Add new task" />
</form>
</div>
</div>
</div>
</div>
</div>
`;
let doc = new DOMParser().parseFromString(html.trim(), 'text/html');
let taskListNode = doc.body.querySelector('div');
accordian.appendChild(taskListNode);
const taskform = accordian.querySelector(`#task-form${tasklistId}`);
const tasklist = accordian.querySelector(`#tasklist${tasklistId}`);
const clearTasks = accordian.querySelector(`#clear-tasks${tasklistId}`);
const clearCompTasks = accordian.querySelector(`#clear-comp-tasks${tasklistId}`);
const filter = accordian.querySelector(`#filter${tasklistId}`);
taskListNode.querySelector('button').addEventListener('click', (event) => {
selected = event.target.dataset.indexnum;
Tasklist.init();
});
taskform.addEventListener('click', Tasklist.add);
tasklist.addEventListener('click', Tasklist.remove);
tasklist.addEventListener('mouseup', Tasklist.complete);
clearTasks.addEventListener('click', Tasklist.deleteAll);
clearCompTasks.addEventListener('click', Tasklist.deleteAllCompleted);
filter.addEventListener('keyup', Tasklist.filter);
tasklists[tasklistId] = {
name: taskListName,
tasks: [],
};
++tasklistId;
localStorage.setItem('tasklists', JSON.stringify(tasklists));
}
}
The init function looks like this
static init() {
//Only render tasks from init once otherwise tasks added everytime tasklist is opened
if (!('rendered' in tasklists[selected]) || tasklists[selected].rendered == false) {
tasklists[selected].rendered = true;
localStorage.setItem('tasklists', JSON.stringify(tasklists));
tasklists[selected].tasks.forEach((task) => Tasklist.renderTask(task));
}
Tasklist.filter(); //TODO: ???
}
It just renders the tasks to the page and only renders once or else it would add the tasks repeatedly.
Overall Thoughts
I recommend just using a framework once you realized that global state management is a must or if you realize that you benefit from components. Only go through this pain if you want to figure stuff out. The PR was pretty fun though.
Top comments (0)