Passing mouseover through element - javascript

I'm working on a regex-analyzer that has syntax highlighting as a feature.
My site uses an overlay of two contenteditable divs.
Because of the difficulty in getting the cursor position and maintaining that even as tags are added and subtracted, I decided the best route was to have two contenteditable divs, one on top of the other. The first (#rInput) is pretty plain. If not for some nuisance problems that made me switch from a textarea, it could be a textarea. The second (#rSyntax) gets its value from rInput and provides syntax highlighting. I make sure that both are always scrolled to the same position so that the overlay is perfect (However, I also use a transparent (rgba(...)) font on rSyntax, so that if a momentary sync-delay should occur, the text is still legible.)
In the lower portion snapshot above, the code of the contenteditable rSyntax is this:
<span class="cglayer">(test<span class="cglayer">(this)</span>string)</span>
While rInput, positioned exactly on top, contains only this
(test(this)string)
The problem with this method is that I want to offer some alt-tooltips (or a javascript version) when a user mouses over. When the user mouses over rInput, I would like to pass the mouseover event to elements of rSyntax.
I'd like mousing over (test ... string) to show "Capturing group 1", while mousing over (this) would show "Capturing group 2", or if it were (?:test), it would say "Non-capturing group".
The terminology is a little tough because searching for things like "above" and "below" pulls up a lot of results that have nothing to do with z-index.
I did find the css property pointer-events which sounded ideal for the briefest moment but doesn't allow you to filter pointer events (set rInput to receive clicks, scrolls, but pass mouseover through to rSyntax and its child-elements.
I found document.elementFromPoint which may offer a solution but I'm not really sure how. I thought I would try document.getElementById('rInput').getElementFromPoint(mouseX,mouseY), but this function is not available to child elements.
Theoretically, I could hide the rInput on mousemove, using a setTimeout to put it back quickly, and then a tooltip should appear when mousing over child elements of rSyntax, but that doesn't seem like the best solution because I don't want rSyntax to be clickable. Clicks should always go to rInput.
It's possible to disable the mouseover wizardry while rInput has the focus, but then tooltips from rSyntax won't work, and though I haven't tried this method, I'm not sure if passing a click event from rSyntax to rInput would position the cursor properly.
Is there a method to make this work?

Update
The best solution was to move the syntax highlighter rSyntax to an iframe because then I can pass elementFromPoint() without flickering the div which really is a vast improvement.
$(document).on("mousemove", "#rInput", function (e) {
$element = $(document.getElementById('frSyntax').contentDocument.elementFromPoint(e.pageX,e.pageY));
if ($element.attr("id") != "frSyntax" && $element.attr("id") != "rSyntax" && $element.attr("title") && $element.attr("title").length) {
$mother.find(".dashed").removeClass("dashed")
$element.addClass("dashed")
$("#syntip").html($element.attr("title"))
$("#syntip").css({"top": e.pageY+10, "left": e.pageX, "display": "inline-block"})
} else {
$mother.find(".dashed").removeClass("dashed");
$("#syntip").hide()
}
})
And at the beginning of my $(document).ready(..., I added this
$('#frSyntax').load(function(){
$mother = $("#frSyntax").contents();
//frSyntax is the name of the iframe.
$syntax = $mother.find("#rSyntax")
});
First Solution
I'll leave this here in case it's more like what someone wants.
From further research, I feel like the only approach is mousemove wizardry making the element disappear, finding the element below, with document.elementFromPosition, and popping it back in.
I'm still looking for advice if anyone has a better option.
The trouble is that this "flickering" will cause mousemove and mouseover to keep triggering, which is needless. So I created a variable to log the coords and only apply a change when the mouse actually moved.
$(document).on("mousemove", "#rInput", function (e) {
if (holdmouse != (e.pageX + "," + e.pageY)) {
$("#rInput").hide();
element = $(document.elementFromPoint(e.pageX,e.pageY));
if (element.attr("id") != "rInput" && element.attr("id") != "rSyntax") {
$(".classes, .cglayer").css("border-bottom","none");
element.css("border-bottom","2px dashed black")
$("#syntip").html(element.attr("title"))
$("#syntip").css({"top": e.pageY+10, "left": e.pageX, "display": "inline-block"})
holdmouse = e.pageX + "," + e.pageY
} else {
$(".classes, .cglayer").css("border-bottom","none");
$("#syntip").hide()
}
$("#rInput").show()
}
})

Related

Detecting when clicking anywhere outside of element when the clicked element has been removed from the DOM?

I have a notifications dropdown menu that should be closed when you click anywhere outside of it. The following code was working great until I ran into a new situation:
$(document).click(function(e) {
var target = e.target;
if (!$(target).is('.notification-area') && !$(target).parents().is('.notification-area')) {
$('.notification-area .flyout').removeClass('flyout-show');
}
});
However (and I'm using Backbone if that's relevant), some elements cause part of the menu to re-render. That is to say: remove and rebuild a part of the DOM.
Obviously you can't tell where an element is within the DOM if it's already been removed. So now, if there's a click that causes part of that view to re-render then that bit of code that checks the parents() of the element returns no parents.
Then I thought I might be able to solve it by checking if the length of parents() is greater than 0.
...
if (!$(target).is('.notification-area')
&& !$(target).parents().is('.notification-area')
&& $(target).parents().length > 0)
...
And this works but I wonder what side effects it could have. Is this the best way to do this?
Hope I understood your question correct. You want some simple way of not shutting the notification area if clicked upon it. But close it when clicked on body?
One way to do these kind of things is somewhat like this.
mouseOverArea = false; // This will be globally set, right away
$('.notification-area').mouseenter(function(){
mouseOverArea = true;
}).mouseleave(function(){
mouseOverArea = false;
});
And then when you click on body or whatever, you simply check if mouseOverArea == false... If so, close the notification box, otherwise return false, e.preventDefault(); or whatever fits your coding.
You can simplify this using closest() since it includes both the target and ancestors.
It turns :
!$(target).is('.notification-area') && !$(target).parents().is('.notification-area')
Into a simpler to read:
!$(target).closest('.notification-area').length
reference: closest() docs

What is the correct logic of hiding a DIV the instant its height is below a certain number?

I have a div filled with images.
I have a button that removes images one by one from the div, thus decreasing its height every time.
When the div's height is below 75px in height (implying that it no longer holds any images), I have this jQuery code that is meant to hide the div as soon as the low height is detected:
if ($('#ImageDiv').height() < 75) {
$('#ImageDiv').hide();
}
This code is programmed to activate every time the user clicks that Remove Image button.
Instead, here is what is actually happening: The user clicks the button, thus removing the image, but the div does NOT immediately hide the div despite the height being below 75. Instead, it requires a second click of the button to realize the height is low enough to hide the div.
What is wrong with my logic and how can this problem be fixed?
Without any code that's my better... try to print on console or alert $('#ImageDiv').height() at the end of your function to see what it really sizes.
Are you using an animation to remove the images? If yes, maybe you could check your height value at the complete callback from the animation. Maybe are you evaluating the $('#ImageDiv').height() before the animation has completed?
Hope this helps.
EDITED AFTER COMMENT
With .slideUp() function you can pass a function as second argument (callback). There you can check or do whatever you want:
$('#ImageDiv').slideUp('fast', function() {
// After slideUp is completed, run this...
});
More info here: http://api.jquery.com/slideup/
If you use .remove() function you could check the height with:
$.when($('#your_Removed_DIV_ID').remove()).then(
console.log(
'Height: ' + $('#ImageDiv').height()
)
);
The case might be that you are removing the image from the div after the height check condition. Make sure to do that earlier.
Alternatively, you can simply use if( $('#ImageDiv').has('img').length ) for the presence of img tag inside the div. This would for images less that 75 as well.
This might help you:
$('#remove').on('click', function(){
$('#ImageDiv > img').last().remove(); //remove the last Image
if ($('#ImageDiv > img').length === 0) { //check if there is an image left
$('#ImageDiv').hide();
}
});
Normally I would recommend to cache reused jQuery-Objects, but in this case this would cause an error because an old state is checked.
Side-Note: If this button is within a form-element or a styled anchor-tag you might need to use event.preventDefault() at the beginning of your function.
Demo
You would need to prevent the default activity of button and perform the desired action. http://jsfiddle.net/bcnsk83p/1/
Example:
$("#btn").click(function(e){
e.preventDefault();
$('#ImageDiv img:last-child').remove();
if ($('#ImageDiv').height() < 75) {
$('#ImageDiv').hide();
$('p').text("#ImageDiv is now hidden!");
}
});

Strange lag which disappears by clicking anywhere on the html body

I have some overlay elements which are display: none initially but turn to display: inline when I hover over specific items on the page, and disappear again when the mouse hovers over something else. Exactly same behavior as tool-tips with the difference that this overlay objects have clickable and interactive elements (such as a jquery accordion).
Everything works perfectly, until I interact with these overlay elements, i.e. click on one of the clickable items in the overlay element. Then, once that overlay item becomes display:none again, the page becomes extremely laggy in terms of how long it takes when I hover over an item to find its corresponding overlay element (they are selected by their id) and for it to appear and disappear.
The strange thing is that if I click anywhere on the html body, the lag disappears and everything becomes fast as in the beginning.
Out of despair, I have tried to programmatically call blur, focus, trigger('click') once the overlay element is set back to display:none but none has helped so far, and I have to manually click on the page for the lag to go away.
Any idea what causes such behavior and how I can fix it? thanks,
Edit: code
CSS part:
span.overlay {
z-index:10;
display:none;
position:absolute;
}
span.visible { display:inline; }
HTML part: lots of such span elements, each with their own unique id.
<span class='overlay ui-widget-content' id='xyz'>
<!-- lots of stuff here -->
</span>
javascript part:
/* displays overlay element when user hovers over the first td */
$('table.foo > tbody > tr > td:first-child').hover(
function(e) {
$(this).parent().tooltip('disable');
var elem = $('#' + $(this).parent().data('overlay-id'));
if (!elem.hasClass('visible')) {
elem.css('left', e.pageX + 20).css('top', e.pageY).addClass('visible');
elem.find('.accordion:first').accordion('refresh');
}
}, function() {
var elem = $('#' + $(this).parent().data('overlay-id'));
if (! elem.is(':hover') && ! elem.hasClass('pin')) {
$(elem).removeClass('visible');
}
$(this).parent().tooltip('enable');
});
/* if mouse leaves span.visible and it is not pinned it will hide the span */
$('body').on('mouseleave', 'span.visible',
function() {
if (!$(this).hasClass('pin')) {
$(this).removeClass('visible');
}
});
Edit: profiling the code, it seems that get offsetHeight and get offsetWidth take way longer than before. Yet I do not know why this should happen and why it should go away by clicking on the page.
previously, when I do not observe the problem, these two functions each take less than 3%.
try binding the mouseleave event upon opening the "tooltip". Replace your code with this (not tested):
/* displays overlay element when user hovers over the first td */
$('table.foo > tbody > tr > td:first-child').on('mouseenter',
function(e) {
$(this).parent().tooltip('disable');
var elem = $('#' + $(this).parent().data('overlay-id'));
if (!elem.hasClass('visible')) {
elem.css('left', e.pageX + 20).css('top', e.pageY).addClass('visible');
elem.find('.accordion:first').accordion('refresh');
// notice the "ONE" handler, it'll unbind the event after execution
elem.one('mouseleave', function() {
if (!$(this).hasClass('pin')) {
$(this).removeClass('visible');
}
$(this).parent().tooltip('enable');
});
}
}
);
Notice the one listener to unbind the event after it's first execution.
I can't guarantee that this will fix your issue but I experienced lots of performance hits when a page has A LOT of elements and browsers need to check hover events that change very quickly.
This way the browser only needs to check one mouseleave event. And if it happened, it's gone again. It seems you may have too many bound events and don't clean them up properly.
I'm not sure if I replicated your desired functionality correctly so please add code if I missed something. I was unsure why exactly you'd need to bind a mouseleave event via body AND via .hover().

Preventing clicking of links using Javascript

$('a').click(function(e){return false;});
It successfully disables links almost everywhere but does not work on the links found in the tooltip-popups on the topic tags on this site (SO). I looked at the DOM and they are indeed <a> tags.
How come?
Is there a more robust way to force my user to stay on the current page? (This is for a bookmarklet where I graphically manipulate the DOM so I need to be able to click and drag everything, including links.)
I know that even if I can disable the regular click functionality on all anchors it won't do anything for any navigation triggered by other click callbacks on other tags.
try with .on - in e.g. SO sites those links are created later then your event attached
$(document).on('click', 'a', function(e) {
return false;
});
A technique I've employed before is to place an invisible absolutely-positioned element over the entire document. This is actually employed by Firebug Lite to allow the Element Inspector to intercept any clicks.
All you need to worry about then is attaching your event listener to your absolutely-positioned element and figuring out what element was under the mouse when you clicked. Luckily, all modern browsers implement a method to discern this: document.elementFromPoint. There are a few differences between implementations; here's the method I usually use (unfortunately feature detection's a bit tricky for this, so I'm doing browser detection):
var scrollIsRelative = !($.browser.opera || $.browser.safari && $.browser.version < "532");
$.fromPoint = function(x, y) {
if(!document.elementFromPoint) return null;
if(scrollIsRelative)
{
x -= $(document).scrollLeft();
y -= $(document).scrollTop();
}
return document.elementFromPoint(x, y);
};
Attach a click event listener to your absolutely-positioned div and on click, hide your div and call this method to get the element, then show your div again. Example:
$('#hugeDiv').on('click', function(ev) {
var x = ev.pageX;
var y = ev.pageY;
$(this).hide();
var el = $.fromPoint(x, y);
$(this).show();
//do stuff with el
});
It would appear the solution is
window.onbeforeunload = function() {
return "Are you sure you want to navigate away?";
}
A right proper use of an event system if I would say so myself.
The original scope of the question was not wide enough. This solves the problem that led to my question.

Help needed with showing and hiding a div

I'm having a problem with an image viewer I'm creating. Like the image below, the 'window' is where the image is shown and 'paging' is where the user can change the image. I've used this Jquery script to make the 'paging' fade in whenever the window is hovered over - It's hidden to start with. Although when the user hovers onto 'paging', it flickers. (Like shows then hides, etc.)
I suppose it's because the mouse isn't hovering over the 'window' anymore. Can anyone suggest how I can make 'paging' remain showing? Thanks for the help! :)
$(".window").hover(function() {
$(".paging").fadeIn('fast');
}, function() {
$(".paging").fadeOut('fast');
});
You can use .stop() here and include both in your .hover() selector, like this:
$(".window, .paging").hover(function() {
$(".paging").stop(true, true).fadeIn('fast');
}, function() {
$(".paging").stop(true, true).fadeOut('fast');
});
This way, when you leave to enter the child or back to the parent it stops the fade out and brings it right back, resulting in no visible action to the user.
You could try using mouseover and mouseout instead. I'm not sure that mouseout would react the same way hover does.
In fact, when you pass your mouse over the paging there is a magical thing that happens which is called "event bubbling": the "hover" event is passed to the container which is the parent of the "hovered" object, and so on until the "document" object.
So to solve your problem, you need to stop bubling, you can do it with "return false":
$(".paging").hover(function() {
return false;
}, function() {
return false;
});
(It's possible that in recent version of jquery you can replace the argument function(){return false;} by just false.)

Categories

Resources