Revert `display: none` on elements to original value [duplicate] - javascript

This question already has answers here:
how to revert back to normal after display:none for table row
(9 answers)
Closed 4 years ago.
explanation
I have a script that hides elements, if the user does not have the permission to view/use it. I set display: none for those elements.
Now later, when I want to show the elements again, I don't just want to set display: block or something, because maybe the elements original display value was something other than block. Is there a way I can revert the display or set it to a neutral value?
example
E.g. <div class="fancy-class">...</div> If fancy-class has display set to inline-block and I just set it to block with my script, that will break the ui.
attempts
I have tried using display: initial but that resets it to the HTML-element's initial styling - not the class's styling.
I hope I don't have to keep the original values in an array and then apply them again. Doesn't seem nice.

use: element.style.display = "" to reset style.display of an element
(() => {
const displayState = reset =>
Array.from(document.querySelectorAll("div"))
.forEach( el => el.style.display = reset ? "" : "none" );
// ^ restore original display state
document.querySelector("#showAll").addEventListener("click", () => displayState(true));
document.querySelector("#hideAll").addEventListener("click", () => displayState());
})();
#first, #second, #third {
display: inline-block;
margin-right: 1em;
}
<div id="first">[First]</div>
<div id="second">[Second]</div>
<div id="third">[Third]</div>
<button id="showAll">show divs</button>
<button id="hideAll">hide divs</button>

Before setting display to none, you can save it, and apply it back.
var display = document.getElementsByClassName("fancy-class").style.display;
document.getElementById("fancy-class").style.display = none;
Set above saved attribute.
document.getElementById("fancy-class").style.display = display;

Related

Can't console.log() .style.display information

I'm able to correct switch between a dropdown menu being displayed and hidden when I click on my input tag, but when console logging the style displayed I get nothing shown on the console. What is the explanation for not being able to see this and how would I go about trying to correctly see which display style I used?
document.getElementById('search').addEventListener("click", function () {
const grid1 = document.querySelector(".modal");
grid1.style.display = grid1.style.display ? "none" : "flex";
console.log(grid1.style.display);
});
Instead of using 'yourelement.style.display' use 'getComputedStyle' function to get the value of any property in this case 'display', thats because Computed style contains all the CSS properties set to an element. Even if do not set a property to an element. You will still find that property in the computed styles.
Modifiying your code with 'getComputerStyle'
document.getElementById('search').addEventListener("click", function () {
const grid1=document.querySelector(".modal")
console.log(window.getComputedStyle(grid1).display)
grid1.style.display = window.getComputedStyle(grid1).display == "none" ? "flex" : "none";
console.log(window.getComputedStyle(grid1).display)
})
.modal {
display: none;
}
<input id="search">
<select class="modal"></select>
For more clarification check Window.getComputedStyle() in MDN Web Docs

toggle / display image through an event listener

