Custom "mouseenter" function doesn't fire - javascript

Here's the JSFiddle.
I'm trying to make mouseenter work on Chrome, Firefox, etc. using the following function:
var addMouseenter = (function () {
var contains = function (parent, elem) {
return parent.contains ? parent.contains(elem) :
!!(parent.compareDocumentPosition(elem) & 16);
},
wrap = function (elem, method) {
return function (e) {
if (elem === e.target && !contains(elem, e.relatedTarget)) {
method.call(elem, e);
}
};
};
return function (elem, listener) {
var listener2 = wrap(elem, listener);
elem.addEventListener('mouseover', listener2, false);
};
}());
Everything worked fine until I ran into this specific situation:
Element A has one of these custom mouseenter listeners
Element A contains Element B
Element B is right up against the edge of Element A
You enter Element A at that same edge
My expectation was that the mouseover event would be triggered on Element B and bubble up to Element A. However, that does not appear to be the case. I tested with Chrome 13 and Firefox 3.6 and got the same result. Did I mess something up?

If you don't oppose using jQuery:
$(document).ready(function() {
$('#first').mouseover(function (e) {
if ($(e.target).attr('id') != 'second') {
alert('hello');
}
});
});
Tried that in your JSFiddle and it works:
when you enter the green square it doesn't fire; when you enter red square from outside it fires; when you enter red square from green square it fires. That's what you wanted right?
new JSFiddle
Or keeping your javascript approach:
// Misc set-up stuff
var greet = function () { alert('Hi, my name is "' + this.id + '."'); },
first = document.getElementById('first'),
second = document.getElementById('second');
// The Actual Function
var addMouseenter = (function () {
var contains = function (parent, elem) {
return parent.contains ? parent.contains(elem) :
!!(parent.compareDocumentPosition(elem) & 16);
},
wrap = function (elem, method) {
return function (e) {
//if (elem === e.target && !contains(elem, e.relatedTarget)) {
if (elem === e.target && (e.target != second)) {
method.call(elem, e);
}
};
};
return function (elem, listener) {
var listener2 = wrap(elem, listener);
elem.addEventListener('mouseover', listener2, false);
};
}());
// GOGOGO
addMouseenter(first, greet);
http://jsfiddle.net/AUc88/

The reason my custom function wasn't firing is because it didn't work.
I updated the fiddle showing that all is as it should be.
My mistake was only checking to see if e.target was the same as the element I had attached the listener to. What I needed to be checking was if they were the same or if e.target was a child of the element.
When you mouse over the two squares really quickly, it only registers the mouseover event on the inner one, and because my listener was attached to the outer one, the elem === e.target test was failing.
So I changed the if code in the wrap function to this:
if ((elem === e.target || contains(elem, e.target)) &&
!contains(elem, e.relatedTarget)) {
e.stopPropagation();
method.call(elem, e);
}

Related

Event Delegation / Attaching events to dynamically created elements using Vanilla JavaScript

I am in the process of converting a large script from jQuery to JavaScript. This was code that I didn't write myself but that I forked from a project on GitHub.
I've consulted W3Schools, the official documentation and this website as a reference.
http://youmightnotneedjquery.com/
One of the parts I'm trying to convert into JavaScript is the following.
$('body').on('click','.vb',function(){
exportVB(this.value);
});
According to the aforementioned link,
$(document).on(eventName, elementSelector, handler);
converts to this
document.addEventListener(eventName, function(e) {
// loop parent nodes from the target to the delegation node
for (var target = e.target; target && target != this; target = target.parentNode) {
if (target.matches(elementSelector)) {
handler.call(target, e);
break;
}
}
}, false);
My attempt is as follows
/*document.addEventListener('click',function(e) {
for (var target = e.target; target && target != this; target = target.parentNode) {
if (target.matches('.vb')) {
exportVB.call(target,e);
break;
}
}
}, false);*/
That evidently didn't work so I did a Google search that brought me to this StackOverflow solution
Attach event to dynamic elements in javascript
document.addEventListener('click',function(e){
if(e.target && e.target.id== 'brnPrepend'){
//do something
}
});
//$(document).on('click','#btnPrepend',function(){//do something})
Testing that gave me this idea. I commented it out because that apparently didn't work either.
/*document.addEventListener('click',function(e) {
if (e.target && e.target.className == 'vb') {
exportVB(this.value);
}
});*/
Just for reference, the original jQuery function works well.
I solved it.
document.body.addEventListener('click',function(e) {
for (var target = e.target; target && target != this; target = target.parentNode) {
if (target.matches('.vb')) {
exportVB(target.value);
break;
}
}
});
I can't explain how it worked because I didn't write the original code in the first place. But there were two things I change.
exportVB.call(target.e) to exportVB(target.value)
Removing the false as the last argument.
Rather than iterating over each parent element manually, consider using .closest instead, which will return the ancestor element (or the current element) which matches a selector:
document.querySelector('button').addEventListener('click', () => {
document.body.insertAdjacentHTML('beforeend', '<span class="vb">some span </span>');
});
document.body.addEventListener('click', (e) => {
if (e.target.closest('.vb')) {
console.log('vb clicked');
}
});
<button>add span</button>

