My onmouseenter function isn't triggering.
I created a button that would allow you to go to the top. However, once there I want the button to have a display property of none. I tried doing it in two ways. One with classList and the other with style.display = 'none'. Both didn't work, am I missing something in the logic ?
EDIT-------------
onmouseleave the button should reappear. I added the function.
Here is a code pen
const topDiv = document.getElementById('topDiv')
const arrowup = document.getElementById('arrowup')
const hideArrow = () => {
if (topDiv) {
arrowup.classList.remove(showme');
arrowup.classlist.add('hideme');
} else {
arrowup.classList.add('showme');
}
}
const showArrow = () => {
if (!topDiv) {
arrowup.classList.remove('hideme');
arrowup.classList.add('showme');
}
}
#top {
height: 1000px;
}
a {
position: fixed;
top: 10px;
}
.showme {
display: block;
}
.hideme {
display: none;
}
<div onmouseleave="showArrow() onmouseenter="hideArrow()" id="top">
hello
</div>
<a class="showme" id="arrowup" onClick="hideArrow()" href="#top">
click me
</a>
There are some issues:
onmouseenter="hideArrow" is missing brackets -> onmouseenter="hideArrow()"
add and remove are functions, that get the class as param -> add('showme')
the return is wrong -> remove it
Working example:
const topDiv = document.getElementById('topDiv')
const arrowup = document.getElementById('arrowup')
const hideArrow = () => {
if (topDiv) {
arrowup.classList.remove('showme');
arrowup.classList.add('hideme');
}
else {
arrowup.classList.add('showme');
}
}
#top {
height: 1000px;
}
a {
position: fixed;
top: 50px;
}
.showme {
display: block;
}
.hideme {
display: none;
}
<div onmouseenter="hideArrow()" id="topDiv">
hello
</div>
<a class="showme" id="arrowup" onClick="hideArrow()" href="#topDiv">
click me
</a>
Remove the return keyword from the if condition because it causes the function to stop running at that point
element.classList.add() is a function while you are assigning classes to the element using the '=' operator. So the correct way to do it is arrowup.classList.add('hideme') and arrowup.classList.remove('showme').
Related
I'm trying to make an image popup using the .toggle function in javascript however it only works 1.5 times.
I can click it and it opens it, click the background to close it, open it again but then i cannot close it.
Here is the codepen with everything needed, any guidance would be great so thanks in advance. (im pretty new to js
https://codepen.io/user843/pen/gOXobZZ
function prodImgPopup(a) {
popUpModal = document.querySelector(".prodimgpop")
popupImgID = document.getElementById("popID")
popUpModal.classList.toggle("product-popup-show")
popupImgID.src = a.src
popUpModal.addEventListener("click", e => {
popUpModal.classList.toggle("product-popup-show")
})
every time you click the image you add the event listener ... therefore, when you've clicked the image twice, it has the event listener running twice ... which means it does toggle twice
Of course toggling twice === do nothing
Change your code to
const popUpModal = document.querySelector(".prodimgpop")
const popupImgID = document.getElementById("popID")
popUpModal.addEventListener("click", e => {
popUpModal.classList.remove("product-popup-show")
});
function prodImgPopup(a) {
popupImgID.src = a.src
popUpModal.classList.add("product-popup-show")
}
edit: not sure why every second time you load that page you get that prodImgPopup is undefined
anyway - here's an alternative
const popUpModal = document.querySelector(".prodimgpop");
const popupImgID = document.getElementById("popID");
popUpModal.addEventListener("click", (e) => {
popUpModal.classList.remove("product-popup-show");
});
document.querySelectorAll("img[loading=lazy]").forEach((img) =>
img.addEventListener("click", (prodImgPopup) => {
popupImgID.src = img.src;
popUpModal.classList.add("product-popup-show");
})
);
and remove the onclick= from those <img loading="lazy" ...> images
You also need CSS change to fix the latent image
.prodimgpop {
display: none;
}
.prodimgpop.product-popup-show {
display: block;
}
Here's the code all fixed up and running
const popUpModal = document.querySelector(".prodimgpop");
const popupImgID = document.getElementById("popID");
popUpModal.addEventListener("click", (e) => {
popUpModal.classList.remove("product-popup-show");
});
function prodImgPopup(a) {
popupImgID.src = this.src;
popUpModal.classList.add("product-popup-show");
}
document.querySelectorAll("img[loading=lazy]").forEach((img) =>
img.addEventListener("click", (prodImgPopup) => {
popupImgID.src = img.src;
popUpModal.classList.add("product-popup-show");
})
);
.product-popup {
display: none;
}
.product-popup-show {
width: 100%;
height: 100%;
position: absolute;
top: 0;
right: 0;
z-index: 100000;
background-color: rgba(32,32,32, .8);
position: fixed;
}
.popup-img {
display: flex;
justify-content: center;
align-items: center;
padding: 5rem;
}
.popup-img .img {
width: 100%;
max-width: 750px;
object-fit: cover;
}
/* fix popup display */
.prodimgpop {
display: none;
}
.prodimgpop.product-popup-show {
display: block;
}
<div class="prod">
<img loading="lazy" src="https://images.unsplash.com/photo-1602471615287-d733c59b79c4?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1171&q=80" class="img prod-img" alt="">
<img loading="lazy" src="https://images.unsplash.com/photo-1533689476487-034f57831a58?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1171&q=80" class="img prod-img" alt="">
</div>
<div id="productPop" class="prodimgpop">
<div class="popup-img">
<img id="popID" class="img">
</div>
</div>
As mentioned by Jaromanda, the event listener is being added each time you click. Try changing the Javascript to this.
function prodImgPopup(a) {
popUpModal = document.querySelector(".prodimgpop")
popupImgID = document.getElementById("popID")
popUpModal.classList.toggle("product-popup-show")
popupImgID.src = a.src
}
const popupdisplay = document.querySelector("#productPop");
popupdisplay.addEventListener("click", e => {
popUpModal.classList.toggle("product-popup-show")
});
I would like to open and close overlay using single button, so when the button is clicked an additional class is added, when closed the class is removed and overlay is closed.
So far I wrote the code that opens overlay and add/remove the class to the button.
Also I've created the method to close the overlay but I'm struggling to create a proper event to actually close it, so I would be happy if anyone can guide me a bit.
I think there should be an 'if' statement within the events() checking if the button have added class, if so, the overlay will be closed using this function element.classList.contains("active");
Also the button is animated, so when class is added 3 bars (hamburger icon) becomes X and this is the main reason I don't want to have separate buttons to open and close, I already achieved that but this is not what I'm looking for.
class OverlayNav {
constructor() {
this.injectHTML()
this.hamburgerIcon = document.querySelector(".menu-icon")
this.events()
}
events() {
this.hamburgerIcon.addEventListener("click", () => this.overlayOpen())
}
overlayOpen() {
document.getElementById("myNav").style.width = "100%";
this.hamburgerIcon.classList.toggle("menu-icon--close-x")
}
overlayClose() {
document.getElementById("myNav").style.width = "0%";
}
injectHTML() {
document.body.insertAdjacentHTML('beforeend', `
<div id="myNav" class="overlay">
<p>My Overlay</p>
</div>
`)
}
}
export default OverlayNav
You can make a function with a if statement handle Opening and closing the overlay
Here is your code edited
class OverlayNav {
constructor() {
this.injectHTML();
this.hamburgerIcon = document.querySelector(".menu-icon");
this.events();
}
events() {
this.hamburgerIcon.addEventListener("click", () => this.overlayHandle());
}
overlayOpen() {
document.getElementById("myNav").style.width = "100%";
this.hamburgerIcon.classList.toggle("menu-icon--close-x");
}
overlayClose() {
document.getElementById("myNav").style.width = "0%";
}
overlayHandle() {
if (element.classList.contains("active")) {
this.overlayClose();
} else {
this.overlayOpen();
}
}
injectHTML() {
document.body.insertAdjacentHTML(
"beforeend",
`
<div id="myNav" class="overlay">
<p>My Overlay</p>
</div>
`
);
}
}
export default OverlayNav;
You can add a property that keeps track of the state of the nav bar.
constructor() {
this.injectHTML()
this.hamburgerIcon = document.querySelector(".menu-icon")
this.events()
this.overlayVisible=true;
}
Then add a method that toggles the state and calls the right open/close-method:
toggleOverlay() {
if (this.overlayVisible)
this.overlayOpen();
else
this.overlayClose();
this.overlayVisible=!this.overlayVisible;
}
Finally make the events method call toggleOverlay() instead of overlayOpen().
events() {
this.hamburgerIcon.addEventListener("click", () => this.toggleOverlay())
}
Alternativly, a pure HTML + CSS solution, using only the details element and the [open] CSS attribute selector.
.overlay > p {
padding: 1rem;
margin: 0;
display: flex;
flex-direction: column;
width: 25vw
}
.overlay summary {
padding: 1rem 0.5rem;
cursor: pointer;
max-height: 90vh;
overflow: auto;
font-size: 4em;
list-style: none;
}
.overlay[open] summary {
background: black;
color: white;
padding: 0.5rem;
font-size: 1em;
}
.overlay[open] {
position: fixed;
/* top: calc(50% - 25vw); */
left: calc(50% - 15vw);
outline: 5000px #00000090 solid;
border: 5px red solid;
border-radius: 0.5rem;
font-size: 1em
}
.overlay[open] summary::after {
content: '❌';
float: right;
}
<details class="overlay">
<summary>☰</summary>
<p>
Hello world!
</p>
</details>
I have made a modal in a component, the data works fine, it's dynamic which is perfect. Problem is, when I open it, it seems that whenever I click anywhere inside the modal, it closes it again.
The modal is managed using useState hook. I think the problem lies in my onClick calls further down. Any advise please?
const LeaveRequestsUnits = () => {
let [data, setData] = useState([]);
let [modalState, setModalState] = useState(false);
let modalOnOff = () => {
setModalState(!modalState);
};
let [selectedUnit, setSelectedUnit] = useState('');
let updateSelectedUnit = (item) => {
setSelectedUnit(item);
const getLeaveUnits = data.map((item) => {
// fct to update the modalState and display-block the modal
const openModal = (item) => {
updateSelectedUnit(item);
modalOnOff();
$('.modalBackground').css('display', 'block');
};
const modal = () => {
return (
<div>
<p>{selectedUnit.note}</p>
<p>{selectedUnit.start}</p>
<p>{selectedUnit.end}</p>
Google
<h1>Close</h1>
</div>
);
};
// display:none the modal if the modalState is false
if (!modalState) {
$('.modalBackground').css('display', 'none');
}
if (item.end >= today && item.approved !== false) {
return (
<div
className={unitColour}
key={item.reqID}
onClick={() => openModal(item)}
>
<div className='unitLeft'>
<img src={statusIcon} alt='Status Icon' id='statusIcon' />
</div>
<div className='unitMiddle'>
<p id='unitLeaveType'>{leaveTypeName}</p>
<p id='unitDate'>{startEndDate(item.start, item.end)}</p>
</div>
<div className='unitDivider'></div>
<div className='unitRight'>
<p id='unitDuration'>
{convertTimestamp(item.duration, item.type)}
</p>
</div>
{/* modal */}
<div className={`modalBackground modalShowing-${modalState}`}>
{modal()}
</div>
{/* end modal */}
</div>
);
}
});
return <div className='requestsContainer
CSS below:
.modalBackground {
display: none;
z-index: 10000;
width: 80vw;
height: 250px;
background-color: white;
border-radius: 15px;
position: absolute;
top: 10vh;
overflow: hidden;
color: black;
opacity: 0;
pointer-events: none;
cursor: auto;
}
.modalShowing-true {
/* display: none;
position: absolute;
top: 0;
width: 100%;
height: 100%;
background-color: black;
opacity: 0.3; */
opacity: 1;
pointer-events: auto;
}
When you define the modal component, you need to tell it to prevent clicks on it from bubbling up to the parent elements click listener that will try to close the modal.
const cancelClick = useEffect( (event) => {
event && event.stopPropagation();
}, []) // only need to create this function once
const modal = () => {
return (
<div onClick={cancelClick}>
<p>{selectedUnit.note}</p>
<p>{selectedUnit.start}</p>
<p>{selectedUnit.end}</p>
Google
<h1>Close</h1>
</div>
);
};
I would HIGHLY recommend you stop using jquery here as well. In this component its a super easy change, just remove the jquery calls to change the display css property on the backdrop and instead use the state variable to control showing that.
<div className={`modalBackground${!!modalState ? ' open' : ''}`}>
{modal()}
</div>
and then you can clean up the css for it. I dropped the display: none; style and went with transform: scale(0);. This gives more flexibility in how you decide how you want to show the modal (fade in would do nicely).
.modalBackground {
position: absolute;
top: 10vh;
z-index: 10000;
overflow: hidden;
width: 80vw;
height: 250px;
opacity: 0;
transform: scale(0);
background-color: white;
color: black;
border-radius: 15px;
cursor: auto;
/* here you can add a transition on both opacity and scale to make the modal animate in. */
}
.modalBackground.open {
opacity: 1;
transform: scale(1);
}
To get a better idea what i'm doing look here for my previous code that i try to make a little better >>Codepen
I want to have an array that i fill up with all the id's that i try to animate and with one function toggle the classes .open .closed on every id in the array.
so on an click add .open to #Hamburger, #Navigation, #Black-filter. and one second click remove .open and add .closed for those id's.
because i'm still learning javascript i want it to work in vanilla javascript so i understand the basics before im going on with jquery.
var hamburger = document.getElementById('Hamburger');
var navigation = document.getElementById('Navigation');
var blackFilter = document.getElementById('Black-filter');
var isOpen = true; // true or false
var animation = [h, s, b]; // #H #S #B
var open = "open"; // .open
var closed = "closed"; // .closed
function trigger() {
if (isOpen === true) {
animation.classList.add(open); // add .open to all id's
animation.classList.remove(closed); // remove .closed from all id's
} else {
animation.classList.add(closed);
animation.classList.remove(open);
}
isOpen = !isOpen; // toggles true to false
}
hamburger.addEventListener('click', trigger, false); // onclick toggle class
blackFilter.addEventListener('click', trigger, false); // onclick toggle class
body {
width: 100%;
}
#Hamburger {
height: 100px;
background: red;
width: 100px;
}
#Hamburger.open {
opacity: 0.5;
}
#Hamburger.closed {
opacity: 1;
}
#Navigation {
height: 100px;
background: blue;
width: 100px;
}
#Navigation.open {
opacity: 0.5;
}
#Navigation.closed {
opacity: 1;
}
#Black-filter {
height: 100px;
background: green;
width: 100px;
}
#Black-filter.open {
opacity: 0.5;
}
#Black-filter.closed {
opacity: 1;
}
<body>
<div id="Hamburger"></div>
<div id="Navigation"></div>
<div id="Black-filter"></div>
</body>
What you are looking for is:
var isOpen = true;
var hamburger = document.getElementById('Hamburger');
var navigation = document.getElementById('Navigation');
var blackFilter = document.getElementById('Black-filter');
var animatable = [hamburger, navigation, blackFilter];
var openClass = "open"; // .open
var closedClass = "closed"; // .closed
function trigger() {
if (isOpen) {
animatable.forEach(function (element) {
element.classList.add(openClass);
element.classList.remove(closedClass);
});
} else {
animatable.forEach(function (element) {
element.classList.add(closedClass);
element.classList.remove(openClass);
});
}
isOpen = !isOpen;
}
hamburger.addEventListener('click', trigger, false);
blackFilter.addEventListener('click', trigger, false);
Demo
There are a few things that need improvement.
First of all you are naming you variables rather poorly. Which is actually already one of your problems, first you say that
var b = document.getElementById('B');
and then later
var b = "closed";
So this needs to be fixed, use variable names that are descriptive so you will know what you are talking about when.
Last but not least you are trying to change the elements of that array a, not the array itself. So you need to access the elements by themselves, set their classes and then you are good to go e.g.:
for( var index in a ) {
if ( open === true ) {
a[index].classList.add(b);
a[index].classList.remove(c);
} else {
a[index].classList.add(c);
a[index].classList.remove(b);
}
open = !open;
Firstly ou don't need "open" AND "close" classes, only one would clearly simplify your code (and there is the "default" state).
Then, add a class for all your buttons, the easily manipulate them in JS and CSS (here the class ".btn");
// Directly get on array (a NodeList more precisely)
var buttons = document.getElementsByClassName('btn');
function toggleClass() {
// Loop to add or remove (toggle) the the '.open' class
for (var i=0; i<buttons.length; i++) {
buttons[i].classList.toggle('open');
}
}
// Loop to add event listener to all buttons
for (var i=0; i<buttons.length; i++) {
buttons[i].addEventListener('click', toggleClass, false);
}
.btn {
height: 100px;
width: 100px;
}
.btn.open {
opacity: 0.5;
}
#Hamburger { background: red; }
#Navigation { background: blue; }
#Black-filter { background: green; }
<div id="Hamburger" class="btn"></div>
<div id="Navigation" class="btn"></div>
<div id="Black-filter" class="btn"></div>
This is already way simpler. But you should have a parent element holding the opened/closes state, so you wouldn't loop in an array.
// Only need to manipulate one DOM node
var menu = document.getElementById('menu');
function toggleClass() {
menu.classList.toggle('open');
}
menu.addEventListener('click', toggleClass, false);
body {
width: 100%;
}
.btn {
height: 100px;
width: 100px;
}
.menu.open > .btn {
opacity: 0.5;
}
#Hamburger { background: red; }
#Navigation { background: blue; }
#Black-filter { background: green; }
<div class="menu" id="menu">
<div id="Hamburger" class="btn"></div>
<div id="Navigation" class="btn"></div>
<div id="Black-filter" class="btn"
</div>
Your event listener gets the event as the 1st argument. Use it to decide what to do:
function trigger(event) {// use event.target ... }
I am trying to create a link <a> with two different backgrounds, one a "Down Arrow" and one an "Up Arrow" depending on the classname arrow-up and arrow-down.
So when you click on the down arrow, the div named product-add-wrapper slides down, and the down arrow* becomes an **up arrow and vice versa.
The problem is, the .toggle + callback seems to work fine, it adds the desired class name and removes the desired class name, however, the background-image doesn't change (the down arrow doesn't become an up arrow).
Here is the html.
<span class="arrow-right">
<a class="updown arrow-down"> </a>
</span>
Here is the css.
.updown {
display: block;
margin-top: -1px;
height: 25px;
width: 25px;
}
.arrow-up {
background-image: url('../img/arrow-up.png');
}
.arrow-down {
background-image: url('../img/arrow-down.png');
}
And here is the javascript.
$('.updown').click(function() {
$('#product-add-wrapper').toggle('slow', function() {
var classname = $('.updown').attr('class');
if (classname === 'up arrow-down') {
$('.updown').removeClass('arrow-down').addClass('arrow-up');
} else {
$('.updown').removeClass('arrow-up').addClass('arrow-down');
}
});
});
Any ideas? Thanks in advance.
The classname will never be 'up arrow-down' you probably meant 'updown arrow-down'.
if (classname === 'up arrow-down') {
This would probably be better rewritten like this:
$('.updown').click(function() {
$('#product-add-wrapper').toggle('slow', function() {
if ($('.updown').hasClass('arrow-down')) {
$('.updown').removeClass('arrow-down').addClass('arrow-up');
} else {
$('.updown').removeClass('arrow-up').addClass('arrow-down');
}
});
});
Or better yet:
Js:
$('.updown').click(function() {
$('#product-add-wrapper').toggle('slow', function() {
$('.updown').toggleClass('arrow-up');
});
});
CSS:
.updown {
display: block;
margin-top: -1px;
height: 25px;
width: 25px;
background-image: url('../img/arrow-down.png');
color:green;
}
.arrow-up {
background-image: url('../img/arrow-up.png');
color:red;
}
HTML:
<span class="arrow-right">
<a class="updown"> aa</a>
</span>
<div id="product-add-wrapper">
aa
</div>
Jsfiddle: http://jsfiddle.net/kingmotley/K7274/1/
Here it is using the .click() event handler instead of toggle:
$('.updown').click(function (){
if ($(this).hasClass("arrow-down")){
$('#product-add-wrapper').slideUp("slow");
$(this).removeClass('arrow-down').addClass('arrow-up');
} else {
$('#product-add-wrapper').slideDown("slow");
$(this).removeClass('arrow-up').addClass('arrow-down');
};
});
HTML as follows:
<span class="arrow-right">
<a class="updown arrow-down"> </a>
</span>
<div id="product-add-wrapper">
... DIV CONTENT ...
</div>
Working demonstration: http://jsfiddle.net/9wxfJ/3/
This should work, using hasClass:
$('.updown').click(function() {
$('#product-add-wrapper').toggle('slow', function() {
if($('.updown').hasClass('arrow-down')) {
$('.updown').addClass('arrow-up');
$('.updown').removeClass('arrow-down');
} else {
$('.updown').addClass('arrow-down');
$('.updown').removeClass('arrow-up');
}
});
});
Fiddle: http://jsfiddle.net/RP294/
Personally, I'd go about this using one class instead of two since you know the element you're working with has a default state. So instead of having an .arrow-down class, just give that background to the .updown class and have the .arrow-up class use !important to overrule it when toggled:
HTML
<span class="arrow-right">
<a class="updown"> </a>
</span>
CSS
.updown {
display: block;
margin-top: -1px;
height: 25px;
width: 25px;
background-image: url('../img/arrow-down.png');
}
.arrow-up {
background-image: url('../img/arrow-up.png') !important;
}
This will also shorten your javascript code quite a bit:
$('body').on('click', '.updown', function(e){
var arrow = $(this);
$('#product-add-wrapper').toggle('slow', function() {
arrow.toggleClass('arrow-up');
});
});
Work fine for me
http://jsfiddle.net/Hzmxc/
$('.updowns').click(function() {
$('#product-add-wrapper .updown').toggle('slow', function() {
if ($('.updowns').hasClass('arrow-down')) {
$('.updowns').removeClass('arrow-down').addClass('arrow-up');
} else {
$('.updowns').removeClass('arrow-up').addClass('arrow-down');
}
});
});