0

I have two problems with my simple to-do app. I've decided to add an option to save your tasks in local Storage and load them next time you visit the app. I've managed to write a function that saves all items by clicking "Zapisz" button in localStorage, but when I reload the page to load them, it cuts 1 or 2 of the items.

Also, I can't figure out how to add appropriate class to saved item (there are two different classes, "item" and "itemDone"). So this is my problem, and here is the JS code:

(function() {
/* Load items from localStorage if there are any */
window.addEventListener("DOMContentLoaded", function() {
    var myList = document.getElementById("list");
    for (var i = 0; i < localStorage.length; i++) {
        var item = document.createElement("li");
        localKey = localStorage.getItem(localStorage.key(i));
        item.innerHTML = JSON.parse(localKey);
        if (item.innerHTML.indexOf("check") != -1) {
            item.className = "itemDone";
        } else {
            item.className = "item";
        }
        myList.appendChild(item);
        var closers = document.getElementsByClassName("closer");
        for (var i = 0; i < closers.length; i++) {
            closers[i].addEventListener("click", deleteItem);
        }
    };
});

/* This function deletes parent element of clicked element - in this case a clicked element will be a "span" element with "closer" class, and it's parent is "li" element */
function deleteItem() {
    this.parentNode.parentNode.removeChild(this.parentNode);
};

/* this function allows to delete item from the loist */
var x = document.getElementsByClassName("closer"); // get all elements with "closer" class
for (var i = 0; i < x.length; i++) { // simple loop that adds eventListener to all elements with "closer" class
    x[i].addEventListener("click", deleteItem); // on click function "deleteItem" is invoked
};

/* This function adds "li" element to the list with text value from input #newInput, and then adds a span with innertext "X" and class .closer */
function addItem() {
    var myList = document.getElementById("list"); // get the main list ("ul")
    var newListItem = document.createElement("li"); //create a new "li" element
    var itemText = document.getElementById("newInput").value; // read the input value from #newInput
    var listText = document.createTextNode(itemText); // create text node with calue from input
    newListItem.appendChild(listText); // add text node to new "li" element
    newListItem.className = ""
    if (itemText === "") { // if input value is empty
        alert("Pole nie może być puste"); // show this alert
    } else { // if it's not empty
        var x = document.createElement("span"); // create a new "span" element
        x.innerText = "X"; // add inner text to "span" element
        x.className = "closer"; // add class to "span" element
        x.addEventListener("click", deleteItem, false);
        myList.appendChild(newListItem); // add created "li" element to "ul"
        newListItem.className = "item"; // add class to new "li" element
        newListItem.appendChild(x); // add a "span" to new "li" element
        var itemText = document.getElementById("newInput"); // read current input value
        itemText.value = ""; // set current input value to null
    }
};
/* addButton reffers to the button used to add new elements to the list. After clicking it, function "addItem" is called */
var addButton = document.getElementById("createNew"); // fetch the "createNew" button
addButton.addEventListener("click", addItem); // add click event to "createNew" button and run function
/* This function clears all added elements from the list */

/* This script adds a method in which you can also add a new "li" item with "enter" keydown */
var textAdd = document.getElementById("newInput"); // target input
textAdd.addEventListener ("keypress", function (e) { // add event listener on "keypress"
    var key = e.which || e.keyCode; // which key is being used
    if (key === 13){ // if pressed key is "enter" (13)
    e.preventDefault(); // prevent default "enter" action
    addItem(); // addItem function is invoked
    };
});

function clearList(){
    var myList = document.getElementById("list"); // get the "ul" element
    myList.innerHTML = ""; // clear all it's children
    window.localStorage.clear();
};
/* deleteAll variable reffers the button used to clear all items. After clicking it, function "deleteAll" is called */
var deleteAll = document.getElementById("deleteAll"); // fetch the "deleteAll" button
deleteAll.addEventListener("click", clearList); // add click event to "deleteAll" button and run finction

/* This code changes the class of clicked "li" element from "item" to "itemDone" using event delegation method */
document.getElementById("list").addEventListener("click", function(e) {
    if (e.target && e.target.matches("li.item")) { // if event target matches an "li" item with "item" class
        e.target.className = "itemDone"; // change class to "itemDone"
        var check = document.createElement("i"); // create new "i" element
        check.className = "fa fa-check done"; // add class to "i" element
        e.target.appendChild(check); // add new "i" element to event target ("li")
    } else if (e.target && e.target.matches("li.itemDone")) {// if event target matches an "li" item with "itemDone" class
    e.target.className = "item"; // change class to "item"
        var done = e.target.getElementsByClassName("done"); // choose target with "done" class
        for (var i = 0; i < done.length; i++) {
        e.target.removeChild(done[i]); // remove "i" element with "done" class from target
        };
    };
});

/* Save all "li" items form the list to localStorage */
var saveAll = document.getElementById("saveAll");
saveAll.addEventListener("click", function() {
    var item = document.getElementsByTagName("li");
    for (var i = 0; i < item.length; i++) {
        localStorage.setItem([i], JSON.stringify(item[i].innerHTML));
    };
});
})();

