Remove hover event after some time using plain JavaScript - javascript

I have a Flip cards, based on: https://www.w3schools.com/howto/howto_css_flip_card.asp.
They work well, nonetheless in mobile (since there's no mouse hover) it expects to tap out in order to the card flip to the unhover state. To prevent that, I want to remove hover event after a certain time has passed using javascript when previously pressed.
This is my current JS code:
let test = document.getElementById("flip-card");
// This handler will be executed only once when the cursor is hovered
test.addEventListener("mouseenter", function( event ) {
console.log('onHover was excecuted'); // debug thing
// reset the hover after a short delay
setTimeout(function() {
test.unbind('mouseenter mouseleave'); //this needs jQuery, is there anyway to do it with plain Js?
}, 3000);
}, false);
Thank you for your advice!
Append Information
I want to "unflip" the "flip-card" after certain time has passed, no matter if the cursor is still there.

I think what you're looking for is removeEventListener, but it has a catch. You can't remove handlers that are set using addEventListener('event', function() { ... }). You have to define your function, meaning your example would look like:
let test = document.getElementById("flip-card");
function handler( event ) {
console.log('onHover was excecuted'); // debug thing
// reset the hover after a short delay
setTimeout(function() {
test.unbind('mouseenter mouseleave'); //this needs jQuery, is there anyway to do it with plain Js?
}, 3000);
}
// This handler will be executed only once when the cursor is hovered
test.addEventListener("mouseenter", handler, false);
removeEventListener must receive the same capture argument that the addEventListener used to add the event in the first place. For example:
test.addEventListener("mouseenter", handler, false);
// Will be removed by
test.removeEventListener('mouseenter', handler, false);
test.removeEventListener('mouseenter', handler, { capture: false });
// But not by
test.removeEventListener('mouseenter', handler, true);
test.removeEventListener('mouseenter', handler, { capture: true });
You can read up more about it on MDN here.
Update
After discussing it more, it turns out the issue was more about changing the "active" state when on mobile vs on desktop.
To achieve this, we can stop using mouse* events and instead use pointer* events. They have the event.pointerType property, which can be either "mouse", "pen", or "touch"
Inside of our handler function, we'll add a conditional to look for pointerType === 'touch'.
function handler(event) {
if (event.pointerType === 'touch') {
// Do stuff
}
}
To avoid too much complexity, we want to leave all the styling up to the CSS you already wrote. All we need need to do is:
a. Mark the body as being "touch compatible".
b. Add a .active class to replace :hover styles.
Our updated function looks like this
function handler(event) {
if (event.pointerType === 'touch') {
event.preventDefault();
document.body.classList.add('pointer-active'); // arbitrary class name
element.classList.add('active');
}
}
Now we just need to remove the class after a certain amount of delay, which we can do with a setTimeout.
function handler(event) {
if (event.pointerType === 'touch') {
event.preventDefault();
document.body.classList.add('pointer-active'); // arbitrary class name
element.classList.add('active');
setTimeout(function() {
element.classList.remove('active');
}, 3000);
}
}
Then we add that back into your original example, it looks like this:
let test = document.getElementById("flip-card");
function handler( event ) {
console.log('onPointerDown was excecuted'); // debug thing
// If the pointer is touch, from mouse, pen, touch
if (event.pointerType === 'touch') {
event.preventDefault();
document.body.classList.add('pointer-active')
// add the active class
test.classList.add('active');
// Remove class after a delay
setTimeout(function() {
test.classList.remove('active');
}, 3000);
}
}
// This handler will be executed only once when the cursor is hovered
test.addEventListener("pointerout", handler, false);
Here's a codepen where you can mess around with it further.

Related

Dynamically changing a checkbox doesn't trigger onChange?

Note: jQuery is not an option.
I want to detect a change in the state of a checkbox, but the onChange event doesn't seem to fire when I do this:
document.getElementById('myCheckBox').addEventListener('change',function() {
console.log('Changed!');
});
document.getElementById('someLink').onClick = function() {
// toggle checkbox
document.getElementById('myCheckBox').checked = !document.getElementById('myCheckBox').checked;
};
When I click #someLink the change event is not fired. I could add another listener to #myLink, but then if I add other links that check the same box I have to add more listeners. I want one listener for a checkbox change event. Again, jQuery is not an option, I need vanilla JS.
EDIT: Sorry if I did not make this more clear, but I want to avoid adding complexity to each link that will check this box. So ideally (and maybe the answer is that this is impossible) I don't want to have to alter the link/click logic at all. I want to be able to change the .checked property anywhere in the code and have it be detected without additional logic at each change (if possible).
UPDATE:
Okay so there is apparently no way to do this nicely without altering the onClick logic, so (like some kind of animal) I ended up brute forcing the problem as follows:
function mySentinel() {
if(document.getElementById('myCheckBox').checked) {
console.log("I've been checked!");
return;
}
setTimeout("mySentinel()",100);
}
// then call this somewhere in the on document load section...
mySentinel();
You can add some sort of timeout if you want also:
function mySentinel(var i) {
if(document.getElementById('myCheckBox').checked) {
console.log("I've been checked!");
return;
}
if(i <= 0) {
console.log("Time out. Still not checked");
}
i--;
setTimeout("mySentinel("+i+")",100);
}
// then call this somewhere in the on document load section...
// 60 second timeout (some math can make the argument look nicer)
mySentinel(600);
That is correct, changing the value or checked state of an input programatically does not fire the event handlers.
You'll have to trigger the event as well with javascript
document.getElementById('myCheckBox').addEventListener('change',function() {
console.log('Changed!');
});
document.getElementById('someLink').onclick = function() {
var box = document.getElementById('myCheckBox')
box.checked = !box.checked;
if ("createEvent" in document) {
var evt = document.createEvent("HTMLEvents");
evt.initEvent("change", false, true);
box.dispatchEvent(evt);
} else {
box.fireEvent("onchange");
}
};
and note that's it's onclick (all lowercase)
FIDDLE
EDIT
I want to avoid adding complexity to each link that will check this box.
So ideally ... I don't want to have to alter the link/click logic at all.
I want to be able to change the .checked property anywhere in the code and have it be detected without additional logic at each change (if possible).
And that's not really possible, without using horrible hacks with intervals etc.
When the checked state is changed programatically the event handler isn't triggered at all, because that would really be a huge issue in most cases, and much harder to work around the opposite scenario, where you just trigger the event handler manually instead, and that is really the only way to do it.
Of course, you can make it a lot more convenient using classes and external function and such
document.getElementById('myCheckBox').addEventListener('change',function() {
console.log('Changed!');
});
var links = document.querySelectorAll('.someLinkClass');
for (var i = links.length; i--;) {
links[i].addEventListener('click', triggerChange, false);
}
function triggerChange() {
var box = document.getElementById('myCheckBox')
box.checked = !box.checked;
if ("createEvent" in document) {
var evt = document.createEvent("HTMLEvents");
evt.initEvent("change", false, true);
box.dispatchEvent(evt);
} else {
box.fireEvent("onchange");
}
};
and anytime you add a link, you just add that class
Change the checkbox
Change the checkbox again
Change the checkbox even more
etc.
If you want to add more links that will trigger the checkbox, create a class name for them and use getElementByClass('classname')
Use onclick in your html, not js. Example: <div onclick="doSomething()"></div>
Just use an if/else statement for the check/uncheck:
if(document.getElementById('myCheck').checked){document.getElementById("myCheck").checked = false;} else{document.getElementById("myCheck").checked = true;}
I think jQuery have a change event. And you don't use it in the good way.
Read this page: http://api.jquery.com/change/
and this: jQuery checkbox change and click event
When any change occurs in the checkbox, jQuery event function is called. And then you can write anything in this event function
You can invoke/trigger an event but its not as easy as it seems, especially when you have to deal with internet explorer.
The cleanest solution is to put your event into its own function, then call it where you need it.
function handleEvent(){
console.log('Changed!');
}
documentent.getElementById('myCheckBox').addEventListener('change',function() {
handleEvent()
});
document.getElementById('someLink').onClick = function() {
// toggle checkbox
document.getElementById('myCheckBox').checked = !document.getElementById('myCheckBox').checked;
handleEvent();
};

How can I tell if an event is being fired by the same event that created it?

Let's say I have the following events:
$(button).on('mouseup', function (event1) {
$(something).show();
$(document).on('mouseup', function (event2) {
$(something).hide();
});
});
So, it shows "something" when button is clicked and hides it when the document is clicked. How can I make sure the second evnet doesn't trigger in the same event that created it? right now the something will show and hide instantly (at least on firefox).
I need to do this without any globals of any kind, preferably.
How about this:
$(button).on('mouseup', function (event1) {
$(something).show();
event1.stopPropagation();
$(document).one('mouseup', function (event2) {
$(something).hide();
});
});
stopPropagation() will stop the event from going past the button (to the document).
one() will only run the event once and then go away... can be recreated again with another click on the button.
JSFiddle
Here's another solution that doesn't rely on timestamps:
$("button").on('mouseup', function (event1) {
$("#something").show();
setTimeout(function () {
$(document).one('mouseup', function (event2) {
$("#something").hide();
});
}, 0);
});
demo
Using setTimeout with a delay of 0 will make it execute the code as soon as it's finished with this event. Also note I'm using one rather than on because you only need this event handler one time and without it you will end up attaching unlimited numbers of event handlers, every single one of which will need processing when a mouseup fires anywhere on your page.
A less silly solution might look like this:
$("button").on('mouseup', function (event1) {
$("#something").show();
});
$(document).on('mouseup', function (event2) {
if(event2.target != $("button")[0]) {
$("#something").hide();
}
});
demo
Why don't you isolate the event like so:
$(button).on('mouseup', function (event1) {
$(something).show();
});
$(document).on('mouseup', function (event2) {
$(something).hide();
});
I ended up using the event.timeStamp property to check if the two events are distinct. Also added an unbind and a namespace to the document event to prevent event stacking.
$(button).on('mouseup', function (event1) {
$(something).show();
$(document).on('mouseup.'+someIdentifier, function (event2) {
if(event1.timeStamp != event2.timeStamp) {
$(something).hide();
$(document).unbind('mouseup.'+someIdentifier);
}
});
});

Toggling click handlers in Javascript

I have an HTML button to which I attach an event, using jQuery's bind(), like so:
$('#mybutton').bind('click', myFirstHandlerFunction);
In myFirstHandlerFunction, I'd like this handler to replace itself with a new handler, mySecondHandlerFunction, like this:
function myFirstHandlerFunction(e) {
$(this).unbind('click', myFirstHandlerFunction).bind('click', mySecondHandlerFunction);
}
In the second click handler, mySecondHandlerFunction, I'd like to toggle the button back to its original state: unbind the mySecondHandlerFunction handler and reattach the original handler, myFirstHandlerFunction, like so:
function mySecondHandlerFunction(e) {
$(this).unbind('click', mySecondHandlerFunction).bind('click', myFirstHandlerFunction);
}
This works great, except for one small detail: because the click event has not yet propagated through each of the button's click handlers, the click event is passed on to the button's next click handler, which happens to be the handler that was just bound in the previous handler. The end result is mySecondHandlerFunction being executed immediately after myFirstHandlerFunction is executed.
This problem can be easily solved by calling e.stopPropagation() in each handler, but this has the negative side-effect of cancelling any other click handlers that may have been attached independently.
Is there a way to safely and and consistently toggle between two click handlers, without having to stop the propagation of the click event?
Update: Since this form of toggle() was removed in jQuery 1.9, the solution below does not work anymore. See this question for
alternatives.
It looks like toggle() would solve your problem:
$("#mybutton").toggle(myFirstHandlerFunction, mySecondHandlerFunction);
The code above will register myFirstHandlerFunction and mySecondHandlerFunction to be called on alternate clicks.
Just use a boolean to toggle the functionality of the handler, there's no need to juggle which handler is listening:
$('#mybutton').bind('click', myHandlerFunction);
var first = true;
function myHandlerFunction(e) {
if(first){
// Code from the first handler here;
}else{
// Code from the second handler here;
}
first = !first; // Invert `first`
}
This solution is a hack, but it is short and sweet for your rough work:
$('#myButton').click(function() {
(this.firstClk = !this.firstClk) ? firstHandler(): secondHandler();
});
It's a hack because it's putting a new property directly onto this which is the click-target HTML DOM element, and that's maybe not best practice. However, it thus avoids creates any new globals, and it can be used unchanged on different buttons simultaneously.
Note that the first part of the ternary operation uses = and not == or ===, i.e. it's an assignment, not a comparison. Note also that the first time the button is clicked, this.firstClk is undefined but is immediately negated, making the first part of the ternary operation evaluate to true the first time.
Here's a working version:
$('#a > button').click(function() {(this.firstClk = !this.firstClk) ? a1(): a2();});
$('#b > button').click(function() {(this.firstClk = !this.firstClk) ? b1(): b2();});
function a1() {$('#a > p').text('one');}
function a2() {$('#a > p').text('two');}
function b1() {$('#b > p').text('ONE');}
function b2() {$('#b > p').text('TWO');}
div {display: inline-block;width: 10em;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="a"><p>not yet clicked</p><button>click</button></div>
<div id="b"><p>NOT YET CLICKED</p><button>CLICK</button></div>
I was looking at this today, and realized there was still not a way to do this without a global variable listed. I came up with the following to add locations to an ESRI basemap, but it would work generally too:
function addLocationClickHandler() {
var addLocationClick = overviewMap.on('click', function (event) {
addLocationClick.remove();
})
$('#locationAddButton').one('click', function (cancelEvent) {
addLocationClick.remove();
$('#locationAddButton').on('click', addLocationClickHandler)
});
}
$('#locationAddButton').on('click', addLocationClickHandler)
This should allow you to put something else in the section where you overwrite the click handler and not necessitate a global variable.
This would help add data-click-state attribute on your button
$(document).ready(function() {
$('#mybutton').on('click', function() {
if ($(this).attr('data-click-state') == 1) {
$(this).attr('data-click-state', 0)
myFirstHandlerFunction();
} else {
$(this).attr('data-click-state', 1)
mySecondHandlerFunction();
}
});
});
Like this:
$(this).bind('click', myMasterHandler);
handler = 0;
function myMasterHandler(e) {
if(handler == 0) {
myFirstHandler(e);
handler = 1;
} else {
mySecondHandler(e);
handler = 0;
}
}

Enable/Disable events of DOM elements with JS / jQuery

I stuck here with a little problem I have put pretty much time in which is pretty bad compared to its functionality.
I have tags in my DOM, and I have been binding several events to them with jQuery..
var a = $('<a>').click(data, function() { ... })
Sometimes I would like to disable some of these elements, which means I add a CSS-Class 'disabled' to it and I'd like to remove all events, so no events are triggered at all anymore. I have created a class here called "Button" to solve that
var button = new Button(a)
button.disable()
I can remove all events from a jQuery object with $.unbind. But I would also like to have the opposite feature
button.enable()
which binds all events with all handlers back to the element
OR
maybe there is a feature in jQuery that actually nows how to do that?!
My Button Class looks something similar to this:
Button = function(obj) {
this.element = obj
this.events = null
this.enable = function() {
this.element.removeClass('disabled')
obj.data('events', this.events)
return this
}
this.disable = function() {
this.element.addClass('disabled')
this.events = obj.data('events')
return this
}
}
Any ideas? Especially this rebind functionality must be available after disable -> enable
var a = $('<a>').click(data, function() { ... })
I found these sources that did not work for me:
http://jquery-howto.blogspot.com/2008/12/how-to-disableenable-element-with.html
http://forum.jquery.com/topic/jquery-temporarily-disabling-events
-> I am not setting the events within the button class
Appreciate your help.
$("a").click(function(event) {
event.preventDefault();
event.stopPropagation();
return false;
});
Returning false is very important.
Or you could write your own enable and disable functions that do something like:
function enable(element, event, eventHandler) {
if(element.data()[event].eventHandler && !eventHandler) { //this is pseudo code to check for null and undefined, you should also perform type checking
element.bind(event, element.data()[event]);
}
else (!element.data()[event] && eventHandler) {
element.bind(event, element.data()[event]);
element.data({event: eventHandler}); //We save the event handler for future enable() calls
}
}
function disable(element, event) {
element.unbind().die();
}
This isn't perfect code, but I'm sure you get the basic idea. Restore the old event handler from the element DOM data when calling enable. The downside is that you will have to use enable() to add any event listener that may need to be disable() d. Otherwise the event handler won't get saved in the DOM data and can't be restored with enable() again. Currently, there's no foolproof way to get a list of all event listeners on an element; this would make the job much easier.
I would go on this with different approach:
<a id="link1">Test function</a>
<a id="link2">Disable/enable function</a>
<script type="text/javascript">
$(function() {
// this needs to be placed before function you want to control with disabled flag
$("#link1").click(function(event) {
console.log("Fired event 1");
if ($(this).hasClass('disabled')) {
event.stopImmediatePropagation();
}
});
$("#link1").click(function() {
console.log("Fired event 2");
});
$("#link2").click(function() {
$("#link1").toggleClass("disabled");
});
});
</script>
This may not be what you require, since it may effect also other functions binded into this event later. The alternative may be to modify the functions itself to be more like:
$("#link1").click(function(event) {
console.log("Fired event 1");
if ($(this).hasClass('disabled')) {
return;
}
// do something here
});
if that is an option.
Instead of adding event handler to each element separately, you should use event delegation. It would make much more manageable structure.
http://www.sitepoint.com/javascript-event-delegation-is-easier-than-you-think/
http://cherny.com/webdev/70/javascript-event-delegation-and-event-hanlders
http://brandonaaron.net/blog/2010/03/4/event-delegation-with-jquery
This why you can just check for class(es) on clicked element , and act accordingly. And you will be able even to re-eanble them , jsut by changing the classes of a tag.
P.S. read the links carefully, so that you can explain it to others later. Event delegation is a very important technique.
You could use an <input type="button"> and then use $("#buttonID").addAttr('disabled', 'disabled'); and $("#buttonID").removeAttr('disabled');. Disabling and enabling will be handled by the browser. You can still restyle it to look like an anchor, if you need that, by removing backgrounds and borders for the button. Be aware though, that some margins and padding might still bugger u in some browsers.

How to send mouseover event to parent div?

I have a Div in which there is a text input, like this:
<div id="parentDive" class="parent">
<input id="textbox"></input>
</div>
I have assigned a functionality to the Div mouseover event and mouseout event by means of JQuery, but when I move my mouse over the text input, it calls mouseout event while it's in the DIV.
How to solve this problem? Should I send the event to the parent? How?
Use the jQuery .hover() method instead of binding mouseover and mouseout:
$("#parentDive").hover(function() {
//mouse over parent div
}, function() {
//mouse out of parent div
});
$("#textbox").hover(function() {
//mouse over textbox
}, function() {
//mouse out of textbox
});
Live test case.
The .hover() is actually binding the mouseenter and mouseleave events, which are what you were looking for.
I suggest to you to use .hover() not .mouseover() and .mouseout() here is a live working example
http://jsfiddle.net/DeUQY/
$('.parent').hover(function(){
alert('mouseenter');
},function(){
alert('mouseleave');
}
);
You need to use a few steps to make that work.
First, create the parent hover functions which would be enter() and exit(). These are setup using the hover() function. Then create the children enterChild() and exitChild() function. The children just change a flag that allows you to know whether a child is being hovered and thus the parent is still being considered to be hovered.
Whatever you want to do in the exit() function, you cannot do it immediately because the events arrive in the correct order for a GUI, but the wrong order for this specific case:
enter parent
exit parent
enter child
exit child
enter parent
exit parent
So when your exit() function gets called, you may be entering the child right after and if you want to process something when both the parent and child are exited, just acting on the exit() will surely be wrong. Note that the browser is written in such a way that an exit event always happens if an enter event happened. The only exception may be if you close the tab/window in which case they may forfeit sending more events.
So, in the parent exit() function we make use of a setTimeout() call to make an asynchronous call which will happen after the enter() function of a child happens. This means we can set a flag there and test it in the asynchronous function.
MyNamespace = {};
MyNamespace.MyObject = function()
{
var that = this;
// setup parent
jQuery(".parentDiv").hover(
function()
{
that.enter_();
},
function()
{
that.exit_();
});
// setup children
jQuery(".parentDiv .children").hover(
function()
{
that.enterChild_();
},
function()
{
that.exitChild_();
});
}
// couple variable members
MyNamespace.MyObject.prototype.parentEntered_ = false;
MyNamespace.MyObject.prototype.inChild_ = false;
MyNamespace.MyObject.prototype.enter_ = function()
{
// WARNING: if the user goes really fast, this event may not
// happen, in that case the childEnter_() calls us
// so we use a flag to make sure we enter only once
if(!this.parentEntered_)
{
this.parentEntered_ = true;
... do what you want to do when entering (parent) ...
}
};
// NO PROTOTYPE, this is a static function (no 'this' either)
MyNamespace.MyObject.realExit_ = function(that) // static
{
if(!that.inChild_)
{
... do what you want to do when exiting (parent) ...
that.parentEntered_ = false;
}
};
MyNamespace.MyObject.prototype.exit_ = function()
{
// need a timeout because otherwise the enter of a child
// does not have the time to change inChild_ as expected
setTimeout(MyNamespace.MyObject.realExit_(this), 0);
};
// detect when a child is entered
MyNamespace.MyObject.prototype.enterChild_ = function()
{
this.inChild_ = true;
this.enter_(); // in case child may be entered directly
};
// detect when a child is exited
MyNamespace.MyObject.prototype.exitChild_ = function()
{
this.inChild_ = false;
// We cannot really do this, although in case the child
// is "exited directly" you will never get the call to
// the 'exit_()' function; I'll leave as an exercise for
// you in case you want it (i.e. use another setTimeout()
// but save the ID and clearTimeout() if exit_() is not
// necessary...)
//this.exit_()
};

Categories

Resources