Javascript - call one function for same element different event? - javascript

I am trying to make this more abstract. I need to find a way to use one function for the same element and different events. I would also like to be able to pass in an element argument but I know I can't do this with a callback. This is what I have thus far:
const divElement = document.querySelector('#test');
divElement.addEventListener(
'mouseenter',
(event) => {
divElement.classList.add('shadow');
console.log(event);
},
false
);
divElement.addEventListener(
'mouseleave',
(event) => {
divElement.classList.remove('shadow');
console.log(event);
},
false
);

Use CSS instead. Never use JS for what can be achieved in CSS.
#test {
background-color: white;
padding: 30px;
transition: all 0.4s ease;
}
#test:hover {
box-shadow: 0 0 10px #000;
}
<div id="test">
Lorem ipsum dolor sit amentur.
</div>
If for some reason you didn't reveal you need it to be event-listener-based, here's what I would do:
const divElement = document.querySelector('#test');
function handleMouseAction({type}) {
this.classList.toggle('shadow', type === 'mouseenter');
}
divElement.addEventListener('mouseenter', handleMouseAction, false);
divElement.addEventListener('mouseleave', handleMouseAction, false);
#test {
padding: 30px;
transition: all 0.4s ease;
}
.shadow {
box-shadow: 0 0 10px #000;
}
<div id="test">
Lorem ipsum dolor sit amentur.
</div>

You can write a helper function and call that from the two callbacks:
const divElement = document.querySelector('#test');
function handleEvent(event, action) {
divElement.classList[action]('shadow');
console.log(event);
}
divElement.addEventListener('mouseenter', (event) => handleEvent(event, 'add'), false);
divElement.addEventListener('mouseleave', (event) => handleEvent(event, 'remove'), false);
Alternatively, you can use partial application with closures to create the two callbacks from one abstract function:
const divElement = document.querySelector('#test');
function makeEventHandler(action) {
return (event) => {
divElement.classList[action]('shadow');
console.log(event);
};
}
divElement.addEventListener('mouseenter', makeEventHandler('add'), false);
divElement.addEventListener('mouseleave', makeEventHandler('remove'), false);
Of course #Wyck is right and in this particular example, you should do everything with CSS only :-)

My way to do that:
const divElement = document.querySelector('#test');
const events = ["mouseenter", "mouseleave"]
events.forEach(event => {
divElement.addEventListener(event, (e) => {
// TODO put logic here.
divElement.classList.add('shadow');
console.log(event);
})
})
Another way to do that, by using onmouseenter="" tag and onmouseleave="" tag, and letting them use the same callback.
const callback = (element) => {
element.classList.add("shadow");
}
.shadow {
display: block;
box-shadow: 0 0 10px black;
}
<div onmouseenter="callback(this)" onmouseleave="callback(this)">Hello World</div>

Related

JavaScript: Get ID from currently dragged CLASS item

