Mobile Safari Event Issue - javascript

I have designed a website with a menu that is initially invisible. When the user clicks on a button, the menu becomes visible. There are two ways for the user to hide the now visible menu:
Click the button that caused the menu to become visible
Click anywhere on the web page that isn't the menu
The way I have coded the second option is to tie an onclick event to the window element, and have it compare where the user clicked to the menu's position to determine if the menu should be hidden. This works great in Firefox and Safari, but it fails in Mobile Safari.
I noticed that the window onclick event only fires when I click on another element with an onclick event already assigned. If I click on an element with no event(s) assigned, the window's onclick event never fires. If I click on the button which displays the menu, it fires along with the event tied to the button.
Is it possible to assign events to the window element in Mobile Safari?

I'v been encountering this same problem. Here is what worked for me. (Note: I am working within a Modernizr and jQuery context)
First, I add a custom Modernizr class using Modernizr's addTest Plugin API to test for iOS, which will add the class appleios or no-appleios accordingly.
Because in my research the body seems to fire events on it's own agenda, I am taking a little precaution by wrapping all the document's content with an element in an iOS context. Then I add an event handler to this element.
$(".appleios body").wrapInner('<div id="appleios-helper" />');
$("#appleios-helper").bind("mouseup", function(){return;});
What was suggested earlier in this thread is using void(0). I did some quick testing, and found that void(0) as the event just wasn't causing touches on the body to be recognized. When I plugged in my own "empty" function in the form of function(){return;} things started working.
This all hinges on the fact that no events are fired in Mobile Safari unless the element explicitly has events to fire (Safari Web Content Guide.) By inserting this empty event on the wrapper, things will bubble up to the body.
If you're doing strait JavaScript with none of these libraries, the same effect could be achieved in the HTML markup
<html>
...
<body>
<div id="appleios-helper" onmouseup="function(){return;}">
...
</div>
</body>
</html>
This worked for me to hide tooltips when touching anywhere on the document's body. Your mileage may vary.

Simply adding the dummy onclick handler to the html body works for me:
<body onclick="void(0)">
Note that I am using usual live event handlers as shown below:
function liveHandler( event ) {
var target = event.target; ...}
window.addEventListener(evtype, liveHandler, true);
// evtype such as 'mousedown' or 'click'
// we use the capturing mode here (third parameter true)

This is an old question, but I struggled with the same thing today.
I found that using touchstart event works.
I solved it like this:
var isTouchDevice = 'ontouchstart' in document.documentElement;
if (isTouchDevice) {
// Do touch related stuff
$(document).on('touchstart', function (event) {
// Do stuff
});
} else {
// Do non-touch related stuff
$(document).on('click', function () {
// Do stuff
});
}

You could just add onclick="void(0);" to some <div> that covers the whole page so that no matter what, you are always clicking on an element that has an onclick event. Not a great solution, though.
I'd prefer not having the onclick event be tied to the window. Why don't you create a container <div> that has that event on it. Then handle it just like you currently are.

You can also:
$('body').css('cursor', 'pointer');
No idea what those "engineers" at Apple are doing. LOL.
This has problems though. You wouldn't want to do this on every touch device. Only touch devices that don't also have a pointing device (Laptops with Touch Screens, for example).
Source: http://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html
The conclusion of the article is this:
So I don’t understand why all this is the case, but it most certainly is the case. If you’re having bubbling problems, just add an empty-function event handler anywhere between the body and the element, and you’re set to go. But it shouldn’t be necessary.

Related

Adding custom event to programatically open contextmenu