How to catch clicks on multiple images?

I have an iOS uiwebview with multiple imagemaps that I need to catch clicks on, so I can handle scaling on different iOS devices. The click handler I install works on the first image, but not on subsequent images. How do I make the click handler work on multiple images? The relevant code is below:
$.fn.imageMapSetup2 = function () {
$('img').each(function () {
if (typeof ($(this).attr('usemap')) == 'undefined') {
return;
}
var img = $(this);
// add click handler
img.on('click', function (event) {
img.imgClick(event);
});
});
};
$.fn.imgClick = function (mouseDown) {
mouseDown.preventDefault();
var $img = this;
var map = $img.attr('usemap').replace('#', '');
$('map[name="' + map + '"]').find('area').each(function () {
var $this = $(this),
coords = $this.attr('coords').split(',');
// lots of scaling code omitted
if (mouseX >= left && mouseX <= right &&
mouseY >= top && mouseY <= bottom) {
window.location = $this.attr('href');
}
});
};
FYI I have debugged the code in Safari and function imgClick() is not getting called for the second and subsequent images.
Add a click event listener to the parent element of the images. This could be the body element. Pass the event as an argument. Then, check the event, and use that variable to make changes to your image.
document.addEventListener("click", function (event) {
if (!event.target.tagName === "img") return;
if (typeof event.target.getAttribute("usemap") == "undefined") {
return;
}
imgClick(event);
});

Why is the focus handler getting executed with the wrong parameter?

I have a focus handler on a textfield:
$("#input").on("focus", (e) => {
// do some stuff
});
When I right-click, however, I don't want that focus handler to be executed, so I did:
$("#input").on("mousedown", (e) => {
if (e.button === 2) { // right click
e.preventDefault();
}
});
However, that also prevents the textfield from ever getting focus when I right-click. I still want it to get focus, I just don't want the handler to execute, so I triggered the handler manually:
$("#input").on("mousedown", (e) => {
if (e.button === 2) { // right click
e.preventDefault();
$("#input").trigger("focus", true);
}
});
$("input").on("focus", (e, someParam) => {
if (someParam) return;
// do some stuff
});
This way, the textfield gets focus, but we immediately return out of the handler.
The problem I noticed is that the first time I trigger the focus handler, someParam is undefined and we end up executing do some stuff. For all subsequent right-clicks, someParam is true.
I commented out the line that triggers the focus handler, and indeed, the focus handler is never executed, because we call preventDefault, so it seems that the first execution of the handler necessarily comes from $("#input").trigger("focus", true);. So why then is someParam undefined if I'm passing in true as the extra parameter?
JsFiddle. Tested in Chrome.
This appears to be a current issue with jQuery. See this github issue.
As a workaround, try the following:
var a = $("#a");
var _focusData = null;
var focusEvent = (e) => {
if (_focusData) {
_focusData = null;
return;
}
_focusData = null;
var t = $("textarea").first();
t.val(t.val() + "\nfocus");
};
a.on("mousedown", (e) => {
if (e.button === 2) {
e.preventDefault();
var t = $("textarea").first();
t.val(t.val() + "\n" + e.button);
_focusData = true;
a.trigger("focus");
}
});
a.on("focus", focusEvent);
After doing a lot more research, including trying to trigger custom events with $.Event, it seems like your best course of action is to either use stack traces, pollute the global scope, or downgrade your jQuery version.
I found another solution besides the comment from CBroe (to just perform the logic in an else statement):
Use a named function as our mouse down handler, then examine the stack trace.
var a = $("#a");
a.on("mousedown", onMouseDown);
function onMouseDown(e) {
if (e.button === 2) {
e.preventDefault();
e.stopImmediatePropagation()
var t = $("textarea").first();
t.val(t.val() + "\n" + e.button);
a.trigger("focus", true);
}
}
a.on("focus", (e, someParam) => {
var stackTrace = getStackTrace();
if(stackTrace.indexOf("onMouseDown") >= 0) return;
var t = $("textarea").first();
t.val(t.val() + "\nfocus");
console.log(someParam);
console.trace();
});
var getStackTrace = function() {
var obj = {};
if(Error.captureStackTrace) { //Chrome (IE/Edge? Didn't test)
Error.captureStackTrace(obj, getStackTrace);
}
else { //Firefox
obj = Error();
}
return obj.stack;
};
https://jsfiddle.net/bjj56eua/4/
As I was typing this up, FrankerZ posted an answer which looks much nicer. I suggest doing that. This was a dirty hack involving string parsing, but it works. It just isn't a good idea.