My application has multiple stones:
-> "let dragStone"
and one container where one of these stones can be placed:
-> "let putCircleNextStoneField"
I want to append the container (parent node) with the stone that is dragged onto it (child node).
The Error code is: "Parameter 1 is not of type 'Node'".
I know that the parameter isn't working beacause the dragStone variable isn't just a reference to the ID but an array-like object of all child elements which have the CLASS name: ".stone".
How do I get the reference to the id of the stone that is currently dragged?
function dragFromStoneFieldToNextStoneField() {
let dragStone = document.querySelector("#stoneField").querySelectorAll(".stone");
let putCircleNextStoneField = document.querySelector("#nextStoneField");
dragStone.forEach(stone => {
stone.classList.add("cursorPointer");
});
dragStone.forEach(stone => {
stone.addEventListener("dragstart", () => {
});
});
putCircleNextStoneField.addEventListener("dragover", e => {
e.preventDefault();
putCircleNextStoneField.classList.add("draggedOver");
putCircleNextStoneField.appendChild(dragStone); //ERROR IN THIS LINE
});
putCircleNextStoneField.addEventListener("dragleave", e => {
putCircleNextStoneField.classList.remove("draggedOver");
});
putCircleNextStoneField.addEventListener("drop", e => {
putCircleNextStoneField.classList.remove("draggedOver");
});
}
dragFromStoneFieldToNextStoneField();
I came up with a solution:
function dragFromStoneFieldToNextStoneField() {
let dragStone = document.querySelector("#stoneField").querySelectorAll(".stone");
let putCircleNextStoneField = document.querySelector("#nextStoneField");
let currentStone;
dragStone.forEach(stone => {
stone.classList.add("cursorPointer");
});
dragStone.forEach(stone => {
stone.addEventListener("dragstart", () => {
currentStone = stone;
});
});
putCircleNextStoneField.addEventListener("dragover", e => {
e.preventDefault();
putCircleNextStoneField.classList.add("draggedOver");
putCircleNextStoneField.appendChild(currentStone);
});
putCircleNextStoneField.addEventListener("dragleave", e => {
putCircleNextStoneField.classList.remove("draggedOver");
});
putCircleNextStoneField.addEventListener("drop", e => {
putCircleNextStoneField.classList.remove("draggedOver");
});
}
dragFromStoneFieldToNextStoneField();
I think that referencing the node is better but I'll post here in the case someone in the future need a example of setData and getData:
function receive(event) {
const sourceId = event.dataTransfer.getData("text");
// find the element and clone it
const element = document.getElementById(sourceId).cloneNode(true);
const container = document.querySelector(".target");
container.appendChild(element);
}
function onDrag(event) {
event.dataTransfer.setData("text", event.target.id);
}
function allowDrop(event) {
event.preventDefault();
}
.target {
padding: 12px;
box-sizing: border-box;
border: 1px dashed #2a2;
border-radius: 4px;
}
.container {
margin: 20px 0;
display: flex;
}
.source {
background-color: #f8f8ff;
box-sizing: border-box;
padding: 8px;
margin: 8px;
color: #08c;
}
.source:hover {
background-color: #f0f0ff;
}
<div class="target" ondrop="receive(event)" ondragover="allowDrop(event)">
drop here
</div>
<div class="container">
<div id="item-104" class="source" draggable="true" ondragstart="onDrag(event)">
draggable #104
</div>
<div id="item-208" class="source" draggable="true" ondragstart="onDrag(event)">
draggable #208
</div>
<div id="item-37" class="source" draggable="true" ondragstart="onDrag(event)">
draggable #37
</div>
</div>

Function running twice using setdelay

My function is running twice , I tried using boolean value to stop it but it doesn't work, here is my code :
window.onload = function(){
setTimeout(addbutton, 2940) // Please increase it if it doesn't work, it doesn't work well if your connection is too long
};
function addbutton(){
var hreflink = "/admin/users/delete/"+id;
var Reportuser = document.getElementsByClassName("sg-button sg-button--solid-light sg-button--full-width");
var x = Reportuser.length-1
Reportuser[x].insertAdjacentHTML('beforebegin', '<button id=button class="sg-button sg-button--solid-mint sg-button--small sg-button"><span class="sg-button__text">Delete</span></button>');
console.log("%cButton added","color: blue; font-size: 20px");
function myFunction() {
window.location.href = hreflink;
}
document.getElementById("button").addEventListener("click", myFunction);
}
Refactored and converted your code to a minimal reproducable example using event delegation. The timeout works, once. If you are experiencing problems with connection times, maybe you should check async functions
setTimeout(addbutton, 500);
document.addEventListener("click", handle);
function handle(evt) {
if (evt.target.dataset.clickable) {
console.clear();
return console.log("Yes! It's clicked!");
}
return true;
}
function addbutton() {
[...document.querySelectorAll(".sg-button")].pop()
.insertAdjacentHTML("beforeend", `<button data-clickable="1">Delete</button>`);
console.log("button added to last div.sg-button ...");
}
.sg-button {
margin-bottom: 1.5rem;
border: 1px solid #999;
padding: 0.2rem;
width: 5rem;
text-align: center;
}
.sg-button button {
display: block;
margin: 0 auto;
}
<div class="sg-button sg-button--solid-light sg-button--full-width">1</div>
<div class="sg-button sg-button--solid-light sg-button--full-width">2</div>

