Web Storage API Case Studies



Good day, friends!

In this article, we’ll look at a couple of examples of using the Web Storage API or the Storage object.

What exactly are we going to do?

  • Learn to remember the video playback time.
  • We’ll work with the page login form.
  • Let's write the logic of the task list.
  • We implement chat.
  • Sketch a basket for goods.

So let's go.

Short review


The Storage object is used to store data on the client side and, in this sense, acts as an alternative to cookies. The advantage of Storage is the storage size (from 5 MB, it depends on the browser, when the limit is exceeded, the error “QUOTA_EXCEEDED_ERR” is thrown) and there is no need to access the server. A significant drawback is security: it costs a malicious script to access the page, and write is lost. Therefore, it is highly discouraged to store confidential information in Storage.

In fairness, it is worth noting that today there are more advanced solutions for storing data on the client side - this is IndexedDB, Service Workers + Cache API, etc.

You can read about service workers here .

The Web Storage API includes localStorage and sessionStorage. The difference between them is the data storage time. In localStorage, data is stored permanently until it is "explicitly" deleted (neither reloading the page nor closing it results in data deletion). The data storage time in sessionStorage, as the name implies, is limited by the browser session. Since sessionStorage is almost never used in practice, we will only consider localStorage.

What you need to know about localStorage?


  • When using localStorage, a representation of the Storage object is created.
  • Data in localStorage is stored as key / value pairs.
  • Data is stored as strings.
  • The data is not sorted, which sometimes leads to their mixing (as we will see with the example of the task list).
  • When you enable incognito or private mode in your browser, using localStorage may become impossible (browser dependent).

localStorage has the following methods:

  • Storage.key (n) - key name with index n
  • Storage.getItem () - get the item
  • Storage.setItem () - write an item
  • Storage.removeItem () - delete an item
  • Storage.clear () - clear storage
  • Storage.length - storage length (number of elements - key / value pairs)

In the specification, it looks like this:

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();
};

Data is written to the storage in one of the following ways:

localStorage.color = 'deepskyblue'
localStorage[color] = 'deepskyblue'
localStorage.setItem('color', 'deepskyblue') //    

You can get the data like this:

localStorage.getItem('color')
localStorage['color']

How to iterate over storage keys and get values?


//  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)}`)
}

As we noted above, the data in the storage has a string format, so there are some difficulties with writing objects, which can be easily solved using the JSON.stringify () - JSON.parse () tandem:

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

To interact with localStorage, there is a special event - storage (onstorage), which occurs when data is written / deleted. It has the following properties:

  • key - key
  • oldValue - old value
  • newValue - new value
  • url - storage address
  • storageArea - the object in which the change occurred

In the specification, it looks like this:

[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;
};

Does localStorage allow prototyping?


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

How to check for data in 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

The localStorage browser can be found here:



Enough words, it's time to get down to business.

Examples of using


Remember the video playback time


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)
}

The result looks like this:



Start the video and stop playback on the third second, for example. The time we left off is stored in localStorage. To verify this, reload or close / open the page. We see that the current video playback time remains the same.

Codepen

Github

We work with the login form


The markup looks like this:

<form>
    Login: <input type="text">
    Password: <input type="text">
    <input type="submit">
</form>

We have a form and three "inputs." For the password, we use <input type = "text">, because if you use the correct type (password), Chrome will try to save the entered data, which will prevent us from implementing our own functionality.

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')
    }
})

Please note that we do not "validate" the form. This, in particular, allows you to record empty lines as a username and password.

The result looks like this:



We introduce magic words.



Data is written to localStorage, and a greeting is displayed on the page.

Codepen

Github

Writing the logic of the task list


The markup looks like this:

<input type="text"><button class="add">add task</button><button class="clear">clear storage</button>

<ul></ul>

We have an “input” for entering a task, a button for adding a task to the list, a button for cleaning the list and storage, and a container for the list.

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()
}

The result looks like this:



Tasks added to the list are stored in localStorage as ready-made markup. When the page is reloaded, the list is formed from the data of the storage (there is mixing, which was mentioned above).



Removing a task from the list by clicking the green checkmark removes the corresponding key / value pair from the repository.

Codepen

Github

Chat Implementation


The markup looks like this:

<input type="text">
<button class="send">send message</button>
<button class="save">save chat</button>
<button class="clear">clear chat</button>

<div></div>

We have an input for entering a message, three buttons: for sending a message, for saving correspondence and for cleaning chat and storage, as well as a container for messages.

JavaScript:

//      
let input = document.querySelector('input')
input.focus()

//  
let sendButton = document.querySelector('.send')
let saveButton = document.querySelector('.save')
let clearButton = document.querySelector('.clear')

//  
let box = document.querySelector('div')

//      "messages"
if (localStorage.messages) {
    //  
    localStorage.messages
        .split('</p>,')
        .map(p => box.insertAdjacentHTML('beforeend', p))
}

//   
sendButton.addEventListener('click', () => {
    //   
    let text = document.querySelector('input').value
    //  
    let template = `<p><time>${new Date().toLocaleTimeString()}</time> ${text}</p>`
    //    
    box.insertAdjacentHTML('afterbegin', template)
    //   
    input.value = ''
    //    
    localStorage.message = template
})

//       "Enter"
window.addEventListener('keydown', e => {
    if (e.keyCode == 13) sendButton.click()
})

//   "storage"
window.addEventListener('storage', event => {
    //     "messages"
    //  
    if (event.key == 'messages') return
    //      null
    //     
    //     
    event.newValue == null ? clearButton.click() : box.insertAdjacentHTML('afterbegin', event.newValue)
})

//  
saveButton.addEventListener('click', () => {
    //  
    let messages = []
    //  
    document.querySelectorAll('p').forEach(p => messages.push(p.outerHTML))
    //    
    localStorage.messages = messages
})

//    
clearButton.addEventListener('click', () => {
    localStorage.clear()
    document.querySelectorAll('p').forEach(p => box.removeChild(p))
    input.focus()
})

The result looks like this: The



message being sent is written to localStorage.message. The "storage" event allows you to organize the exchange of messages between browser tabs.



When a chat is saved, all messages are written to localStorage.messages. When the page is reloaded, correspondence is formed from the recorded messages.

Codepen

Github

Shopping basket layout


We are not aiming to create a fully functional basket, so the code will be written "in the old style."

The layout of one product looks like this:

<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>

We have a container for the goods, the name, image and price of the goods, as well as a button for adding goods to the basket.

We also have a container for buttons to display the contents of the basket and empty the basket and storage and a container for the basket.

<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';
});

The result looks like this:



Selected products are recorded in the store as a single key / value pair.



When you click the Cart button, data from localStorage is displayed in a table, the total number of goods and their value are calculated.

Codepen

Github

Thank you for your attention.

All Articles