Может ли не работать обработчик событий, из за области видимости?

Я только учусь JS недели 2-3.

Сделал с помощью видео сервис TODO (дела). И нужно сделать чтобы дела, которые уже добавлены сохранялись после перезагрузки страницы.
Я это сделал. Но обработчики событий click отказываются работать на кнопках (готово, удалить).

Может ли быть такое, что это как то из за зоны видимости происходит, например код события не видит кнопок созданных после попадания из LocalStorage?

Дела которые добавлены новые, он видит нормально, а те которые из массива после LocalStorage он не обрабатывает.

Вот код HTML index.html:

<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
    <link rel="stylesheet" 
    href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" 
    integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" 
    crossorigin="anonymous">
    <title>TODO</title>
    <script defer src="/home-task/8/todo-app.js"></script>
    <script>
        document.addEventListener('DOMContentLoaded', function() {
        let todoDefault1 = JSON.parse(localStorage.getItem('storageItem'));
            createTodoApp(document.getElementById('todo-app'), 'Мои дела', todoDefault1);
        });
    </script>
</head>
<body>
    <div class="container mb-5">
        <nav class="nav">
            <a href="index.html" class="nav-link">Мои дела</a>
            <a href="dad.html" class="nav-link">Дела кати</a>
            <a href="mom.html" class="nav-link">Пенивайздир делишки</a>
        </nav>
    </div>
    <div id="todo-app" class="container"></div>
</body>
</html>

Вот JS todo-app.js :

