why is my classList applying in all icons - javascript

I have 3 heart icons. I want to add a class to each icon, the class is stored in Localstorage.
When I refresh the page, the class is added to all 3 icons, but I want to select only one to be active.
const heart = document.querySelectorAll('.icons .fa-heart');
let classr = localStorage.getItem("class");
heart.forEach((x) => {
if (classr !== null) {
x.classList.toggle(classr);
}
x.addEventListener("click", () => {
x.classList.toggle("active");
localStorage.setItem("class", "active");
});
});

You are storing the class in the local storage, but what you want to store is a way of identifying the heart that has been selected.
If you don't want to give each heart an ID, then you should be able to use the index of the item in the children collection of their parent.
(function()
{
let heartsContainer = document.getElementById('hearts');
for(let hi = 0; hi < heartsContainer.children.length; hi++)
{
let heart = heartsContainer.children[hi];
let gotValue = localStorage.getItem(hi);
if(gotValue !== null && gotValue === 'true')
{
heart.classList.toggle('empty');
heart.classList.toggle('full');
}
heart.addEventListener('click', function(e)
{
e.target.classList.toggle('empty');
e.target.classList.toggle('full');
let storedValue = localStorage.getItem(hi);
if(storedValue !== null)
{
localStorage.setItem(hi, !(storedValue==='true'));
}
else
{
localStorage.setItem(hi, true);
}
});
}
})();
.empty::before
{
content:'♡';
}
.full::before
{
content:'♥';
}
<div id="hearts">
<p class="empty"></p>
<p class="empty"></p>
<p class="empty"></p>
</div>
(The snippet won't work because of security issues, but you can see it working on codepen: https://codepen.io/schmellen/pen/BaOyMmL)
if(storedValue !== null) checks if there is an entry in the localStorage already for that number. If the entry is not null then it exist, so it only needs toggling.
If the entry does not exist (the value is null), and given the default state for a heart is "empty", the first time the entry is created, the entry should be true because the heart will be filled.

Related

Why is my method running twice and resetting the object value I'm trying to change?

I declare an Object called gameRooms, which contains Objects representing the rooms and their properties. Then I declare gameState and include a property for 'currentRoom.' When the game starts and I set the 'currentRoom' variable to 'Graveyard' via the ChangeRoom() method, it works just fine.
However, when I click a button that executes the function declared in a room's func property, then function writes:
"You Enter The Main Hall.
You Enter The Graveyard."
...to the window. When I check the console, I'm still in the Graveyard. Why?
JSfiddle here: https://jsfiddle.net/SirenKing/fz8pg05L/2/
gameRooms = {
Graveyard: {
description: 'The graveyard is small, enclosed by tall walls. The tombstones are weathered and worn. The names are hard to make out. An abandoned shed sits in one corner. A closed door leads into the castle.',
name: "Graveyard",
navButtons: [
{
name: "Go Inside",
state: "locked",
func: function () {
ChangeRoom( gameRooms.MainHall );
},
id: "tomainhall",
},
(etc etc etc, more rooms and buttons...)
],
},
}
gameState = {
State: {
currentRoom: null,
actionIDs: [ "wakeup" ],
navigationIDs: [],
inventoryIDs: [],
globalMenuIDs: [],
health: 100,
}
};
function ChangeRoom( room ) {
gameState.State.currentRoom = room;
Say( "You enter the " + room.name );
UpdateNavigation( );
UpdateActions( );
return gameState.State.currentRoom;
};
function Say(content) {
let comment = content;
let newParagraph = document.createElement('p');
newParagraph.textContent = comment;
let newestParagraph = document.getElementById("narrative").appendChild(newParagraph);
document.getElementById("narrative").id = "old";
newestParagraph.id = "narrative";
scrollTo(0,document.body.scrollHeight);
};
The HTML, and the button-constructing method, are here:
<html>
<body>
<div style="width:40%;" id="narrative"></div>
<div id="nav" style="top:200px; right:100px; position:fixed;">
<label>Navigate</label>
</div>
</body>
</html>
function UpdateNavigation( ) {
let oldNavigation = gameState.State.navigationIDs;
console.log(oldNavigation);
console.log("removing old nav buttons by id");
for (i = 0; i < oldNavigation.length; i++) {
// for ids in gameState.State.navigationIDs, remove the button elements by those ids
let elem = document.getElementById( gameState.State.navigationIDs[i] );
elem.remove();
}
gameState.State.navigationIDs = [];
let navs = GetCurrentRoom().navButtons;
let roomName = GetCurrentRoom().name;
console.log( roomName, "'s navButtons are ", navs);
console.log("building new list of nav button ids");
for (i = 0; i < navs.length; i++) {
gameState.State.navigationIDs.push( navs[i].id );
};
let currentNavigation = gameState.State.navigationIDs;
console.log(currentNavigation);
console.log("building nav buttons from list of ids");
for (i = 0; i < currentNavigation.length; i++) {
// for objects in room.navButtons, create new action buttons with ids of the rooms you can enter
// add innerHTML and onclick and type to the button, then append it into the navsDiv
elem = document.createElement("button");
elem.innerHTML = navs[i].name ;
elem.type = "button";
elem.id = currentNavigation[i];
elem.onclick = GetCurrentRoom().navButtons[i].func;
let navsDiv = document.getElementById( "nav" );
navsDiv.lastElementChild.appendChild(elem);
};
};
There are a few problems with your code; Say() does nothing with its second parameter, and should either do so, or concatenate the parameters sent to it: function Say(one, two) { return one + two; } or Say(one + two);... But mainly, the largest problem is that you append your buttons to the label element.
If this element were anything but a label, your code would work without issue. This is because the label element propagates click or touch events to the element(s) within it, so every time you click either of the buttons in that label element, the element clicks both buttons at once.
Thus, the simple fix is to remove the code that appends the buttons to the label:
navsDiv.lastElementChild.appendChild(elem);
Should be changed to:
navsDiv.appendChild(elem);
This of course goes the same for any code that appends buttons to the label elements. Also relevant is your UpdateActions() function, where you append content to the last child... A label.
Fixed fiddle

Get non hidden element with previousElementSibling

I have a long list of items. One of them is active. From that active item I want to go to the previous one. I can do that with previousElementSibling.
The problem is that I want to skip the hidden elements. How can I do that?
const active = document.querySelector('.active');
const prev = active.previousElementSibling;
console.log(active);
console.log(prev);
<div></div>
<div hidden></div>
<div class="active"></div>
In the real world, the list is dynamic and longer so no crazy hacks will work.
You can do this by looping to repeatedly walk through previousElementSibling's, until you find one that's not hidden. For example:
const active = document.querySelector('.active');
// Get the previous sibling
let result = active.previousElementSibling;
// If it's hidden, continue back further, until a sibling isn't hidden
while (result && result.hidden == true) {
result = result.previousElementSibling;
}
if (result) {
// You got a non-hidden sibling!
console.log(result);
} else {
// There is no previous sibling that's not hidden
}
You can keep going to previous elements until you find one that is not hidden or until you reach the first element :
const active = document.querySelector('.active');
let prev = active.previousElementSibling;
while (prev !== null) {
if (!prev.hidden) break;
prev = prev.previousElementSibling;
}
console.log(active);
console.log(prev);

remove hidden class on X items per click on list

I have a large list of transactions where only 5 items are visible at page load.
I have a button to load the rest by simply removing a "hidden" class. But I do not want to display them all at the button click but only 5 at a time.
I am a bit unsure of how I can do this.
My current script
const cashBackTransactionsWrapper = document.querySelector('.cashback--transactions');
if (cashBackTransactionsWrapper !== null) {
const morePostingsButton = document.querySelector('.cash-back--morepostings');
morePostingsButton.addEventListener('click', function (e) {
e.preventDefault();
const cashbackTableRowHidden = cashBackTransactionsWrapper.querySelectorAll('.flex-table.hidden');
for (var i = 0; cashbackTableRowHidden.length > 0; i++) {
cashbackTableRowHidden[i].classList.remove('hidden');
}
});
}
What I want to achieve is that when the user click the "morepostings" button the next 5 items have their hidden class remove. When the user clicks the button again, the next 5 items have the class removed and so forth.
A pagination-style functionality, if you will.
You have infinite for loop. You should replace your condition with i < 5. Should help :)
const cashBackTransactionsWrapper = document.querySelector('.cashback--transactions');
if (cashBackTransactionsWrapper !== null) {
const morePostingsButton = document.querySelector('.cash-back--morepostings');
morePostingsButton.addEventListener('click', function (e) {
e.preventDefault();
const cashbackTableRowHidden = cashBackTransactionsWrapper.querySelectorAll('.flex-table.hidden');
for (var i = 0; i < 5; i++) {
cashbackTableRowHidden[i].classList.remove('hidden');
}
});
}

