Let me start off by saying I'm no jQuery expert. However, I have code (included below) that will grab the element I hover over. The problem I have is if I have the following structure, inside my <div id="literalContent">:
<div>
<table>
<tr>
<td>
<div>
And I hover over the final <div>, I see the border and That's awesome. However, if I then want to select the parent table, it is not selected. Any thoughts on my silly problem would be great.
$(function () {
$('#literalContent').find("*").hover(
function () {
$('[class="SelectedItem"]').removeAttr("class");
$(this).fadeIn(500, function () {
$(this).addClass("SelectedItem");
})
//$(this).addClass("SelectedItem");
//$('#controllabel').html('hovering ' + $(this).prop("tagName"));
},
function () {
if ($(this).attr('class') == "SelectedItem") {
$(this).removeAttr("class");
}
else {
$(this).removeClass("SelectedItem");
}
});
}
);
To highlight only the target (and not any of its ancestors), you can toggle a class on the target's immediate parent on mouseenter/mouseleave.
http://jsfiddle.net/kU3Ba/1/
$('#outer').find('*').hover(
function (e) {
var o = $(e.currentTarget),
par = o.parent();
o.addClass('redBorder');
if(par[0].id != "outer"){ // exclude "outer", since it is the wrapper
par.addClass('hideBorder'); // parent still has "redBorder" class
// "hideBorder" just hides it
}
console.log(e.currentTarget.id+" has focus");
},
function (e) {
var o = $(e.currentTarget),
par = o.parent();
o.removeClass('redBorder');
par.removeClass('hideBorder');
if(par[0].id != "outer"){ // exclude "outer", since it is the wrapper
console.log(par[0].id+" has focus");
}
});
This will visibly hide the border of immediate parent, and "return" the border once the mouse leaves its child.
It appears to be working here - http://jsfiddle.net/jayblanchard/ucM96/
$('#outer').find('*').hover(
function () {
$(this).addClass('redBorder');
},
function () {
$(this).removeClass('redBorder');
});
Note that selecting inner4 with the mouse causes the hover to be applied to inner3 as well - due to their relationship and how mouse events are handled.
You can be more specific by doing something like giving multiple selectors -
$('.foo, .bar').hover(
function (e) {
$(this).addClass('redBorder');
console.log(e.target.id);
},
function () {
$(this).removeClass('redBorder');
});
Related
I created a global dragover event listener so that when something it dragged over the page it would show a dragover area on the page and hide it when you move the cursor off the page:
document.addEventListener('dragover', event => {
event.preventDefault()
this.dragOverArea = true
// ...
})
document.addEventListener('dragleave', event => {
event.preventDefault()
this.dragOverArea = false
// ...
})
Problem
JSfiddle: http://jsfiddle.net/ct3haqf0
Try dragging any file over the elements on the page (from the top left) you will see the dragover event switching to dragleave rapidly back and forth which makes the drop area appear/disappear while you dragging it through the elements.
In my project it creates a noticeable lag. The problem is, dragover event conflicts with all the element's children (and hence activates dragleave), so it constantly shows/hides the drop area while you're dragging something over the page full of elements
Question
I found that you're supposed to set: pointer-events: none to all the children of the element with dragover listener, so I have to set this rule to body * { }
So how do I set this rule to all children of the body with JS inside addEventListener('dragover') ?
What about adding an class to the body, which can be styled with CSS?
adding classes:
function addClassToBody( newClass )
{
document.body.className += " "+newClass+" ";
}
function removeClassFromBody( oldClass )
{
document.body.className = document.body.className.replace(" "+oldClass+" "," ");
}
CSS:
body.drag * {
pointer-events: none !important;
}
I tested it in a jsfiddle: http://jsfiddle.net/timlg07/4fdj0hvc/
I finally figured it out.
Here's an example
I'm using Vue.js so dragging and showDropZone are just variables in data().
html
<div
v-show="showDropZone === true"
id="drop-zone"
class="drop-zone"
>
Drop zone text
</div>
JS
// In the component with the drop zone div:
document.getElementById('drop-zone').addEventListener('drop', event => {
event.preventDefault()
this.showDropZone = false
var files = event.dataTransfer.files
})
// In the entry component:
window.addEventListener('dragenter', event => {
this.dragging++;
this.showDropZone = true
event.stopPropagation();
event.preventDefault();
});
window.addEventListener('dragover', event => {
this.showDropZone = true
event.stopPropagation();
event.preventDefault();
});
window.addEventListener('dragleave', event => {
this.dragging--;
if (this.dragging === 0) {
this.showDropZone = false
}
event.stopPropagation();
event.preventDefault();
});
Suppose I have an element containing several children and want to run some code whenever the mouse enters or leaves the container. If I naively write:
var onHover = function (el, f) {
el.addEventListener('mouseover', function () {
f(true);
});
el.addEventListener('mouseout', function () {
f(false);
});
};
Then I get the desired behavior in some cases - depending on the nature of the callback f. However, when the mouse moves from child to child within the container, f(false) runs immediately followed by f(true). I don't want this to happen - I only want f to be run when the mouse enters or leaves the container as a whole, not called machine-gun style as the user drags their mouse over the elements that are inside the container.
Here's the solution that I came up with:
var onHover = function (el, f) {
var previousMouseover = false;
var receivedMouseover = false;
var pushing = false;
var pushEv = function () {
if (pushing) { return; }
pushing = true;
setTimeout(function () {
pushing = false;
if (previousMouseover !== receivedMouseover) {
f(receivedMouseover);
previousMouseover = receivedMouseover;
}
});
};
el.addEventListener('mouseover', function () {
receivedMouseover = true;
pushEv();
});
el.addEventListener('mouseout', function () {
receivedMouseover = false;
pushEv();
});
};
This solution, like the first solution, assumes and works by the virtue that the mouseout event is sent before the mouseover event is. I would also like to know whether that is formally specified by any W3C documentation, but that is not the topic of this question, and even if it were not the case, it would be easy to write a functioning algorithm in spite of that by setting two separate variables, say receivedMouseover and receivedMouseout inside of the mouseover and mouseout callbacks, both of which are then inspected inside of the setTimeout callback.
The question is: Is it required that both the mouseover and mouseout events be processed before any setTimeout callbacks signed up by either event are run?
Use the mouseenter and mouseleave events instead of mouseover and mouseout.
Since you have attached the event listener to the parent element you may compare the event origin (event.target) with the parent element (this or event.currentTarget) before you take an action. You may do as follows;
var onHover = function (el, f) {
el.addEventListener('mouseover', function (evt) {
this === evt.target && f(true);
});
el.addEventListener('mouseout', function (evt) {
this === evt.target && f(false);
});
};
Most of the elements bubble so at some point this might be the right way to do this job.
Edit: As mentioned in the comments the mouseover and mouseout events can be problematic under some circumstances such as when the parent element has no padding or margins defined and children cover all the parent. Even if they don't the speed of the mouse could be fast enough to make the JS engine fail to sample the mouse over the parent element. This fact is beautifuly explained in this article.
So, as mentioned in the accepted answer, i suppose the mouseenter and mouseleave events are there to solve this problem. Accordingly the right code should be like;
var onHover = function (el, f) {
el.addEventListener('mouseenter', () => f(true));
el.addEventListener('mouseleave', () => f(false));
};
Edit 2: Well... Actually there is a safe way of using mouseover and mouseout in this particular condition. It's about using CSS pointer-events property on the children which disables them from event emitting for mouse activity.
var container = document.getElementById('container');
container.addEventListener('mouseover', function (ev) {
console.log(container === ev.target);
});
container.addEventListener('mouseout', function (ev) {
console.log(container === ev.target);
});
#container * {
pointer-events: none
}
<div id="container">
<div>
<span>text</span>
</div>
</div>
I made a simple plunkr here http://plnkr.co/edit/zNb65ErYH5HXgAQPOSM0?p=preview
I created a little datepicker I would like this to close itself when you focus out of it (focusout of datepicker) if I put blur on input I'm unable to use the datepicker, if I put focusout event on datepicker it doesn't works
I also tried:
angular.element(theCalendar).bind('blur', function () {
$scope.hideCalendar();
});
but it doesn't work.
Any clue?
this is because you are removing the item before you get a chance to do anything, here is a working example:
http://plnkr.co/edit/mDfV9NLAQCP4l7wHdlfi?p=preview
just add a timeout:
thisInput.bind('blur', function () {
$timeout(function(){
$scope.hideCalendar();
}, 200);
});
have you considered using existing datepickers? like angularUI or angular-strap: http://mgcrea.github.io/angular-strap/##datepickers
Update:
Not a complete solution, but should get you quite closer:
angular.element($document[0].body).bind('click', function(e){
console.log(angular.element(e.target), e.target.nodeName)
var classNamed = angular.element(e.target).attr('class');
var inThing = (classNamed.indexOf('datepicker-calendar') > -1);
if (inThing || e.target.nodeName === "INPUT") {
console.log('in');
} else {
console.log('out');
$timeout(function(){
$scope.hideCalendar();
}, 200);
}
});
http://plnkr.co/edit/EbQl5xsCnG837rAEhBZh?p=preview
What you want to do then is to listen for a click on the page, and if the click is outside of the calendar, then close it, otherwise do nothing. The above only takes into account that you are clicking on something that has a class name which includes datepicker-calendar, you will need to adjust it so that clicking within the calendar doesn't close it as well.
How about closing on mouseout?
You need to cancel the close if you move to another div in the calendar though:
//get the calendar as element
theCalendar = element[0].children[1];
// hide the calendar on mouseout
var closeCalendarTimeout = null;
angular.element(theCalendar).bind('mouseout', function () {
if ( closeCalendarTimeout !== null )
$timeout.cancel(closeCalendarTimeout);
closeCalendarTimeout = $timeout(function () {
$scope.hideCalendar();
},250)
});
angular.element(theCalendar).bind('mouseover', function () {
if ( closeCalendarTimeout === null ) return
$timeout.cancel(closeCalendarTimeout);
closeCalendarTimeout = null;
});
EDIT
Adding a tabindex attribute to a div causes it to fire focus and blur events.
, htmlTemplate = '<div class="datepicker-calendar" tabindex="0">' +
angular.element(theCalendar).bind('blur', function () {
$scope.hideCalendar();
});
So, i know it probably is not the best practice or the best way to do this, but at the end i fixed and got what i need using this:
thisInput.bind('focus click', function bindingFunction() {
isMouseOnInput = true;
$scope.showCalendar();
angular.element(theCalendar).triggerHandler('focus');
});
thisInput.bind('blur focusout', function bindingFunction() {
isMouseOnInput = false;
});
angular.element(theCalendar).bind('mouseenter', function () {
isMouseOn = true;
});
angular.element(theCalendar).bind('mouseleave', function () {
isMouseOn = false;
});
angular.element($window).bind('click', function () {
if (!isMouseOn && !isMouseOnInput) {
$scope.hideCalendar();
}
});
I setted up some boolean vars to check where mouse is when you click the page and it works like a charm if you have some better solution that works , please let me know, but this actually fixed all.
I accept this as the answer but i thank all the guys on this page!
I have a small jQuery script:
$('.field').blur(function() {
$(this).next().children().hide();
});
The children that is hidden contains some links. This makes it impossible to click the links (because they get hidden). What is an appropriate solution to this?
This is as close as I have got:
$('.field').blur(function() {
$('*').not('.adress').click(function(e) {
foo = $(this).data('events').click;
if(foo.length <= 1) {
// $(this).next('.spacer').children().removeClass("visible");
}
$(this).unbind(e);
});
});
The uncommented line is suppose to refer to the field that is blurred, but it doesn't seem to work. Any suggestions?
You can give it a slight delay, like this:
$('.field').blur(function() {
var kids = $(this).next().children();
setTimeout(function() { kids.hide(); }, 10);
});
This gives you time to click before those child links go away.
This is how I ended up doing it:
var curFocus;
$(document).delegate('*','mousedown', function(){
if ((this != curFocus) && // don't bother if this was the previous active element
($(curFocus).is('.field')) && // if it was a .field that was blurred
!($(this).is('.adress'))
) {
$('.' + $(curFocus).attr("id")).removeClass("visible"); // take action based on the blurred element
}
curFocus = this; // log the newly focussed element for the next event
});
I believe you can use .not('a') in this situation:
$('.field').not('a').blur(function() {
$(this).next().children().hide();
});
This isn't tested, so I am not sure if this will work or not.
I'm trying to write a web app which replaces the context menu (right-click menu) with my own customized ones. I want it so that when the user clicks on a table row, they get one certain context menu and when they click on the background of the page, they get a different one.
I have already written the menus and gotten them working. The problem comes in when trying to figure out how to get the background's menu to show ONLY when clicking on the background and how to get the table row's menu to show when that is clicked.
I tried using document.body.oncontextmenu for the body and and setting the oncontextmenu function for each table row, but the body's oncontextmenu function overrides the row's so I get the wrong menu. The menu for the table rows DOES work if I stop using the body's menu, so that's not the issue.
I could be using the wrong events, so is there a different event for just the background (and not the elements on top of the background)? Or a way to "prioritize" the events so the table row's function takes precedence?
This is how the code looks:
var tableMenu;
var bodyMenu;
window.onload = function()
{
bodyMenu = new rightClickMenu("bodyMenu");
document.body.oncontextmenu = function() { bodyMenu.show(); tableMenu.hide(); }
bodyMenu.add("Add Entry", function()
{
alert("ADD");
});
tableMenu = new rightClickMenu("tableMenu", "tblSims");
simRows = getElementsByClassName("trSimRow");
for (var i in simRows)
simRows[i].oncontextmenu = function() { tableMenu.show(this.id.substring(2)); bodyMenu.hide(); }
tableMenu.add("Delete Entry", function(mac)
{
alert("DELETE");
});
document.body.onclick = function()
{
bodyMenu.hide();
tableMenu.hide();
};
}
You can capture the target element, e.g.:
$('*').click(function(e) {
alert(e.target);
alert(e.target.tagName);
if(e.target.tagName == 'html') {
// show background menu
}
});
You have to work with the Javascript Event Propagation model. What happens is that your click event is automatically passed down the layers of objects on a page that have been registered as event listeners, unless you explicitly tell it to stop, try something like this:
function setupClickHandlers()
{
document.getElementsByTagName('body')[0].onclick = doBodyMenu;
document.getElementById('tableID').onclick = doTableMenu;
}
function doBodyMenu()
{
//do whatever it does
}
function doTableMenu(e)
{
//do whatever it does
//stop the event propagating to the body element
var evt = e ? e : window.event;
if (evt.stopPropagation) {evt.stopPropagation();}
else {evt.cancelBubble=true;}
return false;
}
This should deal with the way each browser handles events.
$( document ).ready(function() {
var childClicked = false;
// myContainer is the nearest container div to the clickable elements
$("#myContainer").children().click(function(e) {
console.log('in element');
childClicked = true;
});
$("#myContainer").click(function(e){
if(!childClicked) {
console.log('in background');
e.preventDefault();
e.stopPropagation();
}
childClicked = false;
});
});
#myContainer {
width:200px;
height:200px;
background-color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="myContainer" style="">
link
<div style="width:50px;height:50px;background-color: white;">
another link
</div>
</div>