(function() {
    //создаем и возвращаем заголовок приложения
    function createAppTitle(title) {
        let appTitle = document.createElement('h2');
        appTitle.innerHTML = title;
        return appTitle;
    }

    //создаем и возвращаем форму для создания дела
    function createTodoItemForm() {
        let form = document.createElement('form');
        let input = document.createElement('input');
        let buttonWrapper = document.createElement('div');
        let button = document.createElement('button');

        form.classList.add('input-group', 'mb-3');
        input.classList.add('form-control');
        input.placeholder = 'Введите название нового дела';
        buttonWrapper.classList.add('input-group-append');
        button.classList.add('btn', 'btn-primary');
        button.textContent = 'Добавить дело';
        button.disabled = 'true';

        buttonWrapper.append(button);
        form.append(input);
        form.append(buttonWrapper);

        return {
            form,
            input,
            button,
        };
    }
    
    //создаем и возвращаем список элементов
    function createTodoList() {
        let list = document.createElement('li');
        list.classList.add('list-group');
        return list;
    }

    function createTodoItem(name) {
        
        let item = document.createElement('li');
        //кнопки помещаем в элемент, который красиво покажет их в одной группе
        let buttonGroup = document.createElement('div');
        let doneButton = document.createElement('button');
        let deleteButton = document.createElement('button');
        let textContentGroup = document.createElement('div');

        //устанавливаем тили для элемента списка, а также для размещения кнопкок в его правой части с помощью Flex
        item.classList.add('list-group-item', 'd-flex', 'justify-content-between', 'align-items-center');
        textContentGroup.textContent = name;

        buttonGroup.classList.add('btn-group', 'btn-group-sm');
        doneButton.classList.add('btn', 'btn-success');
        doneButton.textContent = 'Готово';
        deleteButton.classList.add('btn', 'btn-danger');
        deleteButton.textContent = 'Удалить';
        textContentGroup.classList.add('textContentGroup');

        //вкладываем кнопки в отдельный элемент, чтобы они объеденились в один блок
        buttonGroup.append(doneButton);
        buttonGroup.append(deleteButton);
        item.append(textContentGroup);
        item.append(buttonGroup);

        //прилоожению нужен доступ к самому элменту и кнопкам, чтобы отрабатывать события нажатия
        return {
            item,
            doneButton,
            deleteButton,
            textContentGroup,
        };

    }

   

    let storageItem = [];  //создал массив в который напихаю объектов, из которых потом создам дела при перезагрузке.
    let todoObject = {};     // создал объект в который напихаю значений name и done и потом запушу в storageItem
    function createTodoApp(container, title = "список дел", todoDefault = [],) {

    let todoAppTitle = createAppTitle(title);
    let todoItemForm = createTodoItemForm();
    let todoList = createTodoList();

    container.append(todoAppTitle);
    container.append(todoItemForm.form);
    container.append(todoList);

    
    if (todoDefault) {
        for (todos of todoDefault) {
            let todoDefaultname = todos.name;
            let todoItem = createTodoItem(todoDefaultname);
            todoList.append(todoItem.item);
            if (todos.done === true) {
                todoItem.item.classList.toggle('list-group-item-success');
            };
        };
    };
    

    //обработчик если вводят что то то кнопка активная становится.
    todoItemForm.input.addEventListener('input', function() {
        todoItemForm.button.disabled = '';
        if (!todoItemForm.input.value) {
            todoItemForm.button.disabled = 'true';
        }
    });

    //браузер создает событие submit на форме по нажатию Enter или на кнопку создания дела
    todoItemForm.form.addEventListener('submit', function(e) {
        //эта строчка необходима, чтобы предотвратить стандартное действие браузера
        //в данном случае мы не хотим, чтобы страница перезагружалась при отправке формы
        e.preventDefault();

        //игнорируем создание элемента, если пользователь ничего не ввел в поле
        if (!todoItemForm.input.value) {
            return;
        }
        
        //локальное сохранение
        let nameOfTodo = todoItemForm.input.value;    //имя в переменной, гуд
        //функция которая делает объект
        function createTodoObject(name) {
            todoObject = {
                name: name,
                done: false,
            };

        }
        // вызываем функцию и пушим полученное значение в массив
        createTodoObject(nameOfTodo);
        storageItem.push(todoObject);
        console.log(storageItem);
        


        let todoItem = createTodoItem(todoItemForm.input.value);
        localStorage.clear();
        localStorage.setItem('storageItem', JSON.stringify(storageItem));

       

        //добавлем обработчики на кнопки
        todoItem.doneButton.addEventListener('click', function() {
            todoItem.item.classList.toggle('list-group-item-success');
            let nameOfDone = todoItem.textContentGroup.textContent;   //имя нажатой в переменной, гуд
            //при клики нужно запустить функцию которая сравнит, есть ли стиль подсветки зеленой у имени, если есть, то нужно добавить в переменную значение

            updateDoneStorage();
            function updateDoneStorage() {
              for (storageValue of storageItem) {
                if (storageValue.name === nameOfDone) {
                    storageValue.done = !storageValue.done;
                    localStorage.clear();
                    localStorage.setItem('storageItem', JSON.stringify(storageItem));
                }
              }
            }
        });

        todoItem.deleteButton.addEventListener('click', function() {
            if (confirm('вы уверены?')) {
                todoItem.item.remove();
                let nameOfDelete = todoItem.textContentGroup.textContent;  // имя удаленной в переменной, гуд

                deleteObjInStorage(nameOfDelete);
                function deleteObjInStorage(nameDelete) {
                    let filteredStorage = storageItem.filter((item) => item.name !== nameDelete);
                    storageItem.length = 0;
                    storageItem = filteredStorage;
                    localStorage.clear();
                    localStorage.setItem('storageItem', JSON.stringify(storageItem));
                };
            }
        });   

        //создаем и добавляем в список новое дело с названием из поля для ввода
        todoList.append(todoItem.item);

        //               локальное хранилище 
        

        //Обнуляем значение в поле, чтобы не пришлось стирать его вручную
        todoItemForm.input.value = '';
        todoItemForm.button.disabled = 'true';
       });
    };

        window.createTodoApp = createTodoApp;
})();

Ответы (1 шт):

Автор решения: c_k_rim