I have a list of featured products which I get through an API call, with the title and the icon displayed in the list. All the products also have images (which I also get through the same API call)
I want the image to not display when the icon is not active, but to display when the icon is active. Not sure how I get to display that specific image when the icon to that product is active.
(kinda new into coding, so sorry if this is a weird question)
export function featuredProducts(products)
const featuredProductsContainer = document.querySelector(".featured-products_list");
featuredProductsContainer.innerHTML = "";
for (let i = 0; i < products.length; i++) {
console.log(products[i]);
if (products[i].featured) {
featuredProductsContainer.innerHTML +=
`<li class="featured-products">
<p>${products[i].title}<i class="far fa-flag" data-name="${products[i].title}"></i></p></li>
<img src="http://localhost:1337${products[i].image.url}" class="${products[i].title}"
height="300" width="300" style="display: none;">`;
}
}
const flag = document.querySelectorAll(".featured-products i");
flag.forEach(function(icon) {
icon.addEventListener("click", clicked);
});
function clicked(event) {
event.target.classList.toggle("fas"); //active
event.target.classList.toggle("far"); //unactive
}
}
TL;DR You'll want to add css to hide/show the images (As #barmar answered above).
I will propose a slightly different approach, which is toggling the classes on the images directly, to avoid a more complex rearrangement of the markup and gigantic css selectors.
But first, to make it easier, you should place the img tags inside their li, not beside them.
So, first, let's move the closing li tag to the end, after the img. Note I'm also removing the inline style of style="display: none;".
for (let i = 0; i < products.length; i++) {
console.log(products[i]);
if (products[i].featured) {
featuredProductsContainer.innerHTML +=
`<li class="featured-products">
<p>${products[i].title}<i class="far fa-flag" data-name="${products[i].title}"></i></p>
<img src="http://localhost:1337${products[i].image.url}" class="${products[i].title}"
height="300" width="300"></li>`;
}
}
Then, in your click handler, let's do something different:
function clicked(event) {
// remove all active classes
const $imgs = document.querySelectorAll('.fas')
$imgs.forEach(i => i.classList.toggle('fas'))
// add active class to targeting img
const $img = event.target.closest('li.featured-products').querySelector('img')
$img.classList.toggle("fas")
$img.classList.toggle("far");
}
Lastly, modified from from #barmar
.featured-products img.fas {
display: block;
}
.featured-products img.far {
display: none;
}
You can do this with CSS. Since your event listener toggles the far and fas classes, use CSS selectors that match an img inside those containers.
.featured-products.fas img {
display: block;
}
.featured-products.far img {
display: none;
}
There are many ways to go about this, a lot depends on what triggers the active state of the icon.
if it's any kind of input and you can keep the data in the same container then all you need to do Is add an "active" css class to the parent. This is the most performant way as you keep reads, writes and any reflows to a minimum.
Just add a general rule in in your css for the active class:
.active img { visibility: visible; }
If the images are in a separate element, you can add a dataset custom property to the icon in your html. With a value you can use in Javascript.
I. e.
<img id="icon" dataset-foo="imgContainer">
and in JS
var imgContainer = document.getElementById(icon.dataset.foo)
imgContainer.classList.add("active")
You can wrap it in a function and maybe save any references in an object. This way it's easy to keep track of any data and have very readable code.

remove styles from all nodelist element and add only to clicked element Vanilla JS