And HTML:

<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Sansita&amp;subset=latin-ext"> 
    <link rel="stylesheet" href="css/font-awesome.min.css">
    <link rel="stylesheet" href="css/main.css">
    <title>Prosta aplikacja To-Do list</title>
    <meta name="description" content="Prosta aplikacja zadań To-Do">
    <meta name="keywords" content="aplikacja, to-do, lista, zadań, javascript">
    <meta name="author" content="Michał Grochowski">
</head>
<body> 
<header id="header">
    <div class="description">
    <h1>Prosta aplikacja To-Do</h1>
    </div>
</header>
<section>  
    <div class="mainlist">
        <form class="form">
            <input id="newInput" type="text" placeholder="Dodaj pozycję">
            <button id="createNew" type="button">Dodaj</button>
        </form>
        <h2>Moja lista:</h2>
        <div class="listBg">
            <ul id="list">
            </ul>
        </div>
        <div class="flex-row">
            <button id="deleteAll" type="button">Wyczyść</button>
            <button id="saveAll" type="button">Zapisz</button>
        </div>
        <p class="helpText">Żeby dodać pozycję do listy wpisz tekst w polu "Dodaj pozycję", a następnie kliknij przycisk "Dodaj" lub wciśnij enter.</p>
        <p class="helpText">Kliknij wybrane zadanie, żeby oznaczyć je jako wykonane. W ten sam sposób możesz je odznaczyć.</p>
        <p class="helpText">Usuń wybraną pozycję z listy kilkając "X" w prawym rogu.</p>
        <p class="helpText">Żeby zapisac listę w pamięci przeglądarki, kliknij przycisk "Zapisz".</p>
        <p class="helpText">Żeby wyczyścić całą listę, kliknij przycisk "Wyczyść".</p>
    </div>  
</section>
<footer>
    <div class="footer">
    <p>&copy 2017 <a href="http://dobrywebdev.pl" target="_blank">Michał Grochowski - Dobrywebdev.pl</a></p>
    </div>
</footer>
<script src="js/script.js"></script>
</body>
</html>

I know that it's not perfect and I will gladly accept any criticism of above code, but mostly I want to solve that localStorage issue.

halfer
  • 19,824
  • 17
  • 99
  • 186
grhu
  • 470
  • 3
  • 9
  • 27
  • Maybe you're reaching the [max limit on localStorage](https://stackoverflow.com/questions/2989284/what-is-the-max-size-of-localstorage-values)? – George May 23 '17 at 09:15
  • Propably not, even after adding 2 items, when I refresh the page it loads only 1. – grhu May 23 '17 at 09:25

1 Answers1

1

Problem is with variable i, specifically this for loop:

var closers = document.getElementsByClassName("closer");
    for (var i = 0; i < closers.length; i++) {
         closers[i].addEventListener("click", deleteItem);
    }

You’ve already declared i in the for loop above, and this loop is a duplicate of the loop below that deletes an item. I’ve deleted that loop, also I’ve putted all your code inside DOMContentLoaded event, so it will be available when the initial HTML has been completely loaded and parsed.

Note that in Firefox the order of localStorage keys is reversed, so list order will be reversed as well, in Chrome it will work as intended. And you should declare localKey with var keyword. Hope it will help.

// Not needed, but it helps, like with localKey declaration.
"use strict";