Вот очень переделанный файл todo-app.js Если будут по нему вопросы - пиши, всё постараюсь объяснить. Если коротко, в чём была твоя ошибка - ты когда "доставал" ил localStorage сохранённые ранее "дела" - не "вешал" на кнопки этих "дел" обработчик события. Сравнить файлы можешь с помощью какого-нибудь сервиса, типа https://www.diffchecker.com/ Целом - всё у тебя получается отлично, ты большой молодец

    //создаем и возвращаем заголовок приложения
    const createAppTitle = title => {
        const appTitle = document.createElement('h2');
        appTitle.innerHTML = title;
        return appTitle;
    };

    //создаем и возвращаем форму для создания дела
    const createTodoItemForm = () => {
        const form = document.createElement('form');
        const input = document.createElement('input');
        const buttonWrapper = document.createElement('div');
        const button = document.createElement('button');

        form.classList.add('input-group', 'mb-3');

        input.classList.add('form-control');
        input.placeholder = 'Введите название нового дела';

        buttonWrapper.classList.add('input-group-append');
        button.classList.add('btn', 'btn-primary');
        button.textContent = 'Добавить дело';
        button.disabled = true;

        buttonWrapper.append(button);
        form.append(input);
        form.append(buttonWrapper);

        return {
            form,
            input,
            button,
        }
    };

    //создаем и возвращаем список элементов
    const createTodoList = () => {
        const list = document.createElement('li');
        list.classList.add('list-group');
        return list;
    };

    const createTodoItem = name => {

        const item = document.createElement('li');
        //кнопки помещаем в элемент, который красиво покажет их в одной группе
        const buttonGroup = document.createElement('div');
        const doneButton = document.createElement('button');
        const deleteButton = document.createElement('button');
        const textContentGroup = document.createElement('div');

        //устанавливаем стили для элемента списка, а также для размещения кнопок в его правой части с помощью Flex
        item.classList.add('list-group-item', 'd-flex', 'justify-content-between', 'align-items-center');
        textContentGroup.textContent = name;

        buttonGroup.classList.add('btn-group', 'btn-group-sm');
        doneButton.classList.add('btn', 'btn-success');
        doneButton.textContent = 'Готово';
        deleteButton.classList.add('btn', 'btn-danger');
        deleteButton.textContent = 'Удалить';
        textContentGroup.classList.add('textContentGroup');

        //вкладываем кнопки в отдельный элемент, чтобы они объединились в один блок
        buttonGroup.append(doneButton);
        buttonGroup.append(deleteButton);
        item.append(textContentGroup);
        item.append(buttonGroup);


        //приложению нужен доступ к самому элементу и кнопкам, чтобы отрабатывать события нажатия
        const todoItem = {
            item,
            doneButton,
            deleteButton,
            textContentGroup,
        };

        bindEventForTodoItem(todoItem);

        return todoItem
    };



    const storageItem = [];  //создал массив в который напихаю объектов, из которых потом создам дела при перезагрузке.

    const bindEventForTodoItem = (todoItem) => {

        todoItem.doneButton.addEventListener('click', () => {
            todoItem.item.classList.toggle('list-group-item-success');
            const nameOfDone = todoItem.textContentGroup.textContent;   //имя нажатой в переменной, гуд
            //при клики нужно запустить функцию которая сравнит, есть ли стиль подсветки зеленой у имени, если есть, то нужно добавить в переменную значение

            for (const storageValue of storageItem) {
                if (storageValue.name === nameOfDone) {
                    storageValue.done = !storageValue.done;
                    localStorage.clear();
                    localStorage.setItem('storageItem', JSON.stringify(storageItem));
                }
            }
        });

        todoItem.deleteButton.addEventListener('click', () => {
            if (confirm('вы уверены?')) {
                todoItem.item.remove();
                const nameOfDelete = todoItem.textContentGroup.textContent;  // имя удаленной в переменной, гуд

                const removedIndex = storageItem.findIndex(item => item.name === nameOfDelete);
                storageItem.splice(removedIndex, 1);
                localStorage.clear();
                localStorage.setItem('storageItem', JSON.stringify(storageItem));
            }
        });
    }

    window.createTodoApp = (container, title = "список дел", todoDefault = []) => {

        const todoAppTitle = createAppTitle(title);
        const todoItemForm = createTodoItemForm();
        const todoList = createTodoList();

        container.append(todoAppTitle);
        container.append(todoItemForm.form);
        container.append(todoList);


        if (todoDefault) {
            for (const {name, done} of todoDefault) {
                storageItem.push({name, done});
                const todoItem = createTodoItem(name);
                todoList.append(todoItem.item);
                if (done) {
                    todoItem.item.classList.toggle('list-group-item-success');
                }
            }
        }

        //обработчик если вводят что то то кнопка активная становится.
        todoItemForm.input.addEventListener('input', () => todoItemForm.button.disabled = !todoItemForm.input.value ? true : '');

        //браузер создает событие submit на форме по нажатию Enter или на кнопку создания дела
        todoItemForm.form.addEventListener('submit', e => {
            //эта строчка необходима, чтобы предотвратить стандартное действие браузера
            //в данном случае мы не хотим, чтобы страница перезагружалась при отправке формы
            e.preventDefault();

            //игнорируем создание элемента, если пользователь ничего не ввел в поле
            if (!todoItemForm.input.value) {
                return;
            }

            storageItem.push({
                name : todoItemForm.input.value,
                done: false,
            });


            const todoItem = createTodoItem(todoItemForm.input.value);
            localStorage.clear();
            localStorage.setItem('storageItem', JSON.stringify(storageItem));

            //создаем и добавляем в список новое дело с названием из поля для ввода
            todoList.append(todoItem.item);

            //Обнуляем значение в поле, чтобы не пришлось стирать его вручную
            todoItemForm.input.value = '';
            todoItemForm.button.disabled = true;
        });
    };
→ Ссылка