Execute code after the lack or eventual completion of a CSS transition

I'm trying to execute some code immediately after a CSS transition finishes. The problem is that in certain cases (not determined by me), the transition does not have to occur. How do I know when to expect a transition? I know about the transitionrun and transitionstart events and I tried to use them, but they don't do the job:
function animateIn (elem, action) {
var expecting = false
setTimeout(() => {
console.log('timeout', expecting)
}, 0)
window.requestAnimationFrame(() => {
console.log('raf', expecting)
})
elem.addEventListener('transitionrun', () => {
expecting = true
console.log('transitionrun')
})
elem.addEventListener('transitionstart', () => {
expecting = true
console.log('transitionstart')
})
elem.addEventListener('transitionend', () => {
console.log('transitionend')
})
action()
}
var elem = document.getElementById('main')
elem.addEventListener('click', () => {
animateIn(elem, () => {
elem.classList.remove('is-enter')
})
})
#main {
background: red;
width: 80px;
height: 80px;
transition: background 0.5s ease;
}
#main.is-enter {
background: blue;
}
<div id="main" class="is-enter"></div>
As you can see, I want to use the expecting variable to check whether a transition will start after whatever happens in the action function. And that's what I basically want - to check if a certain action (the removal of a class in this case) causes a transition to run.
My idea was to use requestAnimationFrame or setTimeout to wait for a little bit and if transitionrun or transitionstart has fired in the meantime - then a transition is expected, otherwise - it's not.
The problem with that is the fact that both requestAnimationFrame and setTimeout run before transitionrun and transitionstart had the chance to fire (at least in Chrome). So it appears that the transition takes longer to start than one render loop? Why is that?
One workaround is to use an arbitrary timeout that gives the transition enough time to eventually start:
function checkTransition (elem, action) {
return new Promise ((resolve, reject) => {
var willTransition = false
var startHandler = function () {
willTransition = true
elem.addEventListener('transitionend', function endHandler () {
elem.removeEventListener('transitionend', endHandler)
resolve(true)
})
}
elem.addEventListener('transitionstart', startHandler)
setTimeout(() => {
elem.removeEventListener('transitionstart', startHandler)
if (!willTransition) {
resolve(false)
}
}, 100)
action()
})
}
var elem = document.getElementById('main')
elem.addEventListener('click', () => {
checkTransition(elem, () => {
elem.classList.remove('is-enter')
}).then(status => {
console.log('had transition:', status)
})
})
#main {
background: red;
width: 80px;
height: 80px;
transition: background 0.5s ease;
}
#main.is-enter {
background: blue;
}
<div id="main" class="is-enter"></div>
When you click the box for the first time, the Promise waits for the transition to finish and resolves with true to signal that a transition has happened. If you click it more times after that, it resolves with false because no transition occurred. That's exactly what I need.
The problem is setTimeout. That 100ms wait is completely arbitrary and very error-prone, I suppose. Is there a better way to capture this? I need some sort of way to fire code immediately after a transitionstart might occur. I was hoping that requestAnimationFrame would do this but it doesn't. Why?
It appears to work in Chrome (latest), Firefox (latest) and IE11 with a double requestAnimationFrame, even for delayed transitions:
function checkTransition (elem, action) {
return new Promise ((resolve, reject) => {
var willTransition = false
var startHandler = function () {
willTransition = true
elem.addEventListener('transitionend', function endHandler () {
elem.removeEventListener('transitionend', endHandler)
resolve(true)
})
}
elem.addEventListener('transitionrun', startHandler)
elem.addEventListener('transitionstart', startHandler)
window.requestAnimationFrame(() => {
window.requestAnimationFrame(() => {
elem.removeEventListener('transitionrun', startHandler)
elem.removeEventListener('transitionstart', startHandler)
if (!willTransition) {
resolve(false)
}
})
})
action()
})
}
function bind (elem) {
elem.addEventListener('click', () => {
checkTransition(elem, () => {
elem.classList.remove('is-enter')
}).then(status => {
console.log('had transition:', status)
})
})
}
bind(document.getElementById('main'))
bind(document.getElementById('delayed'))
.target {
display: inline-block;
width: 80px;
height: 80px;
margin-right: 5px;
background: red;
}
.target.is-enter {
background: blue;
}
#main {
transition: background 0.5s ease;
}
#delayed {
transition: background 0.5s ease 0.4s;
}
<div id="main" class="target is-enter"></div>
<div id="delayed" class="target is-enter"></div>
I think it's far better than an arbitrary timeout but I still have doubts whether it's reliable. To answer that, I think we need to answer first why one requestAnimationFrame call is not enough in Chrome.
Edit: This trick doesn't seem to work in Chrome on mobile.

