3
\$\begingroup\$

This particular snippet is a simple to-do app that allows the user to add new items to the list, mark them as done, delete a single item, or delete all items.

What I would be looking for advice on are my use of a function constructor. It may be overkill for something like this, but it is more of a learning exercise to become familar with the process. Could the building up of each list item be done in a better way?

On the use of an IIFE, my understanding of these is that it can help self-contain code snippets and ensure they keep their scope. Would this be a good practice going forward, or do they have their best use cases?

If there are other areas of improvement I would welcome input regarding those. Thanks.

(function(){
    "use strict"

    let form, listArea, formInput, clearList;

    // Function constructor
    // Will build up each list item
    // With associated functions
    const Task = function (taskName) {
        this.taskName = taskName;
        this.buildTask = function () {
            let newListItem = document.createElement('li');

            newListItem.innerHTML = `<span></span>${this.taskName}<button class="remove-item">Bin It</button>`;
            newListItem.querySelector('.remove-item').addEventListener('click', removeItem);
            newListItem.querySelector('span').addEventListener('click', markDone);

            listArea.appendChild(newListItem);
        }
    }

    function markDone(e) {
        e.currentTarget.parentNode.classList.toggle('done');
    }

    function removeItem(e) {
        e.currentTarget.parentNode.remove();
    }

    function clearAllItems() {
        let listAreaItem = listArea.querySelectorAll('li');
        listAreaItem.forEach((item) => {
            item.remove();
        })
    }

    // On form submit
    // Create new instance of Task
    function formValidate(e) {
        e.preventDefault();

        const value = formInput.value

        if(!value) {
            formInput.dataset.state = 'invalid';
            return;
        }

        // Trim whitespace from the input
        const trimValue = formInput.value.trim();

        if(trimValue) {
            formInput.dataset.state = 'valid';
            new Task(trimValue).buildTask();
            formInput.value = '';
        }
    }

    function startup() {
        form = document.getElementById('form');
        listArea = document.getElementById('listArea');
        formInput = document.getElementById('addToField');
        clearList = document.getElementById('clearList');

        // Check for an submit on the form field
        form.addEventListener('submit', formValidate);
        clearList.addEventListener('click', clearAllItems);
    }

    window.addEventListener('DOMContentLoaded', startup);
})();
* {
    box-sizing: border-box;
}

body,
button,
form input,
form button {
    font-family: 'Space Mono', monospace;
}

button {
    color: #fff;
    border: none;
    background: #7e7e7e;
    height: 60px;
    padding: 0 25px;
}

button:hover,
ul li span:hover {
    cursor: pointer;
}

.input-area {
    max-width: 600px;
    margin-left: auto;
    margin-right: auto;
    padding: 50px;
}

#form {
    display: flex;
}

form input {
    border: none;
    border-bottom: 2px solid rgba(0,0,0,0.25);
    height: 60px;
    flex-grow: 1;
    font-size: 20px;
}

form input[data-state="invalid"] {
    border-color: red;
}

form input[data-state="valid"] {
    border-color: green;
}

form button {
    height: 60px;
    border: 2px solid rgba(0,0,0,0.25);
    flex-basis: 175px;
    flex-grow: 0;
    flex-shrink: 0;
    border: none;
    color: #fff;
}

ul {
    list-style: none;
    padding-left: 0;
}

ul li {
    display: flex;
    line-height: 60px;
    background-color: #e3e3e3;
    margin: 5px 0;
}

ul li.done {
    background-color: limegreen;
}

ul li span {
    display: inline-block;
    width: 60px;
    height: 60px;
    background-color: rgba(0,255,0,0.2);
    margin-right: 20px;
}

ul li.done span {
    background-color: green;
}

ul li button {
    height: 60px;
    padding: 0 20px;
    margin-left: auto;
    background-color: #db4646;
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>To Do App</title>
    <link rel="stylesheet" href="style.css">
    <link href="https://fonts.googleapis.com/css2?family=Space+Mono&display=swap" rel="stylesheet">
</head>
<body>
    <main>

        <div class="input-area">
            <h1>Do it.</h1>
            <form id="form">
                <input id="addToField" type="text">
                <button id="submitToList" type="submit">Add to List</button>
            </form>
            <ul id="listArea"></ul>
            <button id="clearList">Clear List</button>
        </div>
    </main>
</body>
<script src="script-2.js"></script>
</html>

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

On the use of an IIFE, my understanding of these is that it can help self-contain code snippets and ensure they keep their scope. Would this be a good practice going forward, or do they have their best use cases?

Usually you'd write ES5 modules and use a build tool like Rollup, Webpack or Parcel to compile your code into something understandable by older browsers.
Learning and keeping up with the whole ecosystem of NodeJS-based development tools can be overwhelming though.

If there are other areas of improvement I would welcome input regarding those.

Modern frameworks like React tend to approach working with DOM differently: instead of changing the existing tree of nodes, they throw away and recreate the whole UI on every change.

You'd need four things:

  1. A variable that holds the current state. I believe in your case that would be a list of tasks, e.g. {taskName: "task name", done: true}.
  2. Event handlers that respond to DOM events.
  3. A function that takes the current state and returns a new tree of DOM nodes, with event handlers attached.
  4. (Optional) A convenience function for wrapping event handlers. Each event handler changes state based on the current state and the event, and then replaces the old DOM with the new DOM created by function (3). This way defining an event handler is as simple as writing (state, event) => doSomethingAndReturnNewState(...).

This way you can store your whole application state in JS. Having a single source of truth makes it easier to keep track of all the possible states your application can be in.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.