(function() {
/* Load items from localStorage if there are any */
window.addEventListener("DOMContentLoaded", function() {
    var myList = document.getElementById("list");

    for (var i = 0; i < localStorage.length; i++) {
        var item = document.createElement("li");
        var localKey = localStorage.getItem(localStorage.key(i));
        item.innerHTML = JSON.parse(localKey);
        if (item.innerHTML.indexOf("check") != -1) {
            item.className = "itemDone";
        } else {
            item.className = "item";
        }
        myList.appendChild(item);
    };

    /* This function deletes parent element of clicked element - in this case a clicked element will be a "span" element with "closer" class, and it's parent is "li" element */
    function deleteItem() {
        this.parentNode.parentNode.removeChild(this.parentNode);
    };

    // TODO: Not a function, wasn't working because all code needed to be inside addEventListener("DOMContentLoaded", like now.
    /* this function allows to delete item from the loist */
    var x = document.getElementsByClassName("closer"); // get all elements with "closer" class

    for (var i = 0; i < x.length; i++) { // simple loop that adds eventListener to all elements with "closer" class
        x[i].addEventListener("click", deleteItem); // on click function "deleteItem" is invoked
    };

    /* This function adds "li" element to the list with text value from input #newInput, and then adds a span with innertext "X" and class .closer */
    function addItem() {
        var myList = document.getElementById("list"); // get the main list ("ul")
        var newListItem = document.createElement("li"); //create a new "li" element
        var itemText = document.getElementById("newInput").value; // read the input value from #newInput
        var listText = document.createTextNode(itemText); // create text node with calue from input
        newListItem.appendChild(listText); // add text node to new "li" element
        newListItem.className = ""
        if (itemText === "") { // if input value is empty
            alert("Pole nie może być puste"); // show this alert
        } else { // if it's not empty
            var x = document.createElement("span"); // create a new "span" element
            x.innerText = "X"; // add inner text to "span" element
            x.className = "closer"; // add class to "span" element
            x.addEventListener("click", deleteItem, false);
            myList.appendChild(newListItem); // add created "li" element to "ul"
            newListItem.className = "item"; // add class to new "li" element
            newListItem.appendChild(x); // add a "span" to new "li" element
            var itemText = document.getElementById("newInput"); // read current input value
            itemText.value = ""; // set current input value to null
        }
    };
    /* addButton reffers to the button used to add new elements to the list. After clicking it, function "addItem" is called */
    var addButton = document.getElementById("createNew"); // fetch the "createNew" button
    addButton.addEventListener("click", addItem); // add click event to "createNew" button and run function
    /* This function clears all added elements from the list */

    /* This script adds a method in which you can also add a new "li" item with "enter" keydown */
    var textAdd = document.getElementById("newInput"); // target input
    textAdd.addEventListener ("keypress", function (e) { // add event listener on "keypress"
        var key = e.which || e.keyCode; // which key is being used
        if (key === 13){ // if pressed key is "enter" (13)
        e.preventDefault(); // prevent default "enter" action
        addItem(); // addItem function is invoked
        };
    });

    function clearList(){
        var myList = document.getElementById("list"); // get the "ul" element
        myList.innerHTML = ""; // clear all it's children
        window.localStorage.clear();
    };
    /* deleteAll variable reffers the button used to clear all items. After clicking it, function "deleteAll" is called */
    var deleteAll = document.getElementById("deleteAll"); // fetch the "deleteAll" button
    deleteAll.addEventListener("click", clearList); // add click event to "deleteAll" button and run finction

    /* This code changes the class of clicked "li" element from "item" to "itemDone" using event delegation method */
    document.getElementById("list").addEventListener("click", function(e) {
        if (e.target && e.target.matches("li.item")) { // if event target matches an "li" item with "item" class
            e.target.className = "itemDone"; // change class to "itemDone"
            var check = document.createElement("i"); // create new "i" element
            check.className = "fa fa-check done"; // add class to "i" element
            e.target.appendChild(check); // add new "i" element to event target ("li")
        } else if (e.target && e.target.matches("li.itemDone")) {// if event target matches an "li" item with "itemDone" class
        e.target.className = "item"; // change class to "item"
            var done = e.target.getElementsByClassName("done"); // choose target with "done" class
            for (var i = 0; i < done.length; i++) {
            e.target.removeChild(done[i]); // remove "i" element with "done" class from target
            };
        };
    });

    /* Save all "li" items form the list to localStorage */
    var saveAll = document.getElementById("saveAll");
    saveAll.addEventListener("click", function() {
        var item = document.getElementsByTagName("li");
        for (var i = 0; i < item.length; i++) {
            localStorage.setItem([i], JSON.stringify(item[i].innerHTML));
        };
    });
});
})();
Zero
  • 356
  • 3
  • 7
  • Wow, thank you for your help :) As usual, one or two stupid mistakes can go unnoticed and whole scripts doesn't behave like you want it to. But I'm curious, why does Firefox user-agent load localStorage keys in different order than Chrome or Edge? I couldn't find any information about that beside a small entry on MDN saying that you should not trust that ordering. – grhu May 24 '17 at 13:19
  • Happy to help! It is interesting for me also, unfortunately I could not find the answer. Sometimes browsers implement things differently. – Zero May 24 '17 at 13:52
  • Well, maybe I'll try to find some more about that. :) Anyway, thanks again for your help. – grhu May 24 '17 at 16:01