A(nother) todo: Version 1.1
Published: 2021-03-06 23:43:36

**Note** This post refers to [this branch](https://gitlab.com/jhechtf/vanilla-todo/-/tree/version-1.1)

Our initial version of the project works, but things are kind of ugly. 

For one thing, the entirety of the application lives inside the `main.js` file, and while that may not be an issue _now_, when the app is relatively simple, it's going to end up being a ***nightmare*** later on down the line as we add more and more features. I've kept a good summary in the [README.md](https://gitlab.com/jhechtf/vanilla-todo/-/blob/version-1.1/README.md), but I'll copy/paste it over so we can talk about what is going on.

>This branch is meant to be "I've been working in this project for awhile now, and I would like to refactor it to do some things differently."
>
>The things that are different here
>
>1. The application now does not all live inside the `main.js` file. Instead all of the functionality has been put into it's own files, with the main.js file simply serving as the aggregator of those events.
>2. The renderer and the store have been largely separated. The render function here is what would be called a "Pure" function -- it keeps no track of state and will render the todos based solely off their values into the DOM.
>3. Largely there are no direct interactions with the `todo` variable outside of the `todo.store.js` file with 1 exception. I could imagine that a slightly more knowledgeable engineer would  want to persist information between browser refreshes, and as such takes advantage of the `localStorage`. However, trying to pull this data from storage and use our todos store API was likely a bit more complex. I imagine someone having had a bit of time to fiddle around with adding the items in a loop, but "this way was faster and just worked" is definitely something I could see myself in my earlier years saying.
>
>However, this definitely has some downsides. The largest thing is that since we are no longer doing this all in the `main.js` file there are multiple places where we need to call `document.querySelector('#todo-list-container')`, which even for a relatively small app was rather annoying to write. 
>
>The question then is: how do we make it so that we only have to call the query selector once? There are a few ways, if you are starting off with Javascript and ESM think of a few and test them out.
>
>Beyond that, I think a lot of developers after looking at this code will say something like "I just _feel_ like there should be a way to get the rendering function to run when the data updates... there has to be **some** way to do that, right?"
>
>Also at this point many people are probably feeling the itch of wanting to make the UI a bit prettier, with a navbar and things...

So let's look at what our `src/` directory looks like now

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/njg9xgtr7brjvt15zc9h.PNG)

Well, there's definitely more files!

Notice that each one of the files is a bit more specific. There are two files that start with `todos`, but one very clearly says it is the "store"(`todos.store.js`) while the other is the "render" (`todos.render.js`). The store now handles actions related to the actual array of todos. This means adding, removing, and marking todos as done is all stored in one logical place.

```js
// todos.store.js
import renderTodos, { itemCache } from './todos.render';

/** @type {import('./todos.render').todo[]} */
const todos = [];
// Get an item from local storage
const stored = localStorage.getItem('todos__visited');

// if we do not have a value from our localStorage, add an example to the array.
if (!stored) {
  todos.push({
    title: 'Example todo',
    done: false,
    description: 'Extra details you may want the user to know.'
  });
  // make sure to set the storage so that other people do not see it again.
  localStorage.setItem('todos__visited', 'true');
}

export default todos;
/**
 * 
 * @param {string} title
 * @param {stirng} description
 * @returns {boolean} true if added / false if not.
 */
export function addTodo(title, description = null) {
  try {
    todos.push({ title, description: description || '', done: false });
    // render tick
    renderTodos(document.querySelector('#todo-list-container'), todos);
    return true;
  } catch (e) {
    return false;
  }

}

/**
 * 
 * @param {number} todo the index of the todo
 * @returns {-1 | 0 | 1} 1 for success, 0 for failure, -1 if the item could not be found. Helpful for debugging and not much else.
 */
export function removeTodo(todo) {
  // if we do not have this todo, don't try anything further
  if (!todos[todo]) return -1;
  // try to do this
  try {
    (itemCache.get(todos[todo]) || []).forEach(el => el && el.remove && el.remove());
    todos.splice(todo, 1);
    renderTodos(document.querySelector('#todo-list-container'), todos);
    return 1;
  } catch (e) {
    return 0;
  }
  // re-render
}
/**
 * 
 * @param {number} index 
 * @returns {boolean} True if toggled, false if there was an error.
 */
export function toggleTodo(index) {
  if (!todos[index]) return false;
  todos[index].done = !todos[index].done;
  renderTodos(document.querySelector('#todo-list-container'), todos);
}
```

The `.render.` file focuses solely on taking the data and rendering it out to the provided `<dl>` element.

Other than that, there isn't really a whole lot different about this branch. It was just meant to serve as a logical thing, going from the "but it works!" to the "well, now things are a bit sectioned out and we've separated out concerns."

```js
// todos.render.js
import styles from './styles.module.css';
/**
 * @typedef todo
 * @property {string} title
 * @property {boolean} done
 * @property {string} description
 */

/** @type {Map<todo, [HTMLElement, HTMLElement]} */
export const itemCache = new Map();

/**
 * @param {HTMLDListElement} todo
 * @param {todo[]} todos 
 * @returns {void} 
 */
export default function renderTodos(todo, todos) {
  const els = todos.flatMap((todo, index) => {
    // either we can grab this item from the cache OR we create a new one.
    const [dt, dd] = itemCache.get(todo) || [document.createElement('dt'), document.createElement('dd')];
    if (!itemCache.has(todo)) {
      // set the innerHTML, along with creating the delete span.
      const span = document.createElement('span');
      // Very generic close button
      span.innerHTML = '&times;'
      span.setAttribute('role', 'delete');
      dt.append(
        document.createTextNode(todo.title),
        span
      );

      // Add necessary classes.
      dt.classList.add(styles['todo-list-item']);
      dt.classList.toggle(styles['todo-list-complete'], todo.done);

      //update description element if we have a description
      if (todo.description) {
        dd.innerHTML = todo.description;
      }
      itemCache.set(todo, [dt, dd]);
    }

    dt.dataset.key = dd.dataset.key = index;
    dt.classList.toggle(styles['todo-list-complete'], todo.done);

    return [dt, dd];
  });
  // store the data so that it persists between sessions.
  localStorage.setItem('todos__data', JSON.stringify(todos));
  // append the items to the list.
  todo.append(...els);
}

```

Please do view the gitlab repository. I am working on getting a live preview of the "app" up and going but the workflow here is a bit weird.