In IE exists .setCapture(); .releaseCapture() functions.
What's the equivalent of these functions in Firefox without using jQuery? (my client does not want to use it)
As it has been said above, Firefox does not offer this functionality, and you can work around it by monitoring events on the entire document. To be sure that there is no a better trick, I’ve just checked jQuery UI, and it appears they use the same approach. So for instance if you wanted to capture mouse movements when mouse is down in jQuery, you would do:
$("#someElement").
mousedown(function() { $(document).mousemove(captureMouseMove) }).
mouseup(function() { $(document).unbind("mousemove", captureMouseMove) });
function captureMouseMove(event)
{
// ...
}
https://developer.mozilla.org/en-US/docs/DOM/element.setCapture
setCapture and releaseCapture were added to Firefox 4 (with the release of Gecko 2) on March 22, 2011. However, WebKit (Chrome/Safari) still lacks these functions.
I believe element.setCapture() and document.releaseCapture() were added to Firefox as of FF4:
https://developer.mozilla.org/en/DOM/element.setCapture
"Call element.setCapture() method during the handling of a mousedown event to retarget all mouse events to this element until the mouse button is released or document.releaseCapture() is called."
To capture the mouse at anytime is not good behavior, I think that's why setCapture is not provided.
However, to capture the mouse for a drag-and-drop, you just need to handle the mouse events (mouse{up,down,move}) of the document object, which may be triggered when dragging even outside the client area.
<html>
<head>
<title>Capture test</title>
</head>
<body>
<script type="text/javascript">
document.onmousedown = function () {
state.innerHTML = "Dragging started";
};
document.onmousemove = function (e) {
coord.innerHTML = e.clientX + ',' + e.clientY;
}
document.onmouseup = function (e) {
state.innerHTML = "Dragging stopped";
}
</script>
<p id="state">.</p>
<p id="coord">.</p>
</body>
</html>
#JanZich's solution works great except it doesn't capture the mouse up event if the mouse is outside the element. This worked better for me:
$("#someElement").mousedown(function() {
$(document).mousemove(captureMouseMove);
$(document).mouseup(captureMouseUp);
});
function captureMouseMove(event) {
console.log("mouse move");
}
function captureMouseUp(event) {
console.log("mouse up");
$(document).unbind("mousemove", captureMouseMove);
$(document).unbind("mouseup", captureMouseUp);
}
Use event bubbling: add event listeners for the bubbling mouse events to a high-level container (possibly even document) and use a variable to track which element should be the capturing one.
Without further information on what you're trying to do, there's not really any more to say.
setCapture() and releaseCapture() are Internet Explorer specific non-standard methods. There is no implementation in Firefox. There is a framework called Gimme that gives you some mouse capture functionality. http://www.codeplex.com/gimme/
SetCapture and ReleaseCapture are IE propriatory functions as you've discovered. There is no native way to manipulate the content menu in the same way in Firefox.
It seems like it might be possible with Gimme which can be found at http://www.codeplex.com/gimme/Wiki/Recent.aspx. There is a blog post here: http://blog.stchur.com/2007/11/21/setcapture-with-gimme which describes one scenario of using this to replace the functions.
Use true as the third parameter of the addEventListener method to capture. For example:
document.addEventListener("click", function(event){location.hash=event.target}, true)
Use removeEventListener with the same parameters to release:
document.removeEventListener("click", function(event){location.hash=event.target}, true);
References
DOM Level 3 Event Flow
Netscape 6, Part III: The Event Model
Current Events: A Client Side Tipsheet
There is no such function in FF / JavaScript. The capture functions only exists in JScript.
Related
I am working on some javascript UI, and using a lot of touch events like 'touchend' for improved response on touch devices. However, there are some logical issues which are bugging me ...
I have seen that many developers mingle 'touchend' and 'click' in the same event. In many cases it will not hurt, but essentially the function would fire twice on touch devices:
button.on('click touchend', function(event) {
// this fires twice on touch devices
});
It has been suggested that one could detect touch capability, and set the event appropriately for example:
var myEvent = ('ontouchstart' in document.documentElement) ? 'touchend' : 'click';
button.on(myEvent, function(event) {
// this fires only once regardless of device
});
The problem with the above, is that it will break on devices that support both touch and mouse. If the user is currently using mouse on a dual-input device, the 'click' will not fire because only 'touchend' is assigned to the button.
Another solution is to detect the device (e.g. "iOS") and assign an event based on that:
Click event called twice on touchend in iPad.
Of course, the solution in the link above is only for iOS (not Android or other devices), and seems more like a "hack" to solve something quite elementary.
Another solution would be to detect mouse-motion, and combine it with touch-capability to figure out if the user is on mouse or touch. Problem of course being that the user might not be moving the mouse from when you want to detect it ...
The most reliable solution I can think of, is to use a simple debounce function to simply make sure the function only triggers once within a short interval (for example 100ms):
button.on('click touchend', $.debounce(100, function(event) {
// this fires only once on all devices
}));
Am I missing something, or does anyone have any better suggestions?
Edit: I found this link after my post, which suggests a similar solution as the above:
How to bind 'touchstart' and 'click' events but not respond to both?
After a day of research, I figured the best solution is to just stick to click and use https://github.com/ftlabs/fastclick to remove the touch delay. I am not 100% sure this is as efficient as touchend, but not far from at least.
I did figure out a way to disable triggering events twice on touch by using stopPropagation and preventDefault, but this is dodgy as it could interfere with other touch gestures depending on the element where it is applied:
button.on('touchend click', function(event) {
event.stopPropagation();
event.preventDefault();
// this fires once on all devices
});
I was in fact looking for a solution to combine touchstart on some UI elements, but I can't see how that can be combined with click other than the solution above.
This question is answered but maybe needs to be updated.
According to a notice from Google, there will be no 300-350ms delay any more if we include the line below in the <head> element.
<meta name="viewport" content="width=device-width">
That's it! And there will be no difference between click and touch event anymore!
Yes disabling double-tap zoom (and hence the click delay) is usually the best option. And we finally have good advice for doing this that will soon work on all browsers.
If, for some reason, you don't want to do that. You can also use UIEvent.sourceCapabilities.firesTouchEvents to explicitly ignore the redundant click. The polyfill for this does something similar to your debouncing code.
Hello you can implement the following way.
function eventHandler(event, selector) {
event.stopPropagation(); // Stop event bubbling.
event.preventDefault(); // Prevent default behaviour
if (event.type === 'touchend') selector.off('click'); // If event type was touch turn off clicks to prevent phantom clicks.
}
// Implement
$('.class').on('touchend click', function(event) {
eventHandler(event, $(this)); // Handle the event.
// Do somethings...
});
Your debounce function will delay handling of every click for 100 ms:
button.on('click touchend', $.debounce(100, function(event) {
// this is delayed a minimum of 100 ms
}));
Instead, I created a cancelDuplicates function that fires right away, but any subsequent calls within 10 ms will be cancelled:
function cancelDuplicates(fn, threshhold, scope) {
if (typeof threshhold !== 'number') threshhold = 10;
var last = 0;
return function () {
var now = +new Date;
if (now >= last + threshhold) {
last = now;
fn.apply(scope || this, arguments);
}
};
}
Usage:
button.on('click touchend', cancelDuplicates(function(event) {
// This fires right away, and calls within 10 ms after are cancelled.
}));
For me using 'onclick' in the html element itself, worked for both touch and click.
<div onclick="cardClicked(this);">Click or Touch Me</div>
tl;dr Summary
Write a function that—when registered to handle a specific event on multiple elements in a hierarchy—executes on the first element reached during bubbling but does not execute (or returns early) when bubbling further up the hierarchy. (Without actually stopping propagation of the event.)
Simple Example
Given this HTML…
<!DOCTYPE html>
<body>
<div><button>click</button></div>
<script>
var d = document.querySelector('div'),
b = document.querySelector('button');
d.addEventListener('mousedown',clickPick,false);
d.addEventListener('mousedown',startDrag,false);
b.addEventListener('mousedown',startDrag,false);
function clickPick(){ console.log('click',this.tagName); }
function startDrag(){ console.log('drag',this.tagName); }
</script>
…clicking on the button yields this output:
drag BUTTON
click DIV
drag DIV
However, I don't want to drag the div. Specifically, I want startDrag to only be processed once, on the <button>, the leaf-most element for which it is registered in a particular propagation chain.
"Working" solution
The following JavaScript code has been tested to work on IE9, Chrome18, Firefox11, Safari5, and Opera11.
function startDrag(evt){
if (seenHandler(evt,startDrag)) return;
console.log('drag',this.tagName);
}
function seenHandler(evt,f){
if (!evt.handlers) evt.handlers=[];
for (var i=evt.handlers.length;i--;) if (evt.handlers[i]==f) return true;
evt.handlers.push(f);
return false;
}
The above does not work with IE8, even if you use the global window.event object; the same event object is not reused during bubbling.
The Question(s)
However, I'm not sure if the above is guaranteed to work…nor if it's as elegant as it could be.
Is the same event object guaranteed to be passed up the chain during propagation?
Is the event object guaranteed to never be re-used for different event dispatches?
Is the event object guaranteed to support having an expando property added to it?
Can you think of a more elegant way to detect if the same event handler function has been seen already on the current dispatch of an event?
Real World Application
Imagine this SVG hierarchy…
<g>
<rect … />
<rect … />
<circle … />
</g>
…and a user who wants to be able to drag the whole group by dragging on any rect within it, and also to be able to drag just the circle by dragging on it.
Now mix in a generic dragging library that handles most of the details for you. I'm proposing to modify this library such that if the user adds a drag handler to both the <g> and the <circle> then dragging on the circle automatically prevents the dragging from initiating on the group, but without stopping propagation of all mousedown events (since the user might have a high level handler for other purposes that is desirable).
Here's a generic solution that works in the current versions of all major browsers...but not in IE8 and may not be guaranteed to work in future versions:
// Solution supporting arbitrary number of handlers
function seenFunction(evt,f){
var s=evt.__seenFuncs;
if (!s) s=evt.__seenFuncs=[];
for (var i=s.length;i--;) if (s[i]===f) return true;
evt.handlers.push(f);
return false;
}
// Used like so:
function myHandler(evt){
if (seenHandler(evt,myHandler)) return;
// ...otherwise, run code normally
}
Alternatively, here is a less-generic solution that is slightly-but-probably-not-noticeably faster (again, not in IE8 and maybe not guaranteed to work in the future):
// Implemented per handler; small chance of error with a conflicting name
function myHandler(evt){
if (evt.__ranMyHandler) return;
// ...otherwise, run code normally
evt.__ranMyHandler = true;
}
I will happily switch the acceptance to another answer with proper specs provided.
This looks good in Firefox, and can probably be adapted to non-standard browsers, since jQuery essentially does this with delegate and live. It uses stopPropagation and target , both of which are standard.
var d = document.querySelector('div'),
b = document.querySelector('button');
d.addEventListener('mousedown',clickPick,false);
d.addEventListener('mousedown',startDrag,false);
function clickPick(evt){
console.log('click', this.tagName);
}
function startDrag(evt){
console.log('drag', evt.target.tagName);
evt.stopPropagation();
}
For me, the order when you click on the button is reversed. Yours has "drag BUTTON" then "click DIV", while mine is the opposite. However, I think it's undefined in the original.
EDIT: Changed to use target. IE before 9 uses a non-standard srcElement property that means the same thing.
Right let’s get this out the way first. Yes, I want to hide the context menu. No, I’m not trying to prevent someone lifting content off my page. Its intended use is input for an in-browser game and it will be limited to a specific area on the webpage.
Moving from the ideological to the technical...
var mouse_input = function (evt) {
// ...
return false;
}
document.onmousedown = mouse_input; // successful at preventing the menu.
document.addEventListener('mousedown', mouse_input, true); // unsuccessful
Could someone explain to me why the addEventListener version is unable to stop the context menu from firing? The only difference I was able to see in Safari's Web Inspector was that document.onmousedown had a isAttribute value that was true whilst the addEventListener version had the same value as false.
So my unfruitful search suddenly became fruitful.
var mouse_input = function (evt) {
evt.preventDefault();
}
document.addEventListener('contextmenu', mouse_input, false);
Works for Safari, Firefox, Opera. preventDefault() stops the usual actions from happening. I had to change the event that was listened for to accommodate for Safari and it is more logical anyway. Further information: functions that implement EventListener shouldn’t return values so return false had no effect.
To explain the difference .. element.onmousedown = somefunction; is an absolute assignment; you are replacing the event handler on the element. element.addEventListener(...) is, as the name implies, adding a handler in addition to any handler(s) already attached for the event.
This code below works fine
$('html').bind('mousewheel', function(event, delta) {
window.parent.scrollBy(-120 * delta,0);
return false;
});
but this one doesn't, can anyone tell me why. I'd love to know.
$(window.parent).bind('mousewheel', function(event, delta) {
window.parent.scrollBy(-120 * delta,0);
return false;
});
I'd like to clarify that the window selector doesn't work in this case either.
The problem may be that jQuery's event handler wrapper must use window.event to retrieve the current event in IE. If you set a handler from window A on an event in window B, the script in window A will be looking at window A's window.event, whilst the event is actually occurring on window B.
But there may be more issues than that, too. Cross-window/frame scripting is fraught with difficulties and jQuery is not particularly designed to work around them. To make jQuery work properly cross-frame you will generally need an instance of jQuery in both windows, and you should only use the corresponding instance of jQuery ($) to interact with each window.
eta re comment:
OK, having looked into mousewheel further, I don't know how your code can be working in Firefox (it certainly doesn't for me). Firefox doesn't support mousewheel events at all; instead it supports DOMMouseScroll events. Also for the other browsers that support mousewheel, it should be bound to a DOM Node rather than the window. So I guess what you're looking for is:
if ('MouseScrollEvent' in window) {
$(document).bind('DOMMouseScroll', function(event) {
return scroll(event.detail*-40);
});
} else {
$(document).bind('mousewheel', function(event) {
return scroll(event.wheelDelta);
});
}
function scroll(d) {
window.scrollBy(-d, 0);
return false;
};
(However in WebKit this will stop scrolling when the mouse moves out of the horizontal area corresponding to the viewport width. You may prefer to bind the events to the wider element like the div, if it fills the browser.)
I am looking for the most proper and efficient way to bind Javascript events; particularly the onload event (I would like the event to occur after both the page AND all elements such as images are loaded). I know there are simple ways to do this in jQuery but I would like the more efficient raw Javascript method.
There are two different ways to do it. Only one will work; which one depends on the browser. Here's a utility method that uses both:
function bindEvent(element, type, handler) {
if(element.addEventListener) {
element.addEventListener(type, handler, false);
} else {
element.attachEvent('on'+type, handler);
}
}
In your case:
bindEvent(window, 'load', function() {
// all elements such as images are loaded here
});
I know you did only ask about how to bind events. But Ooooo boy the fun doesn't end there. There's a lot more to getting this right cross-browser than just the initial binding.
#d.'s answer will suffice just fine for the specific case of the load event of window you're looking for. But it may give novice readers of your code a false sense of "getting it right". Just because you bound the event doesn't mean you took care to normalize it. You may be better of just fixing window.onload:
window.onload = (function(onload) {
return function(event) {
onload && onload(event);
// now do your thing here
}
}(window.onload))
But for the general case of event binding #d.'s answer is so far from satisfying as to be frustrating. About the only thing it does right is use feature-detection as opposed to browser detection.
Not to go on too much of a rant here but JavaScript event binding is probably the #1 reason to go with a JavaScript library. I don't care which one it is, other people have fixed this problem over and over and over again. Here're the issues in a home-brewed implementation once inside your handler function:
How do I get a hold of the event object itself?
How do I prevent the default action of the event (eg clicking on a link but not navigating)
Why does this point to the window object all the time?
(Re mouse events) What are the x/y coords of the event?
Which mouse button triggered the event?
Was it a Ctrl-click or just a regular click?
What element was the event actually triggered on?
What element is the event going to? (ie relatedTarget, say for blur)
How do I cancel the bubbling of the event up through its parent DOM?
(Re event bubbling) what element is actually receiving the event now? (ie currentTarget)
Why can't I get the freaking char code from this keydown event?
Why is my page leaking memory when I add all these event handlers?? Argh!
Why can't I Unbind this anonymous function I bound earlier?
And the list goes on...
The only good reason to roll your own in these days is for learning. And that's fine. Still, read PPK's Intro to browser events. Look at the jQuery source. Look at the Prototype source. You won't regret it.
Something like that
window.onload = (function(onload) {
return function(event) {
onload && onload(event);
$(".loading-overlay .spinner").fadeOut(300),
$(".loading-overlay").fadeOut(300);
$("body").css({
overflow: "auto",
height: "auto",
position: "relative"
})
}
}(window.onload));
window.onload = function() {
// ...
};