Make a background fade in every time "onclick"

I would like the DIV to show a confirmed message where the css effect "animation" or "fade out" is activated with each click. It works fine on the first click, but not on the clicks that follow.
function clientedetail() {
document.getElementById("guardadoC").innerHTML = "Guardado.";
document.getElementById("guardadoC").style.cssText = "animation: background-fade 3s;padding:5px;";
}
#keyframes background-fade {
0% {
background-color: green;
}
100% {
background-color: none;
}
}
<input type="button" onclick="clientedetail()"></input>
<div id="guardadoC"></div>
You can add a addEventListener('animationend', function() { ... }); to reset the animation so you can run it again.
It's also a good idea to keep your CSS into your CSS file and not write it as a strings in JavaScript. Now, we are adding a class to the element to do what we want.
function clientedetail() {
var el = document.getElementById("guardadoC");
el.innerHTML = "Guardado.";
el.classList.add("animating");
//This function runs when the CSS animation is completed
var listener = el.addEventListener('animationend', function() {
el.classList.remove("animating");
//this removes the listener after it runs so that it doesn't get re-added every time the button is clicked
el.removeEventListener('animationend', listener);
});
}
#keyframes background-fade {
0% {
background-color: green;
}
100% {
background-color: none;
}
}
#guardadoC {
padding:5px;
}
#guardadoC.animating {
animation: background-fade 3s;
}
<button type="button" onclick="clientedetail()">click me</button>
<div id="guardadoC"></div>
You can use the animationend event to reset the animation.
The animationend event is fired when a CSS Animation has completed
(but not if it aborts before reaching completion, such as if the
element becomes invisible or the animation is removed from the
element).
You'll notice in this demo that I'm not using anonymous functions. With anonymous functions, we end up redefining the function over and over, which is not what you want regarding performance. Using a functional reference, we declare a function once and tie an event to it.
const btn = document.querySelector(".myButton");
const guardadoC = document.getElementById("guardadoC");
btn.addEventListener("click", clientedetail);
function clientedetail() {
guardadoC.innerHTML = "Guardado.";
guardadoC.style.cssText = "animation: background-fade 3s;padding:5px;";
}
function resetAnimation() {
guardadoC.innerHTML = "";
guardadoC.style.cssText = "";
}
guardadoC.addEventListener("animationend", resetAnimation);
#keyframes background-fade {
0% {
background-color: green;
}
100% {
background-color: none;
}
}
<input type="button" class="myButton">
<div id="guardadoC"></div>
jsFiddle
More about animationend
You could recreate the element each time you click the button. This will be a complete reset, and so it will even work when you interrupt the previous animation.
function clientedetail() {
var elem = document.getElementById("guardadoC");
var newElem = elem.cloneNode(true);
elem.parentNode.replaceChild(newElem, elem);
newElem.innerHTML = "Guardado.";
newElem.style.cssText = "animation: background-fade 3s;padding:5px;";
}
#keyframes background-fade {
0% {
background-color: green;
}
100% {
background-color: none;
}
}
<input type="button" onclick="clientedetail()"></input>
<div id="guardadoC"></div>
Trigger it based on class if you can, the way you're doing it it will only do it once.
Or you can destroy the element and re-create it kinda like this.
function clientedetail() {
var element = document.getElementById("guardadoC");
if (typeof(element) != 'undefined' && element != null)
{
document.getElementById("guardadoC").remove();
var remakeDiv = document.createElement("div");
remakeDiv.setAttribute("id", "guardadoC");
document.body.appendChild(remakeDiv)
}
document.getElementById("guardadoC").innerHTML = "Guardado.";
document.getElementById("guardadoC").style.cssText = "animation: background-fade 3s;padding:5px;";
}