sessionStorage css property of a div

I am trying to sessionStorage a display state (block/none) of a div filter-panel--content when my site refreshes. I have multiple div's with that class. Inside that div are some checkboxes that refreshes the site after they change state.
That works perfectly, but when my site refreshes this div stays closed 'display:none'. But I try to sessionStorage the display state.
This is a cutting of my sidebar:
<div class="filter-panel filter--property facet--property" data-filter-type="value-list" data-field-name="f">
<div class="filter-panel--flyout" style="border:none;">
<div id="label-it" class="label-it"><label class="filter-panel--title"><h3 class="rums">LED</h3><div id="klapp" class="klapp"></div></label></div>
<div class="filter-panel--content">
[...]
</div>
<div class="filter-panel filter--property facet--property" data-filter-type="value-list" data-field-name="f">
<div class="filter-panel--flyout" style="border:none;">
<div id="label-it" class="label-it"><label class="filter-panel--title"><h3 class="rums">Housing style</h3><div id="klapp" class="klapp"></div></label></div>
<div class="filter-panel--content">
[...]
</div>
When the div "label-it" is clicked, the div "filter-panel--content" toggles display:none and display:block, inside "filter-panel--content" are some checkboxes that refreshes the site.
And at this point I want to store the display property of "filter-panel--content"
This is my first solution of getting the sessionStorage:
sessionStorage.setItem("filterstate", $(this).closest(".filter-panel--flyout").find(".filter-panel--content div").css("display"));
This is my first solution of setting the sessionStorage, it's in the toggle function:
$('.label-it').click(function() {
$(this).find(".filter-panel--title div").toggleClass('klapp klappe');
$(this).closest(".filter-panel--flyout").find(".filter-panel--content div").toggle(350)
sessionStorage.setItem("filterstate", $(this).closest(".filter-panel--flyout").find(".filter-panel--content div").css("display"));
});
I need some help :)
2nd Try:
I tried to implement localStorage unfortunately it doesn't work.. I rly need some help..
SET:
$('.label-it').click(function() {
$(this).find(".filter-panel--title div").toggleClass('klapp klappe');
$(this).closest(".filter-panel--flyout").find(".filter-panel--content div").toggle(350)
var panel = $(this).closest(".filter-panel--flyout").find(".filter-panel--content div");
if ( panel.style.display === "block") {
localStorage.setItem("isOpen", true);
} else {
localStorage.setItem("isOpen", false);
}
});
GET:
$(document).ready(function() {
var isOpen = localStorage.getItem("isOpen");
if (isOpen === "true") {
$(this).closest(".filter-panel--flyout").find(".filter-panel--content div").css('display', 'block');
} else {
$(this).closest(".filter-panel--flyout").find(".filter-panel--content div").css('display', 'none');
}
});
With sessionStorage.setItem("filterstate", $(this).closest(".filter-panel--flyout").find(".filter-panel--content div").css("display")); you're setting filterstate to the display-value like block or none.
This at its own couldnt do what you want to. You have to know wich element you want to set to this saved display state.
For this you should better set an unique id="[...]" to each panel you would
like to use for this and setting the selected ids into the storage.
Then you could check your sessionStorage for the ids, search your
DOM for its elements and toggle them.
Furthermore your getting is a setting. To get something you have to use in your example sessionStorage.getItem('filterstate');
For example:
set: sessionStorage.setItem("filterstate", $(this).closest(".filter-panel--flyout").find(".filter-panel--content div").attr('id'));
get: $('#' + sessionStorage.setItem('filterstate').css('display', 'block');
As you said, you cant set unique ids. Here is an example using jQuerys .index():
set: sessionStorage.setItem("filterstate", $(this).parent().index(this);
get: $('[ ... Parent ... ]').eq(sessionStorage.setItem('filterstate')).css('display', 'block');
(Not tested)
The following is a tested example for
somethinge where more than one panel could be open at the same time. (demo)
Note the comments to learn more:
$('.clickable').click(function() {
var panel = $(this).closest(".wrapper").find(".childToShow"); // get panel
panel.toggle(350, 'swing', function() { // toggle panel and execute function !!!after!!! toggle
var storage = localStorage.getItem('indexes'); // get localStorage
var index = $(this).parent().index(); // get index of childWrapper (parent of .clickable and .childToShow)
if ($(panel).css('display') === "block") { // check if panel is showen
storage = storage ? storage + ';' + index : index; // if panel is showen !!!add!!! its .wrappers index to the localStorage
// you should not overwirte the storage with the index, becaus more than one panel could be open at the same time.
} else { //if panel is closed remove index from localStorage
var newStorage = ''; // initialize new string for localStorage
var storageArr = storage.split(';'); // split actual storage string into an array
storageArr.splice(storageArr.indexOf(index), 1); // remove from storage array
for (var i = 0; i < storageArr.length; i++) {
newStorage = i === 0 ? newStorage + storageArr[i] : newStorage + ';' + storageArr[i]; // build new storage string
}
storage = newStorage; // set storage variable to the new one
}
localStorage.setItem('indexes', storage); // write into storage
});
});
$(document).ready(function() {
console.log('Your "indexes" storage looks like: ', localStorage.getItem("active2"))
var storage = localStorage.getItem("indexes") // get localStorage
if (storage) { // check if storage is not empty
storage = localStorage.getItem("indexes").split(';'); // get storage array
for (var i = 0; i < storage.length; i++) {
$('.wrapper').eq(storage[i]).find(".childToShow").css('display', 'block'); // show each saved wrappers (its index is in the storage) .childToShow
}
}
});

tags underneath headings disappear when clicked again w/ javascript no idea why

I have pasted the javascript below but also a link to my codepen so you can see exactly what I am talking about.
I would like the heading to be clicked and expose the text below. On another click I would like for the text to go back to hidden. Multiple headings can be opened at the same time. What is happening with my current setup is you can click once to show, click again to hide and then when you click again to show nothing shows, if you keep clicking the text and headings below are eaten/dissapear. I would prefer to do this without jquery. thanks for any help.
http://codepen.io/jrutishauser/pen/YPrrNa
var clickToShow = function () {
if (this.nextElementSibling.className === 'open'){
this.nextElementSibling.remove('open');
} else if (this.nextElementSibling.className != 'open') {
this.nextElementSibling.className = 'open';
}
};
var articleHeadings = document.getElementsByTagName('h3');
for (var index = 0; index < articleHeadings.length; index++){
articleHeadings[index].onclick = clickToShow;
}
var subArticleHeadings = document.getElementsByTagName('h4');
for (var index2 = 0; index2 < subArticleHeadings.length; index2++){
subArticleHeadings[index2].onclick = clickToShow;
}
Change this.nextElementSibling.remove('open') to this.nextElementSibling.className = ''. I believe remove() method removes the element, not the class.
You can do it like this also. This is the correct way of doing it.
var clickToShow = function () {
element=this.nextElementSibling;
if (element.className === 'open'){
element.className=element.className.replace('open','');
} else if (element.className != 'open') {
element.className = 'open';
}
};

Categories

Resources