how to prevent tab in jquery?

can we stop prevent blur or tabbing for 5 second in input field.then after 5 second user can tab from one field to another.I use off and on function but it is not working .here is my code
http://jsfiddle.net/GV3YY/99/
$("input").off("blur");
setTimeout(function(){
$("input").on("blur");
},5000)
You need to "lock" the inputs when they is focused and use setTimeout to "unlock" it after 5 seconds. A naive implementation could look something like this: https://jsfiddle.net/my7wk6gj/2/
Update: Now pseudo prevents bluring by click. The blur still happens, but focus is returned to the original input until the 5 seconds have passed. I couldn't get event.stopImmediatePropagation to work for blur, so this is the next best thing...
var lockInput = false;
var focusTarget = null;
var lockTimeout = null;
$('input').on('focus', function (e) {
if (lockTimeout) {
return;
}
lockInput = true;
lockTimeout = setTimeout(function () { lockInput = false; lockTimeout = null }, 5000)
}).on('keydown', function (e) {
if (e.keyCode === 9 && lockInput) {
e.preventDefault();
return false;
}
}).on('blur', function (e) {
console.log('blur')
if (lockInput && focusTarget === null) {
focusTarget = e.target;
setTimeout(function () {
focusTarget.focus();
focusTarget = null;
});
}
});
The global variables are used only for the example, i'd advice against that.
Also, if you have a large number of inputs, i'd suggest using event delegation, instead of adding a listener to every one of them.

Reliable "mouseenter" without jQuery

I've been looking everywhere and I can't seem to find a reliable mouseenter event.
The closest I found was: mouseenter without JQuery
function contains(container, maybe) {
return container.contains ? container.contains(maybe) : !!(container.compareDocumentPosition(maybe) & 16);
}
var _addEvent = window.addEventListener ? function (elem, type, method) {
elem.addEventListener(type, method, false);
} : function (elem, type, method) {
elem.attachEvent('on' + type, method);
};
var _removeEvent = window.removeEventListener ? function (elem, type, method) {
elem.removeEventListener(type, method, false);
} : function (elem, type, method) {
elem.detachEvent('on' + type, method);
};
function _mouseEnterLeave(elem, type, method) {
var mouseEnter = type === 'mouseenter',
ie = mouseEnter ? 'fromElement' : 'toElement',
method2 = function (e) {
e = e || window.event;
var related = e.relatedTarget || e[ie];
if ((elem === e.target || contains(elem, e.target)) &&
!contains(elem, related)) {
method();
}
};
type = mouseEnter ? 'mouseover' : 'mouseout';
_addEvent(elem, type, method2);
return method2;
}
The only issue is that when i run it:
_mouseEnterLeave(ele, 'mouseenter', function(){
console.log('test');
});
I get 40-47ish (different every time) executions at once each time the listener fires.
I tried the Quirksmode one too: http://www.quirksmode.org/js/events_mouse.html#mouseenter
function doSomething(e) {
if (!e) var e = window.event;
var tg = (window.event) ? e.srcElement : e.target;
if (tg.nodeName != 'DIV') return;
var reltg = (e.relatedTarget) ? e.relatedTarget : e.toElement;
while (reltg != tg && reltg.nodeName != 'BODY')
reltg= reltg.parentNode
if (reltg== tg) return;
// Mouseout took place when mouse actually left layer
// Handle event
}
However this one was extremely unreliable and not only that, it assumed the parent/element was a DIV. This has to be more dynamic. This is for a library/script so I can't include jQuery.
In short, I have an element that is hidden until the mouse moves. Once it moves it appears for as long as the mouse is moving OR if the mouse is hovering over the element itself. Less code would be awesome simply because only WebKit doesn't support mouseenter natively and it feels like a waste to have that huge chunk of code from the first example just to support Chrome for a small UI thing.
Is it possible to just scrap the mouseenter and instead use mousemove instead? That takes care of showing it when the mouse is moving. To make it stay visible when hovered directly on the element, just use CSS instead.
#your_element {
display: none;
}
#your_element:hover {
display: block;
}

Categories

Resources