How to store click event in a local storage to use it after page refresh.
const container = document.querySelector('.filter ul');
container.addEventListener('click',(event) =>{
const closest = event.target.closest('.accordion-header');
if (!closest) return;
closest.classList.add('active');
closest.nextElementSibling.classList.add('active');
});
<div class = "filter">
<ul>
<li>
<h4 class = ""> Accordion </h4>
<p> Hello,world </p>
</li>
</ul>
</div>
You can use the localStorage API. You can save the state of the active class for each accordion header and its sibling in local storage, so that you can retrieve it after the page refreshes.
const container = document.querySelector('.filter ul');
container.addEventListener('click', (event) => {
const closest = event.target.closest('.accordion-header');
if (!closest) return;
closest.classList.add('active');
closest.nextElementSibling.classList.add('active');
// Store the state of the accordion header and its sibling in local storage
localStorage.setItem('accordion-header-state', JSON.stringify({
headerClassList: Array.from(closest.classList),
siblingClassList: Array.from(closest.nextElementSibling.classList),
}));
});
// Retrieve the state from local storage after page refresh
const savedState = JSON.parse(localStorage.getItem('accordion-header-state'));
if (savedState) {
const header = document.querySelector('.accordion-header');
const sibling = header.nextElementSibling;
header.classList.add(...savedState.headerClassList);
sibling.classList.add(...savedState.siblingClassList);
}
It sounds like you're not trying to store the click (event), but instead the fact that this item has been clicked and store it through page refresh.
In your click handler you'd need to add a line that stores the clicked state to local storage.
localStorage.setItem('itemClicked', 'true');
Note that localStorage only stores strings. Here we're storing an item in sotrage called itemClicked and giving it the value true.
On page load, you'd then need to check local storage and run classList.add('active') again on the element. In an onload function you'd check for this item as follows
localStorage.getItem('itemClicked') === 'true';
Related
first of all, I apologize that I don't speak English, I am trying to improve in this language.
I have a problem that I don't know how to solve it, I have a web in WordPress and it creates buttons dynamically with some dynamic classes, I need to take 1 of those classes to do the following:
Let's imagine that we have 5 buttons, when I clicked on 1 of those 5 that the other 4 buttons are hidden and this is saved in the local storage, ie everything is kept in the browser.
I have this code:
$('.dce-button').click(function(event){
$(this).attr('data-id' , 'Next');
localStorage.setItem("class", "noDisplay");
localStorage.setItem("noDisplay", "true");
$('.dce-button').not(this).each(function(){
$(this).toggleClass("noDisplay");
});
})
I use it to select the button I need and WORKS, it disappears the other 4 but I don't know how to keep it with the local storage, Is someone can help me?
Something like this would work and address Peter's comment
(working JS fiddle since localStorage isnt accessible in snippets on SO https://jsfiddle.net/f7804xwc/1/)
// on page load, get the stored value if there is one and set the buttons accordingly
let selectedBtn = localStorage.getItem('selectedBtn');
if (selectedBtn) {
$('.dce-button').hide();
$('#'+selectedBtn).show()
}
$('.dce-button').click(function(event) {
let selectedBtn = localStorage.getItem('selectedBtn');
console.log(selectedBtn);
if (selectedBtn === this.id) {
// user clicked the selected button again, lets un-select it and re-show all the buttons
localStorage.removeItem('selectedBtn');
$('.dce-button').show();
} else {
// user clicked a button, store selection and hide the others
localStorage.setItem("selectedBtn", this.id);
$('.dce-button').not(this).each(function() {
$(this).hide();
});
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button class="dce-button" id="btn1">Button 1</button><br>
<button class="dce-button" id="btn2">Button 2</button><br>
<button class="dce-button" id="btn3">Button 3</button><br>
<button class="dce-button" id="btn4">Button 4</button><br>
<button class="dce-button" id="btn5">Button 5</button><br>
From the above comment ...
"Once the OP has managed to solve writing to and reading from the local storage the proper button state there will be never a chance of any other button visible than the very one which was selected at the very first time (unless one resets/erases the local storage's state(s))."
... and all code of the next provided example which relates to initStorageRelatedUIHandling (the last 3 buttons which are for logging, clearing and rendering from the local storage's current state) is going to prove the above said/commented.
Now as for a possible solution, I personally would chose an approach which stores the state(s) of any DCE related button as a JSON.stringify 'ed array. Thus one does not rely on any specific button identification but stays generic from the beginning based on just a list of queried (jquery or selector api ... it doesn't matter) DOM nodes.
One would write 2 functions writeDceState and readDceState which both take care of the storage and JSON handling.
Another function like updateDceState exclusively takes care of generating the current array representation of any buttons state based on the currently targeted/selected button. It queries a list of all DCE buttons and does map each visibility state as false except for the currently chosen. It finally forwards the updated state to writeDceState.
The render part is covered exclusively by renderDceStateFromStorage. It reads the most current state from storage and updates each button's visibility from the parsed array.
Then there is a sole handler function ... handleDceStateChange ... which on click invokes both functions one after the other ... updateDceState and renderDceStateFromStorage.
function writeDceState(state = null) {
// `mock` is a specifc namespace which carries
// `localStorage` behavior for SO environments.
// - Thus the next line can be omitted later.
const { localStorage } = mock;
localStorage
.setItem('dce', JSON.stringify(state)
);
}
function readDceState() {
// `mock` is a specifc namespace which carries
// `localStorage` behavior for SO environments.
// - Thus the next line can be omitted later.
const { localStorage } = mock;
return JSON
.parse(
localStorage.getItem('dce') || null
);
}
function updateDceState(dceTarget) {
const state = Array
.from(
document
.querySelectorAll('.dce-button')
)
.map(elm => (elm === dceTarget));
writeDceState(state);
}
function renderDceStateFromStorage() {
const state = readDceState();
document
.querySelectorAll('.dce-button')
.forEach((elm, idx) =>
elm.disabled = !!state && !state[idx]
);
}
function handleDceStateChange({ currentTarget }) {
updateDceState(currentTarget);
renderDceStateFromStorage();
}
function initDceHandling() {
document
.querySelectorAll('.dce-button')
.forEach(elm =>
elm.addEventListener('click', handleDceStateChange)
);
}
function initStorageRelatedUIHandling() {
document
.querySelector('[data-state-display]')
.addEventListener('click', () =>
console.log( readDceState() )
);
document
.querySelector('[data-state-clear]')
.addEventListener('click', mock.localStorage.clear);
document
.querySelector('[data-state-rerender]')
.addEventListener('click', renderDceStateFromStorage);
}
initDceHandling();
initStorageRelatedUIHandling();
body { margin: 0; }
ul { list-style: none; padding: 0; margin: 0; }
li:nth-child(6) { margin-top: 20px; }
li:nth-child(8) { margin-top: 10px; }
.as-console-wrapper { left: auto!important; width: 50%; min-height: 100%; }
<ul>
<li>
<button class="dce-button">Foo</button>
</li>
<li>
<button class="dce-button">Bar</button>
</li>
<li>
<button class="dce-button">Baz</button>
</li>
<li>
<button class="dce-button">Biz</button>
</li>
<li>
<button class="dce-button">Buzz</button>
</li>
<li>
<button data-state-clear>clear storage state</button>
</li>
<li>
<button data-state-display>display storage state</button>
</li>
<li>
<button data-state-rerender>rerender from storage</button>
</li>
</ul>
<script>
// mock for the SO specific stack snippet
// due to the policies and environment are
// not allowing an original storage access.
const mock = {
localStorage: (function () {
// https://developer.mozilla.org/en-US/docs/Web/API/Storage
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
const storage = new Map;
function key(int) {
return [
...storage.keys()
][parseInt(int, 10)];
}
function setItem(key, value) {
return storage.set(String(key), String(value));
}
function getItem(key) {
return storage.get(String(key));
}
function removeItem(key) {
return storage.delete(String(key));
}
function clear() {
return storage.clear();
}
return {
get length() {
return storage.size;
},
key,
getItem,
setItem,
removeItem,
clear,
};
}()),
}
</script>
Each button changes color when pressed. And I want them to stay that color every time the page reloads until a a localStorage.clear() is ran.
So I'm trying to re-apply the previously applied css with local storage. My idea is to store the Ids of each button, loop through Ids, and re-apply css. But it keeps throwing error: "Cannot read properties of null (reading 'style')". Another issue I seem to have is I lose my local storage on the 2nd page reload!
If you have a way to improve this or a better way to go about this please let me know! Thanks.
UPDATED Code
Here is a link to the most working version
https://codesandbox.io/s/festive-rui-vz7tf?file=/src/App.js
I added a "Delete History Btn".
The only issue now is after a reload and selecting another button, the local storage is completely erased with the new selection. What I need is the selections to stack until the user runs a localStorage.clear().
import React,{useState,useEffect} from 'react';
const App=()=> {
const [btns, setBtns] = useState([]);
useEffect(() => {
localStorage.setItem('BtnsClicked', JSON.stringify({ btns }));
}, [btns]);
const handleAcknowledge=(event)=>{
setBtns([...btns, event.target.id])
event.target.style.backgroundColor = "red"
event.target.innerHTML = "Clicked!"
}
const reSelectBtns=()=>{
const storedBtns = JSON.parse(localStorage.getItem('BtnsClicked'));
if (storedBtns.btns.length){
storedBtns.btns.forEach(btn=>{
console.log(document.getElementById(btn).style.backgroundColor = "red")
})
}
}
reSelectBtns()
return (
<div className="App">
<button id = "1" onClick={handleAcknowledge}>Acknowledge</button>
<button id = "2" onClick={handleAcknowledge}>Acknowledge</button>
<button id = "3" onClick={handleAcknowledge}>Acknowledge</button>
<button id = "4" onClick={handleAcknowledge}>Acknowledge</button>
</div>
);
}
export default App;
Checkout this sandbox link of mine https://codesandbox.io/s/blissful-marco-7wgon?file=/src/App.js
It addresses your problems.
This is something quite simple but somehow resulted in a crazy rabbit hole.
This link shows what I want:
https://www.w3schools.com/howto/howto_js_active_element.asp
Nothing special, now the thing becomes hairy for me when the elements in the navbar are rendered from an array of objects (from the specs). The approach I am following is basically rendering a list of buttons, this list of buttons is the state, since supposedly when you update a state it triggers a re-render, then when a button is clicked it "sets" the active class to false on the entire array-state then activates it only for the clicked one. So far it works.
The problem is that the active class is rendered two steps behind. One for the moment when the class in the array-state's elements are set to false, the other when the clicked element gets updated.
As far as I understand useState and setState are queues, hence those are applied asynchronously on each render, in order to avoid that and get the renders to show the current state, useEffect is utilized.
Now the thing is that I am not sure how to apply useEffect in order to achieve the immediate render of the "active" class.
This is the code I have:
import { options } from 'somewhere...'
export default function SideMenu(props){
let auxArr = []
let targetName
const [stateOptions, setStateOptions] = useState([...options])
const [currentOption, SetCurrentOption] = useState({})
function activeOption(e){
// this helps with event bubbling
if (e.target.tagName == "P" || e.target.tagName == "SPAN"){
targetName = e.target.parentElement.id
} else if (e.target.tagName == "IMG"){
targetName = e.target.parentElement.parentElement.id
} else {
targetName = e.target.id
}
// since the main state is an array of objects I am updating it
// in three steps, first the current object is "activated"
// then the main array-state gets "inactivated" to erase all
// the previous "active" classes, finally the activated object
// replaces the corresponding inactive object in the main state.
let targetElement = stateOptions.filter(e => e.id==targetName)[0]
SetCurrentOption({
id: targetElement.id,
activity:true,
img: targetElement.img,
name: targetElement.name
})
// first the "classes" are set to false, then the
// "activated" object replaces the corresponding one
// in the main object, from here comes the two
// steps delay.
auxArr = [...stateOptions]
auxArr.forEach(e => e.activity=false)
setStateOptions(auxArr)
const newOptions = stateOptions.map(e =>
e.id==currentOption.id ? currentOption : e
)
setStateOptions(newOptions)
}
return(
<aside className={styles.sideDiv}>
<nav>
{stateOptions.map(({id, img, name, activity, link}) => {
return(
<button key={id} id={id} onClick={activeOption} className={activity?styles.active:""}>
<Image src={img}/>
<p className={timeColor.theme}> {name} </p>
</button>
)
})}
</nav>
</aside>
)
}
Thanks in advance for any help you can provide.
Local storage value saved but how to use the saved local storage used. I tried creating a function apply but the class is not applying
document.querySelectorAll("#abc, #xyz").forEach(btn => {
btn.addEventListener("click", () => {
const e = document.querySelectorAll(".dog,.cat, #abc, #xyz");
e.forEach(el => {
el.classList.toggle("red");
var xyz = document.getElementById("xyz").className;
localStorage.setItem("vovo", xyz);
});
});
})
function apply(){
var xy = localStorage.getItem('vovo');
if (xy) {
var single = document.querySelectorAll(".dog,.cat, #abc, #xyz");
single.forEach(el => {
el.classList.add(xy);
});
}
};
The functionality logic :
when a button is pressed we check if it has the red class (as all the elements, buttons and the other divs, will receive the red class at some point).
if it has that class then it will be toggled (it will be removed from all the buttons and the other divs) thus the localStorage will store something like "No the elements don't have the red class".
if it doesn't has it the it will be toggled (it will be added to all the buttons and the other divs) thus the localStorage will store something like "Yes the elements have the red class".
basically, the localStorage will store '0' if the class isn't applied, '1' if the class is applied.
now, when the page gets refreshed, we check if the localStorage item that stores the red class' state is there (not null) and has the value of '1' then apply that class to all the elements (the buttons and the other divs).
remove the item that holds the red class' state from the localStorage.
Here's the update JavaScript code :
Sorry for everyone as I can't make a live demo using SO's snippets as the localStorage can't be reached because the code is sandboxed.
Anyway, here's a CodePen demo illustrating the required functionality.
const els = document.querySelectorAll('.dog,.cat, #abc, #xyz');
/** when the page finishes loading **/
document.addEventListener('DOMContentLoaded', () => {
/** check if the 'red' class was applied **/
applied = window.localStorage.getItem('class-applied') === '0' ? false:true;
/** remove "class-applied" item from the localStorage **/
window.localStorage.removeItem('class-applied');
/** if the "red" class was applied just apply it on document load **/
applied && els.forEach(el => el.classList.add('red'));
});
els.forEach(btn => {
btn.addEventListener('click', () => {
/** store the "red" class' state : '1' means it is applied, '0' means it isn't apllied **/
window.localStorage.setItem(
'class-applied',
!btn.classList.contains('red') ? '1' : '0'
);
els.forEach(el => el.classList.toggle('red'));
});
});
The above code should be placed just before </body>.
just use the following syntax:
localStorage.setItem('myLocalStorageVariable', 'myValue');
If you want to read the value instead:
localStorage.getItem('myLocalStorageVariable')
I'm creating my own carousel and I want to add circles navigation on the bottom of slider. I am fetching data from mongoDb (3 pictures) and load them in App component and passing via props to Carousel component.
I want to map trough array and set index to data-slider property and later read this with e.target.dataset.slider and change in method changeSlider() to this value in data-slider property.
I have weird problem, when I click on this buttons circles sometimes I have value === 2, 0, 1 but sometimes I'm getting undefined and my slider don't know which slider make active.
<div className="circle-container">
{this.props.images.map((el, index) => {
return (
<button
key={index}
onClick={this.setActiveSlide}
className="circle-empty"
data-slider={index}
>
<i className="far fa-circle" />
</button>
);
})}
</div>
Method:
setActiveSlide = e => {
let slider = e.target.dataset.slider;
this.setState({
slider: slider
});
};
Call setActiveSlide method onClick gave me this result:
Your event is probably firing from the icon sometimes simply change the event target to currentTarget
setActiveSlide = e => {
// I've changed this variable to const because our data-slider is not
// going to change.
const slider = e.currentTarget.dataset.slider;
// Using es6 object property shorthand if we have both
// the same variable name and field we can simply pass in just the
// field name and it will auto convert it for us
this.setState({
slider
});
};