JS30. Задание 15 LocalStorage
Код: https://github.com/js3015/js3015.github.io
Задание называется LocalStorage, подсказывая, что речь пойдёт о сохранении введённых пользователем данных при перезагрузке страницы. Эту тему уже рассматривала здесь пункт "Состояния. Хранение состояний", но без конспекта вспомнить как оно реализуется не получается. Попробую вспомнить с конспектом, заодно и оценю его качество.
Получаем переменные:
var button = document.querySelector('input[type="submit"]');
var input = document.querySelector('input[type="text"]');
var list = document.querySelector(".plates");
var items = [];
Отслеживаем клики по button
Выводим в консоль значение input.value
button.addEventListener("click", function(){
console.log(input.value);
});
Здесь первая проблема. В консоль значение input.value не выводится. Зато выводится в alert. И что бы это значило?
UPD: кажется, разобралась. Проблема в форме, у которой есть событие по умолчанию, отменить которое я забыла. Форма пытается отправить данные на сервер, поэтому код работает странно или не работает вообще.
Добавим введённый в поле текст в список:
button.addEventListener("click", function(){
var l = input.value;
var li = document.createElement("li");
li.innerText = l;
list.appendChild(li);
input.value = "";
});
Работает. Ура.
Чтобы сохранить введённый текст при обновлении страницы, создаём переменную state, в которой будем сохранять текущее состояние:
var state = {
items: []
}
Теперь, когда добавляем элемент в список, будем одновременно добавлять его в массив item:
state.item.push(l);
...и сохранять объект state в хранилище localStorage
localStorage.setItem("data", JSON.stringify(state));
Код полностью:
var button = document.querySelector('input[type="submit"]');
var input = document.querySelector('input[type="text"]');
var list = document.querySelector(".plates");
var state = {
items: []
};
var data = localStorage.getItem("data");
if(data) {
state = JSON.parse(data);
}
window.onload = function() {
var loaded = sessionStorage.getItem('loaded');
if(loaded) {
update();
} else {
sessionStorage.setItem('loaded', true);
}
}
function update(){
list.innerHTML = "";
for(var i = 0; i < state.items.length; i++) {
var li = document.createElement("li");
li.innerText = state.items[i];
list.appendChild(li);
}
}
button.addEventListener("click", function(event){
event.preventDefault();
var l = input.value;
var li = document.createElement("li");
li.innerText = l;
list.appendChild(li);
input.value = "";
state.items.push(l);
localStorage.setItem("data", JSON.stringify(state));
});
... и результат его работы
Всё работает, введённые данные сохраняются, в консоли ошибок нет.
Посмотрим, как реализован код автора
Код, правда, немаленький. Попробую разобраться.
1. Находим элементы
var addItems = document.querySelector(".add-items");
var itemsList = document.querySelector(".plates");
var items = JSON.parse(localStorage.getItem("items")) || [];
addItems - это вся форма в которой
- окно для ввода заказов - input type="text"
- кнопка для добавления заказов - input type="submit"
- список, в котором сохраняется всё заказанное - ul class="plates"
То есть, мы не создаём одновременно переменную для инпута ввода заказов и переменную для кнопки добавления заказов, и объединяем их в одну переменную. Думаю, это нужно для того, чтобы не назначать отдельный обработчик каждому элементу. Мы ставим один обработчик на их общего предка - форму. Из него можно получить целевой элемент
event.target
, понять на каком именно потомке произошло событие и обработать его. Такой приём называется делегирование событий и считается одним из ключевых в веб-разработке подробнееitemsList - список, в котором сохраняется всё заказанное - ul class="plates"
items - содержание списка с заказами - JSON.parse(localStorage.getItem("items")) || [];
Здесь достаточно интересная реализация - целая функция внутри объявления переменной
Мы проверяем сохранён ли в localStorage объект items.
Если да, считываем его с помощью функции JSON.parse
Если нет, тогда содержание списка равно пустому массиву.
2. Функция добавления заказа в список
function addItem(event) {
event.preventDefault();
var text = (this.querySelector("[name=item]")).value;
var item = {
text,
done: false
};
items.push(item);
populateList(items, itemsList);
localStorage.setItem("items", JSON.stringify(items));
this.reset();
}
● event.preventDefault() - отмена события по умолчанию. так как у нас форма и кнопка в этой форме, кнопка по клику будет пытаться отправить данные. Чтобы она этого не делала, нужно отменить данное событие, указанное для формы по умолчанию
● var text = (this.querySelector("[name=item]")).value; - это такой странный способ написать input[type="text"].value; В общем, это значение инпута: пустая строка или тот текст, который в него ввели.
Кстати, чтобы инпут не отправляли пустым, в его html-коде используется атрибут required, который устанавливает поле формы обязательным для заполнения перед отправкой формы на сервер
<input type="text" name="item" placeholder="Item Name" required>
● var item = {
text,
done: false
};
Н-да. Это какой-то объект. В нём наш текст, та переменная, которую только что определили. И ключ done с значением false - done: false. Что это я не совсем понимаю, похоже на итератор - объект, значения которого можно перебирать
Пошла смотреть видео. Может получится понять что он там говорит.
С видео не очень получается, но вот что говорит mdn
Возвращаемое значение - объект с двумя свойствами:
done
(boolean)- Имеет значение
true
если iterator прошел конец итерируемой последовательности. В этом случаеvalue
опционально определяется выражением return value внутри итератора . - Имеет значение
false
если iterator имеет возможность вернуть следующее значение последовательности. Это равносильно когда свойство done не указано.
- Имеет значение
Так и есть.
Зачем было добавлять свойство done: false, которое и так уже есть по умолчанию? Вероятно, в этом есть какой-то скрытый смысл. Может дальше получится понять.
● items.push(item);
Это просто. В массив items добавляем только что введённый в поле для ввода заказа текст item
● populateList(items, itemsList);
Это мы вызываем функцию populateList с двумя аргументами items и itemsList.
● localStorage.setItem("items", JSON.stringify(items));
В localStorage добавили - setItem - массив items и сразу преобразовали его в строку - JSON.stringify(items));
● this.reset();
Функция для сброса формы.
3. Функция добавления элемента в список
function populateList(plates = [], platesList) {
platesList.innerHTML = plates.map((plate, i) => {
return `
<li>
<input type="checkbox" data-index=${i} id="item${i}" ${plate.done ? "checked" : ""} />
<label for="item${i}">${plate.text}</label>
</li>
`;
}).join("");
}
function populateList(plates = [], platesList)
Объявляем функцию populateList с двумя параметрами plates и platesList
Вот здесь пока тоже кажется не совсем удобным создавать функцию с параметрами plates и platesList, зная, что потом будем вызывать её с аргументами items и itemsList. Ну то есть сразу создать функцию populateList(items, itemsList); кажется проще и удобнее
Здесь интересный момент, в параметрах функции присваивается значение: plates = []
platesList.innerHTML = plates.map((plate, i) => {
return `
<li>
<input type="checkbox" data-index=${i} id="item${i}" ${plate.done ? "checked" : ""} />
<label for="item${i}">${plate.text}</label>
</li>
`;
}).join("");
Достаточно сложный фрагмент кода.
Попробую понять что этот код делает.
Если помнить, что platesList это на самом деле itemsList, потому что именно с таким аргументом будет вызываться данная функция, то есть список заказов, тогда вот этот фрагмент кода, это то, что мы хотим добавить в html
<li>
<input type="checkbox" data-index=${i} id="item${i}" ${plate.done ? "checked" : ""} />
<label for="item${i}">${plate.text}</label>
</li>
`
Здесь кроме пункта списка <li> видим чекбокс с условием plate.done.
Вот и стало понятно зачем понадобилось свойство done: false над которым я ломала голову. Оказывается, это условие, показывающее отмечен чекбокс галочкой или нет
Есть небольшая проблема - в демо автора чекбоксы у меня не отображаются. Хоть здесь, например (чей-то проект по курсу js30) чекбоксы присутствуют.
4. Функция для переключения свойства done и сохранения списка с заказами в localStorage
function toggleDone(e) {
if (!e.target.matches("input")) return; // skip this unless it's an input
var el = e.target;
var index = el.dataset.index;
items[index].done = !items[index].done;
localStorage.setItem("items", JSON.stringify(items));
populateList(items, itemsList);
}
● if (!e.target.matches("input")) return;
Мы проверили соответствует ли аргумент функции css-селектору input и, если нет: !e.target.matches("input") прекращаем выполнение функции.
● var el = e.target;
var index = el.dataset.index;
items[index].done = !items[index].done;
Переключаем свойство done
● localStorage.setItem("items", JSON.stringify(items));
Теперь, когда у нас есть массив элементов, мы можем сохранить его в LocalStorage, который сохранит этот список при обновлении страницы.
● populateList(items, itemsList);
Вызываем функцию populateList с двумя аргументами items и itemsList.
5. Добавляем слушателей
addItems.addEventListener("submit", addItem);
Событие submit возникает при отправке формы. Мы вызываем функцию addItem для добавления заказа в список
itemsList.addEventListener("click", toggleDone);
При клике по чекбоксу в списке срабатывает функция toggleDone изменяющая состояние чекбокса и сохраняющая его в LocalStorage
populateList(items, itemsList);
Функция, обновляющая html
Всё. Задание было насыщенным и сложным.
для самостоятельной работы автор предлагает добавить ещё три кнопки, позволяющие
- Удалить все элементы.
- Отметить все чекбоксы.
- Очистить все чекбоксы.
Кроме того, на мой взгляд, было бы хорошо выводить список блюд и при клике отправлять их в заказ. При этом можно менять изображение, там где картинка с рыбкой, на то, которое было заказано, благо сайт с картинками автор указал в коде: https://thenounproject.com/search/?q=fish&i=589236
Код:
var add = document.getElementById("add");
var del = document.getElementById("del");
var select = document.querySelector("select");
var icon = document.querySelector(".icon");
var list = document.querySelector(".plates");
var l = "apple";
var state = {
items: []
};
var data = localStorage.getItem("data");
if(data) {
state = JSON.parse(data);
}
window.onload = function() {
var loaded = sessionStorage.getItem("loaded");
if(loaded) {
update();
} else {
sessionStorage.setItem("loaded", true);
}
}
function update(){
list.innerHTML = "";
for(var i = 0; i < state.items.length; i++) {
var li = document.createElement("li");
li.innerText = state.items[i];
list.appendChild(li);
}
}
select.addEventListener("change", function(e) {
l = this.value;
changeLogo();
});
function changeLogo() {
icon.style.backgroundImage = 'url("img/' + l + '.svg")'
}
function addItem(event) {
event.preventDefault();
var li = document.createElement("li");
li.textContent = l;
list.appendChild(li);
state.items.push(l);
localStorage.setItem("data", JSON.stringify(state));
}
function delItem(event){
event.preventDefault();
localStorage.clear();
list.innerHTML = "";
state.items = [];
}
add.addEventListener("click", addItem);
del.addEventListener("click", delItem);