As said in the title, I am trying to customize the contextmenu event. The situation is this: I want to catch the event, preventing it from firing on some elements (I'm ok here, all good), then I want to call it targeting another element (this is not working). At first I just tried dispatching it by creating a custom event and using myTargetElement.dispatchEvent(), the custom element does fire, but context menu won't open.
The reason I need this is that I want to open the contenteditable context menu when the user clicks anywhere. I've tried something similar to the last example on this MDN page, and I logged the event type, it is firing. Here's some example code of what I'm doing.
HTML
<div id="prevent">This div will prevent default event behaviour.</div>
<div id="my-target" contenteditable>Fire here the event and open context menu</div>
For instance, I cannot put one div inside the other.
JS
function showMenu(){
const preventer = document.getElementById('prevent');
const myTarget = document.getElementById('my-target');
const myEvent = new Event('contextmenu', {
bubbles:false //I had to use this, as setting it true was logging an error on Firefox
});
myTarget.dispatchEvent(myEvent);
console.log(myEvent.type); //it does log the event name
}
The listener that prevents default is not important, as when I just run the showMenu() (even when removing every other bit of js) on console it still has not the intended effect. I'm also able to listen to the 'contextmenu' event when I add a listener and run showMenu().
I'm beginning to think that there is not a direct solution to this, but workarounds and ideas would be really appreciated.

How to stop double-touching in iOS from scaling the entire page

I have a webapp with an inner-pane inside an outer-window.
The user can do various touch operations, e.g. zoom-in in the inner-pane via 2-finger pinch, without zooming-in the outer-window, touch move to pan the inner window etc..
On Chrome on Android, the app works as expected.
But Safari on iOS device (iPad), the entire window sometimes scales when quickly double-toching on the border-line of the inner window.
I found out that I can prevent this side effect by adding an event listener on the touchend event.
window.addEventListener('touchend', onTouchEnd5, {capture: false, passive: false});
and calling event.preventDefault() in the binded function.
async function onTouchEnd5( event ) {
console.log('BEG onTouchEnd5');
event.preventDefault();
};
But this causes another side effect where touch-clicks on buttons are not intercepted.
I tried adding eventlisteners on other events that are possibly trigerred by the default behavior, e.g. click, dblclick, touchcancel, fullscreenchange, fullscreenerror, resize, onscroll
The idea was to try and prevent the default behaviour for these events in case that the side effect is caused there.
(the rational was that iphone/ipad can trigger unexpected events).
From the listed events above, the event onscroll is trigerred.
In the binded function to this event, I added preventDefault():
function onScroll5( event ) {
console.log('BEG onScroll5');
event.preventDefault();
};
But the I can still cause the entire window to rescale by quickly double-touching on the border-line of the inner window.
How can I prevent the entire window from scaling in iOS when double touching?
Note:
The scaling of the entire window is the latest observation.
Before that, I found that the entire window scales when e.g. doing a twon-finger pinch in another section of the window.
The solution in this case, was to add preventDefault on another event listener binded function.
Given that there are multiple events in the app, a more general question is:
Is there a way to prevent the entire window from scaling alltogether, in eny event?
Thanks
I've found a bit of a hacky solution.
Make a page called /mobile, or use / and put all your content on a page called /maincontent and use that URL in the next step.
In that page, add an iframe, like this:
<iframe src='/ (or "maincontent")' style='width:100vw;height:100vh;top:0;left:0;position:fixed;border:none;' frameborder='0'></iframe>
I know it's a bit hacky, but it works for me on iOS 14.
I found out the solution.
I had the following DOM structure:
<body>
<div id="div1">
<div id="div2">
<div id="div3">
<canvas>...</canvas>
</div>
</div>
<div id="div4">
<div id="div5"><button></div>
</div>
</div>
</body>
I initially tried to add event.preventDefault() in onTouchStartDiv3(), which listens to events in the canvas element (the closest upstream element with event listener is div3).
This solved the problem of double-touch in the border but caused another problem:
onTouchMoveDiv3() was called immediately (without even moving the finger), which caused other side-effects. (I have some state machine in the level, that depends on the onTouchStartDiv3(), onTouchMoveDiv3(), onTouchEndDiv3()).
I then tried to add event listeners at the window/document levels (onTouchStartWindow(), onTouchStartDocument()) and call event.preventDefault() in them.
This again solved the problem of double-touch in the border but caused another problem:
The button (div5), stopped responding to touch events.
The reason was that, because there is no specific event listener on the button, the event was intercepted higher up, and default behaviour was applied.
Because the window/document event listener called event.preventDefault(), this behaviour was prevented (which stopped the button from responding to touch events).
Finally, I added event listener at the touched border level (div2).
Inside onTouchStartDiv2() I compare event.currentTarget (div2), to event.target where the event originated.
If the event.target is div2 then the event originated from clicking on the border, so I apply event.preventDefault() to prevent the side-effect of resizing the entire image on iOS.
If the event.target is not div2 then the event originated from clicking, e.g. in the canvas, so I do not apply event.preventDefault() to maintain the state machine.
With this logic, I solved the problem:
when touching the canvas element (div3 is the closest upstream element with event listener), the regular behaviour was applied.
when touching the border element (div2), event.preventDefault() was applied to the event listener, and prevent the default behaviour of resizing the entire window (which I wanted to disable).
when touching the button element (div5), the regular behaviour was applied so the button responded to touch events.

Is it possible to intercept/override all click events in the page?

I've written an html5 application which is supposed to work on mobile devices. 90% of the time it works fine however in certain devices (mostly androids 4.0+) the click events fire twice.
I know why that happens, I'm using iScroll 4 to simulate native scrolling and it handles the events that happen inside the scroll.(line 533 dispatches the event if you're interested) Most of the time it works fine but in certain devices both the iScroll dispatched event and the original onClick event attached to the element are fired, so the click happens twice. I can't find a pattern on which devices this happen so I'm looking for alternatives to prevent double clicks.
I already came up with an ugly fix that solves the problem. I've wrapped all the clicks in a "handleClick" method, that is not allowed to run more often than 200ms. That became really tough to maintain. If I have dynamically generated content it becomes a huge mess and it gets worse when I try to pass objects as parameters.
var preventClick = false;
function handleClick(myFunction){
if (preventClick)
return;
setTimeout(function(){preventClick = true;},200);
myFunction.call():
}
function myFunction(){
...
}
<div onclick='handleClick(myfunction)'> click me </div>
I've been trying to find a way to intercept all click events in the whole page, and there somehow work out if the event should be fired or not. Is it possible to do something like that?
Set myFunction on click but before it's called, trigger handleClick()? I'm playing with custom events at the moment, it's looking promising but I'd like to not have to change every event in the whole application.
<div onclick='myfunction()'> click me </div>
You can do that with the following ( i wouldn't recommend it though):
$('body').on('click', function(event){
event.preventDefault();
// your code to handle the clicks
});
This will prevent the default functionality of clicks in your browser, if you want to know the target of the click just use event.target.
Refer to this answer for an idea on how to add a click check before the preventDefault();
I don't like events on attributes, but that's just me.
Thinking jquery: $(selector).click(function(){ <your handler code> } you could do something like:
$(selector).click(function(event){
handleClick(window[$(this).attr("onclick")]);
};
of course, there wouldn't be any parameters...

JavaScript in Chrome

I have a div which onmouseover, displays a panel and onmouseout makes it disappear. JavaScript code is as follows:
function ShowPanel() {
document.getElementById("thePanel").style.display = "inline";
}
function HidePanel() {
document.getElementById("thePanel").style.display = "none";
}
The code works in Firefox and IE perfectly. The problem is Chrome. It works until the mouse is on the textbox in the panel. When the mouse goes on the textbox the onmouseout event is called even though the textbox is part of the panel and should remain open.
What you need is the behavior of the onmouseenter event instead of onmouseover and onmouseleave instead of onmouseout. The problem is that those events work only in IE (they actually got those ones right). You either need to simulate that behavior taking into account all of the differences in the event handling in different browsers, or just use a good JavaScript library that would take care of that for you. For example jQuery has .mouseenter() and .mouseleave() that are simulated on browser that don't support those events natively, and even a nice shortcut .hover() to set both at the same time.
I wouldn't recommend doing it manually unless you really know all of the quirks and inconsistencies of event models in different browsers (and you don't since you asked this question) but if you want to see how jQuery is doing it then see events.js and search for mouseenter and mouseleave.
In Chrome as you've found the mouseout event is fired whenever you move from the parent element (on which the handler is registered) and child elements contained within it.
The simple fix is to use jQuery, which will simulate mouseleave events (which don't suffer that problem) on browsers that don't support it.
Alternatively, in your mouseout handler, look at the toElement property of the event, and traverse its parent list and see if your original parent is in that list. Only process your action if it was not in the list.
document.getElementById('outer').addEventListener('mouseout', function(ev) {
var el = ev.toElement;
while (el && el !== document.body) {
if (el === this) {
console.log('mouseout ignored');
return; // enclosed - don't do anything
}
el = el.parentNode;
}
console.log('mouseout');
}, false);
demo at http://jsfiddle.net/raybellis/s4EQT/

Capture click on div surrounding an iframe

How can I capture a click or mousedown event on a div surrounding an iframe. I've tried attaching the function to click event on the div but since the iframe never bubbles the event up to the surrounding div the function is never called. Is there a way I can capture the event on the div and then propagate it to the iframe for default action?
If the click is in the iframe area, the iframe context handles the click event, it does not bubble up to the iframe parent. So the div will never register the click event at all if it happened in the iframe area.
Furthermore, if the iframe contains a page that does not belong to the same domain as the iframe parent, any interaction is prohibited (re. same origin policy).
When the same origin policy is met, there are a few things you can do, you could call a method in the iframe parent context:
top.parentFunction();
So in the iframe you add an event listener that delegates to the iframe parent (accessible with the top reference.
Propagating events is a lot more complicated, so I'm simply going to refer to Diego Perini's NWEvents library. I believe his event system to be one of the better ones out there and he's particular on iframe interaction.
I certainly would not start writing your own code to achieve this, this can easily be a year long project if you want to do it properly and even then will be inferior to Diego's work.
There's no "good" way to do it, but if you really need to detect a click on an Iframe, you can kind-of do it in the latest browsers.
<iframe src="http://mtw-ed.com/" id="iframe" style=""></iframe>
<script type="text/javascript">
var inIframe = false;
function checkClick() {
if (document.activeElement
&& document.activeElement === document.getElementById("iframe")) {
if (inIframe == false) {
alert("iframe click");
inIframe = true;
}
} else
inIframe = false;
}
setInterval(checkClick, 200);
</script>
This script will check every 200ms whether the user is in the Iframe. Of course, they may not have clicked on the Iframe to get there, but I'm afraid this is the best you can do without #BGerrissen's solution.
It will detect the first 'click' only, unless you click out again. It only works in really modern browsers.
You can use a library like porthole to pass messages between parent and iframe, even across domains. Using this wouldn't exactly propagate the event (you won't be able to get the event object), but you can create your own event in the form of a simple message, and then handle it in the parent as a click.
Here's their example
However, I've used Brendon's answer as it's simpler works for my current need.
If you land here because you need to track a click on a PayPal button (like me), and you have access to the JavaScript SDK, you can listen to the click by adding the onClick callback in the initialization.
Example:
paypal.Buttons({
onClick() {
// here you can track the click
}
}).render('#paypal-container');
Link to the docs: https://developer.paypal.com/sdk/js/reference/#link-oninitonclick.

Categories

Resources