I have multiple divs that when clicked adds a border and scales them up a little. I am looping through all elements using foreach and on click i remove every element's border and scale property except the clicked element, to which i add a border and scale.
My code is completely logical and is supposed to work but for some reason i cant seem to grasp, it only applies the styles to clicked elements but not removing from the rest of the elements (like my code says it should).
JS
document.querySelectorAll('.projcolorpick div').forEach(el => {
el.onclick = (e) => {
el.style.border = "none"
el.style.transform = "scale(1)"
e.target.style.border = "2px solid #fff"
e.target.style.transform = "scale(1.2)"
projcolor = e.target.style.background
}
})
}
give something like this a try... each element needs an id attribute for this to work (the filter part - if there is a unique attribute...)
const list = Array.from(document.querySelectorAll('.projcolorpick div'));
list.forEach(el => {
el.addEventListener('click', (e) => {
//code that affects the element you click on
el.style.border = "2px solid #fff"
el.style.transform = "scale(1.2)"
projcolor = e.target.style.background;
list.filter(x=>x.id!=el.id).forEach(otherEl=>{
//code that affects the other elements you didn't click on
otherEl.style.border = "none"
otherEl.style.transform = "scale(1)"
});
});
});
```
edit:
fixed some typos.
forEach only applies to Arrays unless you configure it otherwise.
querySelectorAll does not return arrays, but array-like objects (NodeLists)
To allow looping over NodeLists, add the following code:
if (window.NodeList && !NodeList.prototype.forEach) {
NodeList.prototype.forEach = Array.prototype.forEach;
}
var nL = document.querySelectorAll('*');
console.log(nL instanceof NodeList); // true
You don't really need an id attribute on each div and I would advocate using class-assignments instead of changing their individual attributes. You can compare the actual DOM elements with each other like c==ev.target, as you can see in my code below:
// === populate the page first ... ============================= START =
const cont=document.getElementById('container');
cont.innerHTML=
[...Array(3)].map(cp=>'<div class="projcolorpick">'+
[...Array(8)].map(d=>{
let hsl= "hsl("+Math.floor(Math.random()*360)+",100%,80%)";
return ' <div style="background-color:'+hsl+'">'+hsl+'</div>'}).join('\n')
+'</div>').join('\n');
// === populate the page first ... =============================== END =
// now, do the action:
cont.onclick=ev=>{
if ( ev.target.parentNode.classList.contains('projcolorpick')
&& ev.target.tagName=='DIV'){
[...ev.target.parentNode.children].forEach(c=>c.classList.toggle('selected',c==ev.target));
ev.target.parentNode.style.backgroundColor=ev.target.textContent;
}
}
.projcolorpick {border: 2px solid #888}
.selected {border: 2px solid #fff; transform:scale(1.2);}
div {margin:6px; padding:4px}
.projcolorpick div {width:200px; height:20px}
<div id="container"></div>
The action happens here:
cont.onclick=ev=>{
if ( ev.target.parentNode.classList.contains('projcolorpick')
&& ev.target.tagName=='DIV'){
[...ev.target.parentNode.children].forEach(c=>c.classList.toggle('selected',c==ev.target));
ev.target.parentNode.style.backgroundColor=ev.target.textContent;
}
}
I use a delegated event-attachment to the parent .container div. The first if statements makes sure that only clicks on .projcolorpick>div elements are processed.
If you want to include more than one generation between them you need to use something like ev.target.closest('.projcolorpick') instead ...
Now, inside the if block two things happen:
Using toggle() on all DOM elements in ev.target.parentNode.children the class "selected" is either
assigned or
removed.
The text found in the clicked div is applied as background-color to the parent .projcolorpick container.

Finding width of hidden element similar to jQuery in pure javascript

I am trying to replicate the jQuery width function using pure JavaScript. Pure JavaScript functions such as getComputedStyle or offsetWidth seem to work if the element is visible, but I cannot replicate the same behavior when the element is hidden.
It seems that jQuery is doing something different here and I cannot figure out what exactly.
To clearly explain what i am trying to do, Here is a codepen example where I try the getComputedStyle in comparison with the jQuery width function for calculating the width of a hidden element that is changing dynamically.
const input = $('input');
const sizer = $('.sizer');
const sizerDom = document.querySelector('.sizer');
input.on('input', evt => {
sizer.text(evt.target.value);
console.log(sizer.width())
console.log(getComputedStyle(sizerDom, null).width);
});
.sizer {
display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<input type="text">
<span class="sizer">
https://codepen.io/OmranAbazid/pen/OJNXyoG
That is because in jQuery's internal logic, it interally swaps the display property from none to another value so that it forces the browser to momentarily render it. Otherwise the measurements will always be 0 since the element is never rendered.
Also, instead of trying to use window.getComputedStyle which will return a string value of the CSS dimension (e.g. 100px), you can use sizerDom.getBoundingClientRect() to get the actual number instead (which returns, say, 100) without needing to do additional parsing.
const input = $('input');
const sizer = $('.sizer');
const sizerDom = document.querySelector('.sizer');
input.on('input', evt => {
sizer.text(evt.target.value);
console.log(sizer.width())
const cachedDisplay = window.getComputedStyle(sizerDom).display;
sizerDom.style.display = 'inline-block';
console.log(sizerDom.getBoundingClientRect().width);
sizerDom.style.display = cachedDisplay;
});
.sizer {
display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<input type="text">
<span class="sizer">

How to get dynamic innerHTML values?

So I retrieve a list of data and each piece of data has an ID provided by SQL. The ID is stored in a <p> tag, and I extract the ID from the <p> tag using innerHTML.
I have an event handler that will delete the element on click based on its ID, But after I click delete on my first element, the innerHTML value remains the same. For example, say the ID of the first element was 10, once I click delete, it will delete it. But once I click delete on the element with the ID of 11, it still says the value is 10 and will NOT delete the element with the ID of 11.
document.addEventListener('click', async (e) => {
e.preventDefault();
if (e.target && e.target.id === 'delete-btn') {
// Reset the notification bar to be displayed again
resetNotification();
let movieToDeleteID = document.getElementById('primary-key').innerHTML;
console.log(movieToDeleteID)
await axios.post('http://localhost:5000/movies/delete', { movieToDeleteID })
.then(response => {
showNotification(response);
movieID.value = ''
})
.catch(err => console.error(err));
}
})
This is the code I'm using currently.
How can I make it so that the value of movieToDeleteID changes with each click of the delete button to reflect that element's ID?
Thank you!
In your code, the line
let movieToDeleteID = document.getElementById('primary-key').innerHTML;
will always get the inner HTML of the element with id='primary-key'-- and every id value on your page must be unique.
If this element's innerHTML content doesn't change between clicks, every click will have the same result as the first.
(The same goes for your id='delete-btn', by the way: there can be only one such element on your page. And your script only runs when you click that particular element, but I think this is probably what you intended.)
If you have one p element per movie, each holding a different movieID, you need to make sure they don't share id values, which may mean you want to use a different selector (besides .getElementById), such as .querySelector or .getElementsByClassName.
It's impossible to give you a 100% solution without seeing your HTML, but here's how it could work in a fabricated HTML structure:
// Selects the outermost div
const container = document.getElementById("container");
// Calls `deleteMovie` on click
container.addEventListener("click", deleteMovie);
// Defines `deleteMovie`
function deleteMovie(event){
// Gets the clicked element and quits early if it's not a button
const clickedEl = event.target;
if(!clickedEl.classList.contains("delete-btn")){ return; }
// Gets the text in the `primary-key` element (identifying its parent div as well)
const
movieDiv = clickedEl.parentElement,
idParagraph = movieDiv.querySelector(".primary-key"),
movieIdToDelete = idParagraph.textContent;
// Calls the DB-communication function (which, here, just logs success)
sendDeleteRequestToDatabase(movieIdToDelete);
// Calls a function to update the page
hideMovieFromList(movieIdToDelete);
}
// Defines `sendDeleteRequestToDatabase`
function sendDeleteRequestToDatabase(movieId){
console.log(`Deleted movie #${movieId}`);
}
//Defines `hideMovieFromList`
function hideMovieFromList(movieId){
// Selects all elements w/ `class='primary-key'`
let idPs = document.getElementsByClassName("primary-key");
// Loops through selected elements to find matching text
for(let p of idPs){
if(p.textContent === movieId){
// Removes movie div from top-level div
const movieDiv = p.parentElement;
container.removeChild(movieDiv);
break; // We already found a match, so might as well stop looking
}
}
}
h3, p{ margin: 0; padding: 0; }
.movie{ width: 15em; border: 1px solid grey; margin: 3px; padding: 3px; }
<div id="container">
<div class="movie">
<h3 class="title">Dracula</h3>
<p class="genre">Horror</p>
<p class="primary-key">1</p>
<button class="delete-btn">Delete</button>
</div>
<div class="movie">
<h3 class="title">Captain Marvel</h3>
<p class="genre">Action</p>
<p class="primary-key">2</p>
<button class="delete-btn">Delete</button>
</div>
<div class="movie">
<h3 class="title">When Harry Met Sally</h3>
<p class="genre">Comedy</p>
<p class="primary-key">3</p>
<button class="delete-btn">Delete</button>
</div>
</div>

Categories

Resources