Pure Js is Hard #2
Tags: opensource, javascript, hacktoberfest, osd600
Published: 2021-12-17 03:06:35

Hey everyone the previous related post is here.

{% link antoniobennett/pure-js-is-hard-41ob %}

The project is still the same so I will jump into the issue and code. 

### Issue

The [issue](https://github.com/ynn1k/simple-js-tasklist/issues/3) 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](https://github.com/ynn1k/simple-js-tasklist/pull/20) 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](https://getbootstrap.com/) and my implementation was going to use Cards which need bootstrap built javascript libraries. The required line was 

```html
 <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

```javascript
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.

```javascript
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

```javascript
  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.