Using localStorage
Using localStorage
In creating a simple web application, it is useful to be able to store data that the application creates. If you don’t want to actually use a database, you could store the data inside localStorage. localStorage is available on with any modern web browser on commonly used operating systems. This is not a robust way of storing the data, but it could be used if you want a very simple storage mechanism.
Pros and Cons of using localStorage
Pros
-
Available with any modern browser on most operating systems.
-
Can be used to store authentication tokens that can control access to different pages of an application. This is useful for applications that store the data in an external database. Instead of going through the overhead of using that external database to provide access to tools or pages, you can store JSON Web Tokens (JWT) in localStorage to control access locally.
-
Since localStorage is client-based, there is no need to store localStorage items on the cloud.
Cons
-
Limited to total size of around 5 MB, depending on the browser.
-
Limited to storing simple strings. Objects must be stringified to be used with localStorage.
-
Providing a client with ways to store their own data in a portable way, is mainly limited to JSON files. That is, you can provide for the client to store data from localStorage by writing the data to a JSON file. You then need to provide a mechanism to read in that JSON file to make the data available on another computer/browser.
Simple example of using localStorage
Suppose you wanted to make a simple web application that allows the user to enter items into a list. Assume that you want that list to be maintained even if the user leaves that page, and comes back to the page (using the same computer and the same browser). You could maintain that list using localStorage. Here is a first version of such a program:
<!DOCTYPE html>
<html>
<head>
<script>
addEventListener('DOMContentLoaded', init);
function init() {
console.log('init started');
}
</script>
</head>
<body>
<h1>Items</h1>
</body>
</html>
This first version does not do much. The HTML markup in the body just shows an <h1> element with the contents Items. The <script> element in the head section just makes it so that the init() function is called after all the HTML markup has been rendered. Here is what the page looks like with the DevTools console open. Note that are serving out the page using the http-server node tool:
Here is a screen shot showing the page in a browser with the first version of the code:
It is a good idea to start with something simple like this, so that you are sure that everything is started correctly.
Adding markup for user input
Let’s add some HTML markup to the body to allow user input. Here is the new version of the code:
Adding markup for user input Let’s add some HTML markup to the body to allow user input. Here is the new version of the code:
<!DOCTYPE html>
<html>
<head>
<script>
addEventListener('DOMContentLoaded', init);
function init() {
console.log('init started');
}
</script>
</head>
<body>
<h1>Items</h1>
Enter item:
<input type="text">
<button id="add_button">Add</button>
</body>
</html>
The new lines are lines 14-16. Line 14 is just text element that adds a label for our input text box. Line 15 defines the <input> element for our input text box. The two elements are non-breaking-space elements that are used to add spaces between HTML elements. Line 16 adds a <button> element with an id="add_button" attribute. This id will be used later to associate a function with clicking on that button.
Although this page is not functional at all yet, this is what it would look like in the browser:
Adding JavaScript to enable the button
Now, let’s add some JavaScript to enable the button. At the same time, let’s add an unordered list (<ul>) where we can start to add our list items. Here is the new version of the code:
<!DOCTYPE html>
<html>
<head>
<script>
addEventListener('DOMContentLoaded', init);
function handleAdd() {
const item_list = document.getElementById('item_list');
const item_box = document.getElementById('item_box');
let contents = item_box.value.trim();
let li = document.createElement("li"); // create <li> element
li.appendChild(document.createTextNode(contents));
item_list.appendChild(li);
}
function init() {
console.log('init started');
const add_button = document.getElementById('add_button');
add_button.addEventListener('click', handleAdd);
}
</script>
</head>
<body>
<h1>Items</h1>
Enter item:
<input type="text" id="item_box">
<button id="add_button">Add</button>
<ul id="item_list"></ul>
</body>
</html>
Line 26 has been modified. Lines 7-14, lines 18-19 and line 28 are new. Line 26 has been modified to add an id="item_box" attribute to the input text box. This should have been added earlier. This attribute is needed so that we can read the value from this text input box.
Line 28 adds a <ul> element with an id="item_list" attribute. The id is needed so that we can append items to this unordered list.
Line 18 obtains a reference to the add button and line 19 associates the handleAdd() function with clicking on that button.
Lines 7-14 define the handleAdd() function. Line 8 gets a reference to the <ul> element that we will append our items to. LIne 9 gets a reference to the text input box that we will read the user input from. Line 10, gets the contents from the user input box. Line 11 creates an empty <li> element. Line 12 puts the text node created from the user input inside the previously empty <li> element. Finally, line 13 appends that <li> element to the <ul> element.
Here is a screen shot after a few items have been added to the <ul> element:
Storing the item data in localStorage
Let’s store the item data inside of localStorage. We will create a global variable called data that we will use to move data in and out of localStorage. In addition, we will use data to create the <ul> element. Here is the next version of the code:
<!DOCTYPE html>
<html>
<head>
<script>
addEventListener('DOMContentLoaded', init);
data = {};
function handleAdd() {
//const item_list = document.getElementById('item_list');
data["last_number"] += 1;
let current_number = data["last_number"];
let current_item = "";
if (current_number < 10) {
current_item = "item" + "0" + current_number;
}
else {
current_item = "item" + current_number;
}
const item_box = document.getElementById('item_box');
let contents = item_box.value.trim();
data[current_item] = contents;
let dataStr = JSON.stringify(data);
localStorage.setItem("mydata", dataStr);
putDataIntoList();
//let li = document.createElement("li"); // create <li> element
//li.appendChild(document.createTextNode(contents));
//item_list.appendChild(li);
}
function putDataIntoList() {
const item_list = document.getElementById('item_list');
let dataStr = localStorage.getItem("mydata");
data = JSON.parse(dataStr);
let sorted_keys = Object.keys(data).sort();
sorted_keys = sorted_keys.slice(0, sorted_keys.length-1);
for (let key of sorted_keys) {
let li = document.createElement('li');
let contents = document.createTextNode(data[key]);
li.appendChild(contents);
item_list.appendChild(li);
}
}
function init() {
console.log('init started');
const add_button = document.getElementById('add_button');
add_button.addEventListener('click', handleAdd);
if (localStorage.getItem("mydata") !== null) {
putDataIntoList();
}
else {
data["last_number"] = 0;
}
}
</script>
</head>
<body>
<h1>Items</h1>
Enter item:
<input type="text" id="item_box">
<button id="add_button">Add</button>
<ul id="item_list"></ul>
</body>
</html>
The modified lines are lines 10 and lines 26-28. These are commented out because we no longer put the items into the <ul> element in the handleAdd() function. In the next version, we will just remove those lines altogether.
Line 7 creates a global variable called data. This will be used to hold the value of the items entered by the user.
Lines 11-19 do the setup for storing the user entered items into data. Line 11 increments the value of data["last_number"] (the "last_number" key) to be one more than it was previously.
Lines 12-19 are used to set up the variable current_item. This will be the key that we store the current user-entered value. The selection statement on lines 14-19 is used to make the current_item go from "item01", "item02", …, "item10", "item11". That is the number following "item" will use two digits. This will allow us to sort and keep the items in the same order as they were entered.
Line 22 gets the user-entered value and stores this inside of data using the key (current_item) generated above. Line 23 stringifies data, so that it can be stored in localStorage. Line 24 stores the data in localStorage using a key of "mydata".
Finally on line 25, the putDataIntoList() function is called to put the items into the <ul> element.
Lines 31-43 define the putDataIntoList() function. This funcion is used to place the stored items into the <ul> elemented with an id="item_list" attribute. Line 33 retrieves the data from localStorage into a string, and line 34 converts this string back into the original data object.
Line 35 obtains an array of all the keys (properties) of the data object and sorts those keys. This is why we used the key naming scheme inside the handleAdd() function. The last key will be "last_number", so line 36 slices that last key off from the end of the array. This allows the for loop on lines 37-42 to iterate over all the user supplied values.
Line 38 creates an empty <li> element. Line 39 creates a text node from the data key (property). Line 40 appends that text node to the <li> element, and line 41 appends the <li> element to the <ul> element.
Lines 49-54 just check to see if the data exists inside of localStorage. It the data exists, line 50 calls the putDataIntoList() function to populate the <ul> element. If the data is not in localStorage then line 53 initializes data["last_number"].
Fix problem with <ul> holding all updates
If you run the program right now, you will see that there is a problem. This is a screen shot after the user has entered "Jane", "John" and then "Bob":
As you can see, the <ul> element keeps all the items placed inside it. We actually want to update the <ul> element so it only shows the items inside of data. To do this, we need to remove the items stored inside of the <ul> element each time we display the data. Here is a new version of the program that does this:
<!DOCTYPE html>
<html>
<head>
<script>
addEventListener('DOMContentLoaded', init);
data = {};
function remove_children(element) {
while (element.children.length != 0) {
element.removeChild(element.children[0]);
}
}
function handleAdd() {
data["last_number"] += 1;
let current_number = data["last_number"];
let current_item = "";
if (current_number < 10) {
current_item = "item" + "0" + current_number;
}
else {
current_item = "item" + current_number;
}
const item_box = document.getElementById('item_box');
let contents = item_box.value.trim();
data[current_item] = contents;
let dataStr = JSON.stringify(data);
localStorage.setItem("mydata", dataStr);
putDataIntoList();
}
function putDataIntoList() {
const item_list = document.getElementById('item_list');
remove_children(item_list);
let dataStr = localStorage.getItem("mydata");
data = JSON.parse(dataStr);
let sorted_keys = Object.keys(data).sort();
sorted_keys = sorted_keys.slice(0, sorted_keys.length-1);
for (let key of sorted_keys) {
let li = document.createElement('li');
let contents = document.createTextNode(data[key]);
li.appendChild(contents);
item_list.appendChild(li);
}
}
function init() {
console.log('init started');
const add_button = document.getElementById('add_button');
add_button.addEventListener('click', handleAdd);
if (localStorage.getItem("mydata") !== null) {
putDataIntoList();
}
else {
data["last_number"] = 0;
}
}
</script>
</head>
<body>
<h1>Items</h1>
Enter item:
<input type="text" id="item_box">
<button id="add_button">Add</button>
<ul id="item_list"></ul>
</body>
</html>
The new lines are lines 9-13 and line 35. Lines 9-13 define the remove_children() function. This will remove all children from an element passed to this function. Line 35 calls the remove_children() function so that all the previously stored <li> elements are removed before placing all the current items in the <ul> element.
Now the list is displayed properly:
Clearing localStorage
If you have the DevTools console open, you can clear localStorage by typing:
localStorage.clear();
data["last_number"] = 0;
Refreshing the browser allows you to start the list all over. But, this is not that convenient, as your user is not likely to have the DevTools console open. So, let’s revise the code to put a "Clear List" button that will do this for the user.
<!DOCTYPE html>
<html>
<head>
<script>
addEventListener('DOMContentLoaded', init);
data = {};
function remove_children(element) {
while (element.children.length != 0) {
element.removeChild(element.children[0]);
}
}
function handleClear() {
localStorage.clear();
data["last_number"] = 0;
document.location.reload();
}
function handleAdd() {
data["last_number"] += 1;
let current_number = data["last_number"];
let current_item = "";
if (current_number < 10) {
current_item = "item" + "0" + current_number;
}
else {
current_item = "item" + current_number;
}
const item_box = document.getElementById('item_box');
let contents = item_box.value.trim();
data[current_item] = contents;
let dataStr = JSON.stringify(data);
localStorage.setItem("mydata", dataStr);
putDataIntoList();
}
function putDataIntoList() {
const item_list = document.getElementById('item_list');
remove_children(item_list);
let dataStr = localStorage.getItem("mydata");
data = JSON.parse(dataStr);
let sorted_keys = Object.keys(data).sort();
sorted_keys = sorted_keys.slice(0, sorted_keys.length-1);
for (let key of sorted_keys) {
let li = document.createElement('li');
let contents = document.createTextNode(data[key]);
li.appendChild(contents);
item_list.appendChild(li);
}
}
function init() {
console.log('init started');
const add_button = document.getElementById('add_button');
add_button.addEventListener('click', handleAdd);
const clear_button = document.getElementById('clear_button');
clear_button.addEventListener('click', handleClear);
if (localStorage.getItem("mydata") !== null) {
putDataIntoList();
}
else {
data["last_number"] = 0;
}
}
</script>
</head>
<body>
<h1>Items</h1>
Enter item:
<input type="text" id="item_box">
<button id="add_button">Add</button>
<button id="clear_button">Clear List</button><br>
<ul id="item_list"></ul>
</body>
</html>
The new lines are lines 15-19, lines 58-59 and line 74. Line 74 adds the markup for the clear button. Lines 57 gets a reference to the clear button and line 58 associates the handleClear() function with clicking on that button. Lines 15-19 define the handleClear() function. Line 16 clears localStorage. Line 17 resets the data["last_number"] value and line 18 reloads the page.
Summary
If you close the browser and come back to the same page days later, you will still get back the last version of the list of items providing that localStorage has not been cleared for that browser on that computer. So, this can give the user an environment where there last entries are preserved.
Using localStorage is convenient, as this is automatically accessible in just about any browser.
However, localStorage can be cleared and your data can be easily overwritten by some other program. In addition, the data is not portable.
In addition, if you think about allowing the editing of the data, this can become complex. For example, if you allow deleting of an item, it can be tricky to maintain the names of the keys of the items. In that case, storing the data inside a database might make the data easier to manage. On the other hand, if the order of the data does not need to be preserved, and you don’t need to store a lot of data, then localStorage might be just what you want to use.
The user interface of this program could be improved so that when you click the Add button, the focus is set back on the text input box.