My website has lots of elements which trigger ajax data retrieval on click, and some drag&drop elements handled by jquery ui. Many elements use their own click events, attached directly to them. I need some functionality which will ignore all mouse clicks/mouseup/mousedown events temporarily under some predefined circumstances. For example, I want to disable drag & drop entirely until some ajax request is in progress, etc. I thought I would bind on click and preventDefault(), I tried to bind on document, like this, but it doesn't seem to work:
$(document).on("mousedown", "*", null, function(ev){ev.preventDefault();});
I think it's because when the event reaches $(document), it was already triggered on all childs, so it's too late to preventDefault().
One solution I could imagine is to set some global variable, like ignore_clicks=true; and then add to every function which handles mouse click a check if this variable is true or not. This seems very difficult and I'm afraid even impossible due to external click handlers like in jquery-ui code.
Another solution I imagine is to temporarily put some fixed style element, 100% width and 100% height, zero opacity, over the current page, but this doesn't seem like an ideal solution, feels more like a hack. Furthermore if there is any ongoing animation on the webpage while it is covered by transparent element, the performance of the animation is degraded.
Is there any simple and elegant solution which would allow me to temporarily block all mouse clicks on the given page? (mouseup & mousedown too).
One solution is to stop the event in the capturing phase by using addEventListener:
document.addEventListener("mousedown", function(e) {
e.preventDefault();
e.stopPropagation();
}, true /* true == capturing phase */);
Do note that this won't work in IE8.
With jquery you can toggle on/off event handlers:
function doClick(e) {
e.preventDefault(); //Prevent default event.
//do some fancy ajax stuf...
};
//Toggle on:
$(document).on('click', '.clickable', doClick);
//Toggle off:
$(document).off('click', '.clickable', doClick);
This will work with any event such as click/mousedown/mouseup etc.
If you want to prevent all other events from being fired, you could try as follows:
function preventPropagation(e) {
e.stopImmediatePropagation();
};
//Toggle on when ajax:
$(document).on('click mousedown mouseup', '*', preventPropagation);
//Toggle off when ajax finishes:
$(document).off('click mousedown mouseup', '*', preventPropagation);
If you want to prevent all mouse-based interactions, you could place an invisible overlay in front of the dom, e.g.
HTML
<div id="click-blocker" style="display: none;"></div>
CSS
#click-blocker {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
JS
// to disable clicks:
$('#click-blocker').show()
// to enable clicks again
$('#click-blocker').hide()
By showing the click-blocker, all clicks will happen to the blocker, since it's in front of everything, but since it has no content or background, the user will perceive it as their clicks are just not doing anything.
This won't work if you only want to disable a specific type of interaction, e.g. drag+drop as you mentioned.
I do not think having a "processing" animation overlay is a hack and it seems pretty elegant to me. Plenty of applications use this because it gives users feedback. They know that the application is working and they'll have to wait.
Related
Is there a better way to handle a "hover" (this tapped, then effect active until something else is tapped) event on touch devices than using a global event handler to detect when the user taps something else?
For example, this code might work, but it relies on an event listener attached to the document body, so it's questionable performance-wise.
//note a namespace is used on the event to clear it without clearing all event listeners
$('myDiv').on('touchstart.temp', function () {
//do stuff
$('body').not(this).on('touchstart.temp', function () {
//undo stuff
$('body').not(this).off('touchstart.temp');
});
});
In the future you should be able to use the touchenter and touchleave events, which apply to specific elements.
$("myDiv").on("touchenter mouseover", function() {
// do hover start code
}).on("touchleave mouseleave", function() {
// do hover end code
});
But according to MDN, this is just a proposal which hasn't been implemented yet.
If you use jQuery Mobile, you can use the vmouseover and vmouseleave events, which simulate the mouse events on mobile devices.
The Javascript onmouseover and onmouseout events work just fine! :)
It won't work on mobile devices, but I believe you anticipated that.
Edit: I also noticed you included the JQuery tag. You could also use the JQuery hover method if it is to your liking.
i.e.
$('div').hover(function(){
$(this).css('background-color', 'red');
}, function(){
$(this).css('background-color', 'blue');
});
Example: https://jsfiddle.net/sofdz0s2/
As for hovering on all touch devices, it is not constant between all touch devices. Mine does not keep hovering after a tap. (I tried tapping on the div that changes and then on the green div, but it kept hovering after a tap.)
Edit 2:
As talked about in the comments, touchenter and touchleave are probably what you want.
I hope this helps!
<div id="menuContainer"></div>
<div id="menuItemTemplate" class="menuItem">
<div class="menuItemTitle"></div>
<div class="menuItemImage"><img src="resources/BlackRightChevron.png"/></div>
</div>
The menuContainer div is dynamically appended with clones of the menuItemTemplate. The current click event:
menuContainer.addEventListener('click',menuContainer_click,false);
does not fire when menuContainer overflows in the y-axis.
So I implemented some code found else where on stackoverflow.
Which makes it scrollable but the click events do not run (probably because of the preventDefault()s). Without them I figure every event would be registered as a click instead of a possible move.
Oh, I'm using jQuery mobile and it's UI as well.
Is there any solution to my problem?
The changes I made as per the suggestion:
var scrollStartPosY=0;
document.getElementById(element).addEventListener("touchstart", function(event) {
scrollStartPosY=this.scrollTop+event.touches[0].pageY;
event.preventDefault();
},false);
document.getElementById(element).addEventListener("touchmove", function(event) {
this.scrollTop=scrollStartPosY-event.touches[0].pageY;
event.preventDefault();
move = true;
},false);
document.getElementById(element).addEventListener("touchend", function(event) {
if(move)
move = false;
else
menuContainer_Click(event);
},false);
I'm sure the preventDefaults are wiping out your click. In any case you're using click/mousedown/touchstart to scroll exclusively.
What I think you should do is register a touchend event to trigger whatever you intend to have the current click event do. You may want to verify whether there has been a scroll in the meantime and if so, ignore the touchend. That would differentiate between the two separate intentions of scrolling and clicking.
Decided that iScroll was just an easier solution. Though having difficulty with only one div not scrolling completely to the "bottom".
Consider the following Web page:
<html>
<body onscroll="alert('body scroll event')">
<div style='width:200px;height:200px;overflow:auto' onscroll="alert('div scroll event')">
<div style='height:400px'>
</div>
</div>
</body>
</html>
This html creates a div with a scrollbar. If you move the scrollbar, the onscroll event on the div element is triggered. However, the onscroll event on the body is NOT fired. This is expected, since the W3C states that element onscroll events do not "bubble".
However, I'm developing a client-side web framework that needs to know any time a scroll bar on ANY element of the page is scrolled. This would be easy to do if onscroll bubbled, but unfortunately it does not. Is there any other way to detect onscroll events across an entire page? (Right now I'm focusing mainly on Webkit, so a Webkit-specific solution would be fine...)
Here are some things I've tried:
Capturing DOMAttrModified (doesn't seem to fire for moving scrollbars.)
Using DOM Observers (also don't seem to fire for scrollbars)
Changing the onscroll event type to bubble (seems to not be possible)
It seems the ONLY way to capture onscroll events globally is to attach an onscroll event to EVERY element that may scroll, which is very ugly and is going to hurt the performance of my framework.
Anyone know a better way?
The simplest way to detect all scroll events in modern browser would be using 'capturing' rather than 'bubbling' when attaching the event:
window.addEventListener('scroll', function(){ code goes here }, true)
Unfortunately as I am aware there is no equivalent in older browser such as <= IE8
I had this same issue.
The easiest way of course is to use jQuery. Be aware that this method could potentially slow down your page significantly. Also it will not account for any new elements that are added after the event is bound.
$("*").scroll(function(e) {
// Handle scroll event
});
In vanilla JavaScript, you can set the useCapture boolean to true on your addEventListener call, and it will fire on all elements, including those added dynamically.
document.addEventListener('scroll', function(e) {
// Handle scroll event
}, true);
Note though that this will fire before the scroll event actually happens. As I understand it, there's two phases events go through. The capture phase happens first, and starts from the page root (ownerDocument?) and traverses down to the element where the event happened. After this comes the bubbling phase, which traverses from the element back up to the root.
Some quick testing too showed that this may be how jQuery handles it (for tracking scrolls on all page elements at least), but I'm not 100% sure.
Here's a JSFiddle showing the vanilla JavaScript method http://jsfiddle.net/0qpq8pcf/
*...crickets chirping... *
OK, I guess this question isn't going to get any stackoverflow love, so I might as well answer my own question as to the best solution I've found so far, in case another user stumbles across this question:
The best solution I've come up with is to capture "onmousedown" and "onkeydown" for the BODY element: These events bubble, and so if a user tries to move a scrollbar on the page these global functions will fire as a by-product. Then, in these functions, simply look up event.target and attach a temporary "onscroll" event to those objects until the mouse/key is "up" again. Using that method, you can avoid "handler bloat" and still globally capture all "onscroll" events. (I think this will work for "mouse wheel" scrolling as well, but my research on that final wrinkle is still pending.)
The following works fine when you want to i.e. close a dialog after anything in the background is scrolled:
var scrollListener = function(e) {
// TODO: hide dialog
document.removeEventListener('scroll', scrollListener, true);
};
document.addEventListener('scroll', scrollListener, true);
I needed to handle scrolling in any context in a complex of custom elements with scroll events in shadowRoots. This covers at least part of the problem in my scenario and generally borrows from the answers and comments here in the context of these newer web component APIs. Attaching and detaching the listener in the appropriate lifecycle callbacks works well so-far (once might not fit your use-case).
self.addEventListener('mousewheel', handler, {capture: true, once: true});
self.addEventListener('keydown', handler, {capture: true, once: true});
Note too the event.composedPath() provides the entire event path through all the shadowRoots with all the nodes if specifics about the scrolling context are needed. If this is the case it might make sense to use this approach and attach a new handler for that specific scenario--to the node of interest.
Like drcode said, capture on body tag is the trick. I just add touchmove to work on mobile.
document
.querySelector("body")
.addEventListener("mousewheel", e => {
console.log("scroll");
});
document
.querySelector("body")
.addEventListener("touchmove", e => {
console.log("scroll");
});
Regards,
I have a structure:
<span class="word">This</span><span class="word">is</span><span class="word">the</span><span class="word">text</span><span>.</span>
I want user to be able to make a selection of whole words (spans, class="words") (in browser on desktop and on iOS as well - like in iBooks).
And how can I style it with css?
What is the right way to do this? (didn't work with selections before)
Thanks.
I know you didn't mention jQuery - but with something this dynamic I think it is highly advisable to use it.
Wrap that whole deal in a div.
<div id="wordSelector"><spans></div>
And then attach a mousedown event to the div. Use a semaphore to ensure that events are handled only during mousedown. Capture mouseover events on the spans until the mouseup event is registered.
Note: These events may need to be attached to document instead of the div to ensure that a mousedown event outside of the div but entering the div is handled, and with mouseup as well in case the mouseup event is outside of the div.
var spansTouched = [];
var mouseDown = 0;
$("#wordSelector").mousedown( function(){
//track spans touched with a semaphore
mouseDown++;
});
$("#wordSelector").mouseup( function(){
mouseDown = 0;
//handle spansTouched and then reset it to []
});
$(".word").mouseover( function(){
if(mouseDown > 0){
spansTouched.push(this);
}
});
Obviously there is room for improvement here, this is just to highlight a possible approach to take using a semaphore and mouse events.
Not sure how you mean that the user should select a word. If the user should "select" a word by clicking on it, you could use a jQuery plugin like TipTip or any other tooltip-plugin. At least tiptip support click.
Not sure if any of the tooltip-plugins support triggering on highlight of text by default, but using a JavaScript to listen for highlighting of text and the trigger the appropriate tooltip to show manually, and hide it again if the text is deselected.
Dave Welsh has written a small piece of jQuery to sniff for text-selection, that could be utilized.
I'm using jQuery to toggle the visibility of a <div> using the jQuery toggle method. The toggle is fired on the mouseenter and mouseleave event, thus creating the effect of the div to fold out on mouseenter and fold in on mouseleave. Problem is, if the user drags the mouse over the <div> a few times and then leaves the <div>, the div will toggle in and out several times. This can happen if the user accidentally moves around the mouse pointer in the <div> are. Do anyone have any idea on how I can avoid this behavior?
Thanx!
Two things:
If you're going to use both mouseenter and mouseleave I'd suggest using the hover() function; and
When using triggered animations it's a good habit to get into to use the stop() method.
So:
$("div.someclass").hover(function() {
$("...").stop().fadeIn("slow");
}, function() {
$("...").stop().fadeOut("slow");
});
Note: replace "..." with the appropriate selector for what you're toggling and use the appropriate effect (I'm using fade here). Also, this in an event handler refers to the source of the event.
You can use the more common mouseover/mouseout events to get a hover event that doesn't fire on internal mouse movements.
But don't use toggle on a mouse event, it can easily go wrong if eg. the mouse is over the element at page load time, or the mouse leaves the browser (which can allow the mouse to leave the bounds of the element without firing a mouseout). Have separate function for over which shows the content, and out which hides it.
Better: just use the hover() method which is meant for exactly this purpose.
Aside from the correct answer by Cletus, i'd like to point out that using mouseenter and mouseleave events is not wrong. The trick only resides into the stop() method, in fact we could still do:
$("div.someclass").on("mouseenter", function() {
$("...").stop().fadeIn("slow");
});
$("div.someclass").on("mouseleave", function() {
$("...").stop().fadeOut("slow");
});
Here is a jsFiddle example :)