Доброго времени суток, друзья!
В данной статье мы рассмотрим парочку примеров использования Web Storage API или объекта «Storage».
Что конкретно мы будем делать?
- Научимся запоминать время воспроизведения видео.
- Поработаем с формой входа на страницу.
- Напишем логику списка задач (todo list).
- Схематично набросаем корзину для товаров.
Итак, поехали.
Краткий обзор
Объект «Storage» используется для хранения данных на стороне клиента и в этом смысле выступает альтернативой cookies. Преимущество Storage состоит в размере хранилища (от 5 Мб, зависит от браузера, при превышении лимита выбрасывается ошибка «QUOTA_EXCEEDED_ERR») и отсутствии необходимости обращаться к серверу. Существенный недостаток — безопасность: стоит вредоносному скрипту получить доступ к странице, и пиши пропало. Поэтому крайне не рекомендуется хранить в Storage конфиденциальную информацию.
Справедливости ради стоит отметить, что на сегодняшний день существуют более продвинутые решения для хранения данных на стороне клиента — это IndexedBD, Service Workers + Cache API и др.
О сервис-воркерах можно почитать здесь.
Web Storage API включает в себя localStorage и sessionStorage. Разница между ними состоит во времени хранения данных. В localStorage данные хранятся постоянно до их «явного» удаления (ни перезагрузка страницы, ни ее закрытие не приводят к удалению данных). Время хранения данных в sessionStorage, как следует из названия, ограничено сессией браузера. Поскольку sessionStorage на практике почти не используется, мы будет рассматривать только localStorage.
Что необходимо знать о localStorage?
- При использовании localStorage создается представление объекта «Storage».
- Данные в localStorage хранятся в виде пар ключ/значение.
- Данные хранятся в виде строк.
- Данные не сортируются, что иногда приводит к их перемешиванию (в чем мы убедимся на примере списка задач).
- При включении в браузере режима инкогнито или приватного режима использование localStorage может стать невозможным (зависит от браузера).
localStorage обладает следующими методами:
- Storage.key(n) — имя ключа с индексом n
- Storage.getItem() — получить элемент
- Storage.setItem() — записать элемент
- Storage.removeItem() — удалить элемент
- Storage.clear() — очистить хранилище
- Storage.length — длина хранилища (количество элементов — пар ключ/значение)
В спецификации это выглядит так:
interface Storage {
readonly attribute unsigned long length;
DOMString? key(unsigned long index);
getter DOMString? getItem(DOMString key);
setter void setItem(DOMString key, DOMString value);
deleter void removeItem(DOMString key);
void clear();
};
Данные в хранилище записываются одним из следующих способов:
localStorage.color = 'deepskyblue'
localStorage[color] = 'deepskyblue'
localStorage.setItem('color', 'deepskyblue') // рекомендуется использовать этот способ
Получить данные можно так:
localStorage.getItem('color')
localStorage['color']
Как перебрать ключи хранилища и получить значения?
// способ 1
for (let i = 0; i < localStorage.length; i++) {
let key = localStorage.key(i)
console.log(`${key}: ${localStorage.getItem(key)}`)
}
// способ 2
let keys = Object.keys(localStorage)
for (let key of keys) {
console.log(`${key}: ${localStorage.getItem(key)}`)
}
Как мы отмечали выше, данные в хранилище имеют строковый формат, поэтому с записью объектов возникают некоторые трудности, которые легко решаются с помощью тандема JSON.stringify() — JSON.parse():
localStorage.user = {
name: 'Harry'
}
console.dir(localStorage.user) // [object Object]
localStorage.user = JSON.stringify({
name: 'Harry'
})
let user = JSON.parse(localStorage.user)
console.dir(user.name) // Harry
Для взаимодействием с localStorage существует специальное событие — storage (onstorage), которое возникает при записи/удалении данных. Оно имеет следующие свойства:
- key — ключ
- oldValue — старое значение
- newValue — новое значение
- url — адрес хранилища
- storageArea — объект, в котором произошло изменение
В спецификации это выглядит так:
[Constructor(DOMString type, optional StorageEventInit eventInitDict)]
interface StorageEvent : Event {
readonly attribute DOMString? key;
readonly attribute DOMString? oldValue;
readonly attribute DOMString? newValue;
readonly attribute DOMString url;
readonly attribute Storage? storageArea;
};
dictionary StorageEventInit : EventInit {
DOMString? key;
DOMString? oldValue;
DOMString? newValue;
DOMString url;
Storage? storageArea;
};
Допускает ли localStorage прототипирование?
Storage.prototype.removeItems = function() {
for (item in arguments) {
this.removeItem(arguments[item])
}
}
localStorage.setItem('first', 'some value')
localStorage.setItem('second', 'some value')
localStorage.removeItems('first', 'second')
console.log(localStorage.length) // 0
Как проверить наличие данных в localStorage?
// способ 1
localStorage.setItem('name', 'Harry')
function isExist(name) {
return (!!localStorage[name])
}
isExist('name') // true
// способ 2
function isItemExist(name) {
return (name in localStorage)
}
isItemExist('name') // true
В браузере localStorage можно найти здесь:
Довольно слов, пора переходить к делу.
Примеры использования
Запоминаем время воспроизведения видео
window.onload = () => {
// находим элемент <video>
let video = document.querySelector('video')
// если localStorage содержит значение currentTime (текущее время), присваиваем это значение video.currentTime
if(localStorage.currentTime) {
video.currentTime = localStorage.currentTime
}
// при каждом изменении video.currentTime, записываем его значение в localStorage.currentTime
video.addEventListener('timeupdate', () => localStorage.currentTime = video.currentTime)
}
Результат выглядит так:
Запускаем видео и останавливаем воспроизведение на третьей секунде, например. Время, на котором мы остановились, хранится в localStorage. Чтобы в этом убедиться, перезагружаем или закрываем/открываем страницу. Видим, что текущее время воспроизведения видео остается прежним.
Работаем с формой для входа
Разметка выглядит так:
<form>
Login: <input type="text">
Password: <input type="text">
<input type="submit">
</form>
У нас имеется форма и три «инпута». Для пароля мы используем <input type = «text»>, поскольку если использовать правильный тип (password), Chrome будет пытаться сохранять введенные данные, что помешает нам реализовать собственный функционал.
JavaScript:
// находим форму и инпуты для ввода логина и пароля
let form = document.querySelector('form')
let login = document.querySelector('input')
let password = document.querySelector('input + input')
// если localStorage не пустой
// получаем из него необходимые данные
// и присваиваем их инпутам
if (localStorage.length != 0) {
login.value = localStorage.login
password.value = localStorage.password
}
// вешаем на форму обработчик события "submit"
form.addEventListener('submit', () => {
// записываем введенные пользователем данные в localStorage
localStorage.login = login.value
localStorage.password = password.value
// если пользователем введены hello и world в качестве логина и пароля, соответственно
// используем древний метод для вывода сообщения "welcome" на страницу
if (login.value == 'hello' && password.value == 'world') {
document.write('welcome')
}
})
Мы не «валидируем» форму, потому что наша цель — изучить возможности localStorage. Отсутствие валидации, в частности, позволяет записывать пустые строки в качестве логина и пароля.
Результат выглядит так:
Вводим волшебные слова.
Данные записываются в localStorage, а на страницу выводится приветствие.
Пишем логику списка задач
Разметка выглядит так:
<input type="text"><button class="add">add task</button><button class="clear">clear storage</button>
<ul></ul>
У нас имеется «инпут» для ввода задачи, кнопка для добавления задачи в список, кнопка для очистки списка и хранилища и контейнер для списка.
JavaScript:
// находим инпут и фокусируемся на нем
let input = document.querySelector('input')
input.focus()
// находим кнопку для добавления задачи в список
let addButton = document.querySelector('.add')
// находим контейнер для списка
let list = document.querySelector('ul')
// если в localStorage имеются данные
if (localStorage.length != 0) {
// цикл по количеству пар ключ/значение
for (let i = 0; i < localStorage.length; i++) {
let key = localStorage.key(i)
// получаем шаблон - элемент списка
let template = `${localStorage.getItem(key)}`
// помещаем задачу в список
list.insertAdjacentHTML('afterbegin', template)
}
// находим все кнопки с классом "close" - галочки для выполненных задач
document.querySelectorAll('.close').forEach(b => {
// для каждой кнопки
b.addEventListener('click', e => {
// получаем родительский элемент "li"
let item = e.target.parentElement
// удаляем задачу из списка
list.removeChild(item)
// удаляем данные из localStorage
localStorage.removeItem(`${item.dataset.id}`)
})
})
}
// возможность добавления задачи в список при нажатии клавиши "Enter"
window.addEventListener('keydown', e => {
if (e.keyCode == 13) addButton.click()
})
// вешаем на кнопку для добавления задачи в список обработчик события "клик"
addButton.addEventListener('click', () => {
// получаем строку - задачу
let text = input.value
// формируем шаблон, запись и идентификация значений по ключам осуществляется через атрибут "data-id"
let template = `<li data-id="${++localStorage.length}"><button class="close">V</button><time>${new Date().toLocaleDateString()}</time> <p>${text}</p></li>`
// добавляем шаблон - задачу в список
list.insertAdjacentHTML('afterbegin', template)
// записываем данные в localStorage
localStorage.setItem(`${++localStorage.length}`, template)
// сбрасываем значение инпута
input.value = ''
// вешаем на кнопку для удаления задачи из списка обработчик события "клик"
document.querySelector('.close').addEventListener('click', e => {
// получаем элемент списка - родительский элемент кнопки
let item = e.target.parentElement
// удаляем задачу из списка
list.removeChild(item)
// удаляем данные из localStorage
localStorage.removeItem(`${item.dataset.id}`)
})
})
// вешаем на кнопку для очистки обработчик события "клик"
document.querySelector('.clear').onclick = () => {
// очищаем хранилище
localStorage.clear()
// удаляем задачи из списка
document.querySelectorAll('li').forEach(item => list.removeChild(item))
// фокусируемся на инпуте
input.focus()
}
Результат выглядит так:
Задачи, добавляемые в список, сохраняются в localStorage в виде готовой разметки. При перезагрузке страницы список формируется из данных хранилища (имеет место перемешивание, о котором упоминалось выше).
Удаление задачи из списка посредством нажатия зеленой галочки приводит к удалению соответствующей пары ключ/значение из хранилища.
Схема корзины для товаров
Мы не преследуем цель создать полнофункциональную корзину, поэтому код будет написан «в старом стиле».
Разметка одного товара выглядит так:
<div class="item">
<h3 class="title">Item1</h3>
<img src="http://placeimg.com/150/200/tech" alt="#">
<p>Price: <span class="price">1000</span></p>
<button class="add" data-id="1">Buy</button>
</div>
У нас имеется контейнер для товара, наименование, изображение и цена товара, а также кнопка для добавления товара в корзину.
Также у нас имеется контейнер для кнопок отображения содержимого корзины и очистки корзины и хранилища и контейнер для корзины.
<div class="buttons">
<button id="open">Cart</button>
<button id="clear">Clear</button>
</div>
<div id="content"></div>
JavaScript:
// находим товары и корзину
let itemBox = document.querySelectorAll('.item'),
cart = document.getElementById('content');
// получаем данные из localStorage
function getCartData() {
return JSON.parse(localStorage.getItem('cart'));
}
// записываем данные в хранилище
function setCartData(o) {
localStorage.setItem('cart', JSON.stringify(o));
}
// добавление товара в корзину
function addToCart() {
// блокируем кнопку на время работы с корзиной
this.disabled = true;
// получаем данные из корзины или создаем новый объект, если данные отсутствуют
let cartData = getCartData() || {},
// родительский элемент кнопки "Buy"
parentBox = this.parentNode,
// id товара
itemId = this.getAttribute('data-id'),
// название товара
itemTitle = parentBox.querySelector('.title').innerHTML,
// стоимость товара
itemPrice = parentBox.querySelector('.price').innerHTML;
// +1 к товару
if (cartData.hasOwnProperty(itemId)) {
cartData[itemId][2] += 1;
} else {
// + товар
cartData[itemId] = [itemTitle, itemPrice, 1];
}
// обновляем данные в localStorage
if (!setCartData(cartData)) {
// снимаем блокировку кнопки
this.disabled = false;
}
}
// устанавливаем обработчик события "клик" на каждую кнопку "Buy"
for (let i = 0; i < itemBox.length; i++) {
itemBox[i].querySelector('.add').addEventListener('click', addToCart)
}
// содержимое корзины
function openCart() {
// получаем данные из хранилища
let cartData = getCartData(),
totalItems = '',
totalGoods = 0,
totalPrice = 0;
// формируем данные для вывода
if (cartData !== null) {
totalItems = '<table><tr><th>Name</th><th>Price</th><th>Amount</th></tr>';
for (let items in cartData) {
totalItems += '<tr>';
for (let i = 0; i < cartData[items].length; i++) {
totalItems += '<td>' + cartData[items][i] + '</td>';
}
totalItems += '</tr>';
totalGoods += cartData[items][2];
totalPrice += cartData[items][1] * cartData[items][2];
}
totalItems += '</table>';
cart.innerHTML = totalItems;
cart.append(document.createElement('p').innerHTML = 'Goods: ' + totalGoods + '. Price: ' + totalPrice);
} else {
// если в корзине пусто
cart.innerHTML = 'empty';
}
}
// открываем корзину
document.getElementById('open').addEventListener('click', openCart);
// очищаем корзину
document.getElementById('clear').addEventListener('click', () => {
localStorage.removeItem('cart');
cart.innerHTML = 'сleared';
});
Результат выглядит так:
Выбранные товары записываются в хранилище в виде одной пары ключ/значение.
При нажатии кнопки «Cart» данные из localStorage выводятся в таблицу, подсчитывается общее количество товаров и их стоимость.
Благодарю за внимание.
Специально для сайта ITWORLD.UZ. Новость взята с сайта Хабр