How do I clear this setInterval inside a function?

Normally, I’d set the interval to a variable and then clear it like var the_int = setInterval(); clearInterval(the_int); but for my code to work I put it in an anonymous function:
function intervalTrigger() {
setInterval(function() {
if (timedCount >= markers.length) {
timedCount = 0;
}
google.maps.event.trigger(markers[timedCount], "click");
timedCount++;
}, 5000);
};
intervalTrigger();
How do I clear this? I gave it a shot and tried var test = intervalTrigger(); clearInterval(test); to be sure, but that didn’t work.
Basically, I need this to stop triggering once my Google Map is clicked, e.g.
google.maps.event.addListener(map, "click", function() {
//stop timer
});
The setInterval method returns a handle that you can use to clear the interval. If you want the function to return it, you just return the result of the method call:
function intervalTrigger() {
return window.setInterval( function() {
if (timedCount >= markers.length) {
timedCount = 0;
}
google.maps.event.trigger(markers[timedCount], "click");
timedCount++;
}, 5000 );
};
var id = intervalTrigger();
Then to clear the interval:
window.clearInterval(id);
// Initiate set interval and assign it to intervalListener
var intervalListener = self.setInterval(function () {someProcess()}, 1000);
function someProcess() {
console.log('someProcess() has been called');
// If some condition is true clear the interval
if (stopIntervalIsTrue) {
window.clearInterval(intervalListener);
}
}
the_int=window.clearInterval(the_int);
Simplest way I could think of: add a class.
Simply add a class (on any element) and check inside the interval if it's there. This is more reliable, customisable and cross-language than any other way, I believe.
var i = 0;
this.setInterval(function() {
if(!$('#counter').hasClass('pauseInterval')) { //only run if it hasn't got this class 'pauseInterval'
console.log('Counting...');
$('#counter').html(i++); //just for explaining and showing
} else {
console.log('Stopped counting');
}
}, 500);
/* In this example, I'm adding a class on mouseover and remove it again on mouseleave. You can of course do pretty much whatever you like */
$('#counter').hover(function() { //mouse enter
$(this).addClass('pauseInterval');
},function() { //mouse leave
$(this).removeClass('pauseInterval');
}
);
/* Other example */
$('#pauseInterval').click(function() {
$('#counter').toggleClass('pauseInterval');
});
body {
background-color: #eee;
font-family: Calibri, Arial, sans-serif;
}
#counter {
width: 50%;
background: #ddd;
border: 2px solid #009afd;
border-radius: 5px;
padding: 5px;
text-align: center;
transition: .3s;
margin: 0 auto;
}
#counter.pauseInterval {
border-color: red;
}
<!-- you'll need jQuery for this. If you really want a vanilla version, ask -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p id="counter"> </p>
<button id="pauseInterval">Pause/unpause</button></p>

Categories