I’m trying to figure out how to keep “Mark as Read” as “Mark as Unread” even after refreshing the page. Vice versa too. How do I save the data with localStorage? So far, this is my code for “Mark as Read”:
<script>
function readunread() {
currentvalue = document.getElementById("readunread").value;
if(currentvalue == "Mark as Unread"){
document.getElementById("readunread").value = "Mark as Read";
} else{
document.getElementById("readunread").value = "Mark as Unread";
}
}
</script>
<style>
.button {
border: none;
color: white;
font-family: Corbel;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
background-color: black;
}
input[type=button] {
font-size: 20px;
font-family: Corbel;
text-decoration: none;
color: white;
border: none;
background: none;
cursor: pointer;
margin: 0;
padding: 0;
}
</style>
<input type = "button" value = "Mark as Read" id = "readunread" onclick = "readunread();">
I click the “Mark as Read” and it becomes “Mark as Unread.” But after refreshing the page, it goes back to “Mark as Read.” How do I avoid that?
CodePudding user response:
In your scripts you'll need to change two things:
<script>
function readunread() {
currentvalue = document.getElementById("readunread").value;
if (currentvalue == "Mark as Unread") {
document.getElementById("readunread").value = "Mark as Read";
// 1. Update the localstorage
localStorage.setItem("readunread", "Mark as Read");
} else {
document.getElementById("readunread").value = "Mark as Unread";
// 1. Update the localstorage
localStorage.setItem("readunread", "Mark as Unread");
}
}
</script>
<input
type="button"
value="Mark as Read"
id="readunread"
onclick="readunread();"
/>
<script>
// 2. Get the value from the local storage
function loadInitialValue() {
const localValue = localStorage.getItem("readunread");
console.log(localValue);
if (localValue == "Mark as Unread") {
document.getElementById("readunread").value = "Mark as Unread";
} else {
document.getElementById("readunread").value = "Mark as Read";
}
}
loadInitialValue(); // Make sure to call the function
</script>
CodePudding user response:
In order to manage the read/unread state of items using localStorage
as your persistent data store, you'll need to serialize your (un)read state as some kind of string to store as a value in the storage area (because localStorage
only stores string values), and then deserialize the value when you retrieve it. JSON is an accessible choice for the serialization format because it naturally represents many JavaScript data structures and is easy to parse/stringify.
Questions like these are always hard to demonstrate in a working code snippet because Stack Overflow's code snippet environment is sandboxed and prevents access to things like localStorage
, so when you try to use those features, a runtime exception is thrown as a result of the lack of permissions. Nonetheless...
Below I've provided a self-contained example of storing the read/unread state for a list of items using basic functional programming techniques to keep the code organized. This is just plain HTML CSS JavaScript and doesn't use any frameworks like React, etc. You can copy paste the code into a local HTML file on your computer and then serve it using a local static file web server (e.g. with Deno or Python, etc.) to see it working. I've included verbose comments for you to explain what's happening at every step of the program.
If you want to examine the state of your localStorage as you test the demo, see the question How to view or edit localStorage?.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>LocalStorage: read/unread items</title>
<style>
/* Just some styles for this example: styling is up to you */
* { box-sizing: border-box; }
body { font-family: sans-serif; }
.toggle-status {
font-size: 1rem;
padding: 0.25rem;
width: 8rem;
}
#list {
list-style: none;
padding: 0;
display: flex;
flex-direction: column;
gap: 0.5rem;
align-items: flex-start;
}
.item {
display: flex;
gap: 1rem;
align-items: center;
}
.item.read > .item-content { font-weight: normal; }
.item.unread > .item-content { font-weight: bold; }
</style>
<script type="module">
// Get the state of all of the read/unread items from localStorage
// as an object:
function getStatusMap () {
try {
// Get the JSON value from local storage:
// if it doesn't exist, it will be null, so use a default value instead:
// a JSON string representing an empty object:
const storageValue = window.localStorage.getItem('read_status_map') ?? '{}';
// Parse the string value into an actual object:
const readStatusMap = JSON.parse(storageValue);
// Return the value if it's a plain object:
if (
typeof readStatusMap === 'object'
&& readStatusMap !== null
&& !Array.isArray(readStatusMap)
) return readStatusMap;
// Else throw an error because it was an invalid value:
throw new Error('Unepxected value');
}
catch (ex) {
// Catch any exception which might have occurred.
// You can handle it however you want (or just ignore it).
// For example, you could print it
// to the console error stream to view it:
console.error(ex);
// Return an empty object as the default:
return {};
}
}
// Update the localStorage state of all the read/unread items:
function setStatusMap (statusMap) {
const json = JSON.stringify(statusMap);
const storageValue = window.localStorage.setItem('read_status_map', json);
}
// Update the read/unread status for a single item:
function updateStatus (statusMap, listItemElement, isReadStatus) {
const button = listItemElement.querySelector(':scope > button.toggle-status');
// Depending on the current status, update the action button's text
// to describe the next (opposite) action:
button.textContent = `Mark as ${isReadStatus ? 'unread' : 'read'}`;
// Get the ID from the list item's data attribute:
const {id} = listItemElement.dataset;
// Get the state object of the current item from the status map object,
// OR create one if it doesn't exist yet. You can store other information
// about each item here, but — in this example — only the ID (string)
// and read status (boolean) properties are stored:
const status = statusMap[id] ??= {id, isRead: false};
// Update the read status of the item:
status.isRead = isReadStatus;
// Update the whole state in localStorage:
setStatusMap(statusMap);
// Optional: update the list item's read/unread class.
// This can help with applying CSS styles to the items:
if (isReadStatus) {
listItemElement.classList.add('read');
listItemElement.classList.remove('unread');
}
else {
listItemElement.classList.remove('read');
listItemElement.classList.add('unread');
}
}
// A convenience function which toggles between read/unread for an item:
function toggleStatus (statusMap, listItemElement) {
// Get the ID from the list item's data attribute:
const {id} = listItemElement.dataset;
// Get the current status (or false by default if it doesn't exist yet):
let isRead = statusMap[id]?.isRead ?? false;
// Toggle it to the opposite state:
isRead = !isRead;
// Update it:
updateStatus(statusMap, listItemElement, isRead);
}
// Now, using the functions above together:
function main () {
// Get the initial read/unread status map:
const statusMap = getStatusMap();
// Get an array of the item elements:
const listItemElements = [...document.querySelectorAll('#list > li.item')];
for (const listItemElement of listItemElements) {
// Get the ID from the list item's data attribute:
const {id} = listItemElement.dataset;
// Set the initial read status for each item to what was found
// in localStorage, or if nothing was found then set to false by default:
const initialStatus = statusMap[id]?.isRead ?? false;
updateStatus(statusMap, listItemElement, initialStatus);
const button = listItemElement.querySelector(':scope > button.toggle-status');
// Set an action for each item's toggle button: when it is clicked,
// toggle the status for that item. Formally, this is called "binding an
// event listener callback to the button's click event":
button.addEventListener(
'click',
() => toggleStatus(statusMap, listItemElement),
);
}
}
// Invoke the main function:
main()
</script>
</head>
<body>
<!--
A list of items, each with:
- a unique ID
- a toggle button,
- and some text content
-->
<ul id="list">
<li data-id="cc9e88ce-3ed4-443a-84fc-fa7147baa025">
<button >Mark as read</button>
<div >First item content</div>
</li>
<li data-id="23a9204c-905f-48db-9f6a-deb3c8f82916">
<button >Mark as read</button>
<div >Second item content</div>
</li>
<li data-id="18b47e4c-635f-49c0-924e-b9088538d08a">
<button >Mark as read</button>
<div >Third item content</div>
</li>
<li data-id="ed2aacca-64f0-409d-8c1b-d1bdcb7c6058">
<button >Mark as read</button>
<div >Fourth item content</div>
</li>
<li data-id="0fce307b-656a-4102-9dc9-5e5be17b068d">
<button >Mark as read</button>
<div >Fifth item content</div>
</li>
<!-- ...etc. -->
</ul>
</body>
</html>