Given the following:
$(window).bind("popstate", function() {
alert('popstate');
});
On first load, the alert fires with FireFox and Chrome but not Safari. Why is that? Anyone else seen this and know how to best solve for this?
See the code from pjax. pjax is fairly popular open source library now, so the below logic might be the best to avoid this issue.
var popped = ('state' in window.history), initialURL = location.href
$(window).bind('popstate', function(event) {
// Ignore inital popstate that some browsers fire on page load
var initialPop = !popped && location.href == initialURL
popped = true
if ( initialPop ) return
...
https://github.com/defunkt/jquery-pjax/blob/master/jquery.pjax.js
In webkit the popstate event is fired on page load. To avoid this, this easy work around works for me:
Every time I fire history.push(...) I add the class historypushed to the body-tag:
history.push(...);
$("body").addClass("historypushed");
When I trigger the popstate event, I check for this class:
$(window).bind('popstate', function(e) {
if($("body").hasClass("historypushed")) {
/* my code */
}
});
The situation is now reversed. Chrome has fixed the bug and now fires popstate on page load but Firefox 4 (since RC) has departed from the spec and now does not fire popstate!
UPDATE: The HTML5 spec was changed in 2011 to state popstate should not fired on page load. Both Firefox and Chrome now do the right thing as of Firefox 4 and Chrome 34.
An easy way to avoid this issue is to set the first argument on pushState to true then check against onpopstate. Similar to the pjax example but a bit more straightforward. The below example will run doSomething() for every popstate event except for the first page load.
function setupPopState() {
if (history.popState) {
# immediately replace state to ensure popstate works for inital page
history.replaceState(true, null, window.location.pathname);
$(window).bind('popstate', function(event) {
if (event.originalEvent.state) {
doSomething();
}
});
}
}
There was a bug in Webkit that incorrectly implemented the "popstate" event. Check out this simple post explaining the problem (cool little show and tell): http://www.bcherry.net/playground/pushstate
My suggestion would be to implement your own "popstate" event tracker for Safari. Something like this:
$(window).load(function(){
function fire_popstate(){
$(this).trigger("popstate"); // fire it when the page first loads
}
var lasthash = window.location.hash;
setInterval(function(){
var currenthash = window.location.hash;
if(lasthash != currenthash){
fire_popstate();
}
}, 500);//check every half second if the url has changed
});
You could wrap that statement in a browser test to check for safari. Even better see if "popstate" has been fired by the time the DOM is ready and then apply the inner function to replace the implementation. The one thing you don't want to happen is have two popstate events to be fired (duplicating your event handler logic, great way to lock up the UI).
This is my workaround.
window.setTimeout(function() {
window.addEventListener('popstate', function() {
// ...
});
}, 1000);
I convert #Nobu & #Tamlyn answers into an object, I also add a little
fix by adding "window.history.state !== null". In some browsers the
history.state exists, but it's null so the it was not working.
/**
* The HTML5 spec was changed in 2011 to state popstate should not
* fired on page load. Chrome(34) & Firefox(4) has fixed the bug but
* some browsers (e.g. Safari 5.1.7) are still fire the popstate on
* the page load. This object created from the Pjax Library to handle
* this issue.
*/
var popstatePageloadFix = {
popped : ('state' in window.history && window.history.state !== null),
initialUrl : location.href,
initialPop : false,
init : function() {
this.initialPop = !this.popped && location.href == this.initialUrl;
this.popped = true;
return this.initialPop;
}
};
$(window).on("popstate", function (event) {
// Ignore initial popstate that some browsers fire on page load
if ( popstatePageloadFix.init() ) return;
...
});
Thanks #Nobu!
Thanks #Tamlyn!
This answer to a similar question suggests to check for boolean truth of event.state in the popstate event handler:
window.addEventListener('popstate', function(event) {
if (event.state) {
alert('!');
}
}, false);
You can also tie your callback function to popstate event like this:
window.onpopstate = callback();
Check here for more information on that solution
Related
I'm trying to create a userscript that causes the native popup blocker to apply to all popups, even those that were the result of user interaction.
I came up with the following idea:
window.addEventListener('click', function(e) {
console.log(e.isTrusted);
if (e.isTrusted) {
e.stopImmediatePropagation();
e.preventDefault();
e.target.dispatchEvent(new e.constructor(e.type, e));
}
}, true);
button.addEventListener('click', function(e) {
window.open('about:blank');
});
<button id="button">Test</button>
(In the snippet window.open won't work because of the iframe sandbox.)
Basically the idea is to add an event listener to the page that replaces any click event that is trusted with a copy of it that isn't trusted. However this doesn't work and the popup is still opened.
Is there any way to accomplish this?
Relevant specification here:
https://html.spec.whatwg.org/multipage/interaction.html#triggered-by-user-activation
This just answers the X part of this XY problem, because I see no real use for the Y.
If you wish to block all popups, then window.open = null; will already block all the ones made from this method, then you may also want to block the ones from anchor elements,
document.addEventListener('click', e => {
if(e.target.nodeName === 'A' && e.target.target === "_blank") {
e.preventDefault();
}
});
Now, you'll have to apply this in all the documents (i.e in iframes too) and you should be good.
But note that there are many legit reasons for pages to open popup windows, and disabling it will definitely break a lot of websites.
The pop-blocker's handling of event contexts seems to be complex. Even generating a setTimeout with an evaluated string does not break the context. In Firefox there is a period of one second after a click to perform a popup, later it is cosidered not to be triggered by a click.
However, I could get Firefox to block popups using setInterval. I did not test it yet in Chrome.
We overwrite the window.open method with a custom one:
window.open = (function()
{
const
openArgs = [],
fnOpen = window.open.bind(window)
;
setInterval( () => { for(let args; args = openArgs.pop(); fnOpen(args)); }, 100);
return function open(...args) { openArgs.push(args); }
})();
<button onclick="window.open('http://example.com');">button</button>
open
I develop a web GUI for a special tablet. This tablet is running with Linux and the used browser is Chromium. The application is a web application with PHP, HTML5, JQuery and JavaScript. Now I run into a problem. The screen is a touchscreen and the user is able to navigate through the application by touch the screen. However now we decided to add a feature for saving electricity. This feature will shutdown the background light after three minutes. To turn on the backlight again, the user should touch the screen again. This leads to this problem, because on any touch the buttons are also pressed even if the background light is shutdown. I want to prevent this by discarding all clicks on the touchscreen if a cookie is set. If this cookie is not set the touchscreen and the application should work as desired. How can I solve this problem?
I installed an event listener to register all clicks and to reset the time.
window.addEventListener('mousedown', function(e){
$.get('php/timeupdate.php', function(){});
}, false);
Code used to stop the execution:
$(document).on('click', function(event) {
$.get('php/getwakeup.php', function(e){
if(e==='true'){
//event.preventDefault(); // I tried all three possibilities
//event.stopImmediatePropagation();
event.stopPropagation();
}
});
});
You can try this:
$(document).on('click', function(event) {
// get your cookie
if( cookie is set ) {
event.stopPropagation();
}
});
event.stopPropagation(); stops every event handling from where you called it =)
EDIT:
You have to set your $.get call synchronous or do it completely diffrent. Take a look at the jQuery.Ajax documenation. There is a parameter called "async".
But be careful unless the call is ready nothing else will be executed on you page! So if your script doesn't answer nothing else will work on your site.
The better solution would be setting ja recurring call that will get the information you need. Set it to every two seconds (setInterval is your friend here). If your response is true than set a global variable that you can check in your onDocumentClick event.
window.isBacklightOff = false;
setInterval(function() {
$.get('php/timeupdate.php', function(e) { window.isBacklightOff = !!e; })
}, 2000);
$(document).on('click', function(event) {
// get your cookie
if( window.isBacklightOff === true ) {
event.stopPropagation();
}
});
When the back light goes off you can set some flag handleEvents=false;
So when the flag is on don't handle any events.
Now when the back light is on you can set handleEvents = true.
$(document).on('click', function(event) {
// get your flag say handleEvents
if( !handleEvents ) {
event.stopImmediatePropagation();
return;
} else {
//do your biz logic send ajax..etc
}
});
Reason why your code above is not working:
$(document).on('click', function(event) {
$.get('php/getwakeup.php', function(e){
if(e==='true'){
//event.preventDefault(); // I tried all three possibilities
//event.stopImmediatePropagation();
event.stopPropagation();
}
});
});
The function inside $.get is async and called on success in that you are setting the event to stop propagating...well by that time when the success function is called the event is already complete and has called all the listeners.
So in short you must not do the event stop propagation inside the success function.
This is simplified code to exactly reproduce a problem I'm having with jQuery.
I would expect it to cancel the original click, then trigger the click event again which in turn would cause the browser to load the href of the url.
<script type="text/javascript">
$( function()
{
var confirmed = false;
$('a').click( function( event )
{
if ( confirmed == false )
{
event.preventDefault();
confirmed = true;
$(event.target).trigger( 'click' );
}
});
});
</script>
Original Question Here
I do not want to change window.location. I want to trigger so that any event handlers that have been bound to the click event will fire too. It just so happens that I also want the browser to follow the link.
.trigger('click') won’t trigger the browser’s default action — it will just trigger jQuery event handlers bound to that event on that element.
Take a look at this answer — you need to create and fire a click event yourself. Here’s the code from that answer:
function fireEvent(obj,evt){
var fireOnThis = obj;
if( document.createEvent ) {
var evObj = document.createEvent('MouseEvents');
evObj.initEvent( evt, true, false );
fireOnThis.dispatchEvent( evObj );
} else if( document.createEventObject ) {
var evObj = document.createEventObject();
fireOnThis.fireEvent( 'on' + evt, evObj );
}
}
Your event handler will always be run when you trigger a "click" event, and before the default behavior happens. So no, it's not a bug.
edit — If you want the default action to happen when the element is clicked and your various conditions are satisfied, just return from your handler.
$('a').click( function( event )
{
if ( confirmed == false )
{
event.preventDefault();
confirmed = true;
}
else
return;
});
When your handler returns, the browser will carry on with the default behavior.
edit again — and of course if you want to simply carry out programmatically the default action of an <a> tag with an "href" attribute, it's just:
window.location = $(element).attr('href');
No need for a "click" event at all.
preventDefault() doesn't cancel the jquery bound click action; it cancels the 'default' click action that is inherent to an anchor tag. unbind() is the proper function for canceling any action/function that is bound to an object.
It looks like you have one state where you want to inhibit the default action (and update a flag), and another state where you simply want the default action to apply. Wouldn't removing the else case entirely do the trick? With no preventDefault() the default click action will still run.
Edit: Pointy's answer now shows your code updated similarly.
Edit: I'm still not entirely sure what you're going for, but if for some reason you must trigger manually, one way to prevent a stack overflow due to recursion would be to assign a custom event (i.e., not click), and then always suppress the default action. Then in your conditional, either do or don't fire the custom event.
So, something like this (untested):
$('a')
.bind('fancyclick', function () { // Your special click logic
changeStatusOf( confirmed );
doCrazyStuff();
location.replace( $(this).attr('href') ); // or whatever
})
.click( function (e) {
e.preventDefault(); // Always suppress natural click
if ( confirmed ){
$(this).trigger('fancyclick'); // Fire the special click
}
});
I'll simply write while I analyse, hoping it'll be easy to follow and easy to compare to your code. Kick me when I can optimize/enhance my replies in any way.
checking - part 1
Move the "preventDefault" outside of the "if" statement! If you don't move it outside of the "if", the code will skip the "preventDefault" if confirmed == true.
<script type="text/javascript">
$( function()
{
var confirmed = false;
$('a').click( function( event )
{
event.preventDefault();
if ( confirmed == false )
{
confirmed = true;
$(event.target).trigger( 'click' );
}
});
});
</script>
checking - part 2
Besides that, I'm wondering about the existence of "var confirmed" in general. The code would also work flawless without it since you're not using the confirmed variable in the function anywhere else...
<script type="text/javascript">
$( function()
{
$('a').click( function( event )
{
event.preventDefault();
$(event.target).trigger( 'click' );
});
});
</script>
checking - part 3
Knowing you can replace the trigger with a simple click, did you try that?
<script type="text/javascript">
$( function()
{
$('a').click( function( event )
{
event.preventDefault();
$(event.target).click();
});
});
</script>
checking - part 4
If the click still fails on the target element, it's definitely time to inspect (using "FireBug on Firefox" or alike) if "event.target" holds an object at all. You never know...
checking - part 5
One more thing: I don't see any Document Ready checking, so I hope you've put that script at the end of your file, right before the "</body>" tag. If you load it in the "<head>" without checking Document Ready, it might happen that javascript tries to bind the event to an element that is yet to be loaded into the dom... which would be like throwing feathers against a 9 Inch steel plate... nothing will happen. ;)
that's all
That's all that comes to mind as an answer to your question. One of them could fix the issue you're having. At least, that's what I hope. These little snippets here at stackoverflow leave massive probabilities related to what we don't see. It's hard to be sure what might be missing when you don't have the "complete view". ;)
How about doing something like creating a link offscreen that would handle the final click? That way, you can prevent the click of the original link, handle the code, then proceed to trigger the click of the hidden copy.
I wonder if its possible to prevent double-tap-to-zoom and double-tap-to-center on a specific HTML element in Safari iOS (iPad 1) ?
Because my small HTML5 game forces users to make fast clicks (or taps), which are interpreted as double clicks, and when it happens - the page changes zoom and centers itself.
Detecting double clicks (like in this answer - Safari iPad : prevent zoom on double-tap) smells bad..
Wrong answer #1:
<meta name="viewport" content="width=device-width, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"> - does not suit my purposes, because it will block any zoom.
Wrong answer #2: maybe would .preventDefault() on click event alone be enough for that ? - Does not have any effect.
There's no other way than catching the events you want to prevent, and call preventDefault() on them, as you had already more or less figured out.
Indeed, some particular CSS properties / values may change the global site behavior (fixed width or fixed, for example), but you're not safe from changes to the OS (see fixedhandling change in iOS5), nor do these changes necessarily prevent all behavior (pinch might be off, but not double-tapping).
So, the best way to disable default behavior only for double-tapping is to take advantage of the count of touches iOS provides: if we have only one contact, then we're tapping. Two, this means we're pinching.
The following setup code provides that functionality:
var elm = document.body; // or some selection of the element you want to disable
var catcher = function(evt) {
if (evt.touches.length < 2)
evt.preventDefault();
};
elm.addEventListener('touchstart', catcher, true);
Demo on jsFiddle.
Note: the third parameter (true) to addEventListener means that we want to capture events, that is catch them all, even for our descendant children.
I am preventing doubletaps like this:
var doubleTouchStartTimestamp = 0;
$(document).bind("touchstart", function (event) {
var now = +(new Date());
if (doubleTouchStartTimestamp + 500 > now) {
event.preventDefault();
}
doubleTouchStartTimestamp = now;
});
The elegance lies within the fact, that no timeouts are needed. I only update a timestamp. It only gets compared on the next touchstart. Works for me on iOS 6.
Doubletaps further down the dom are not affected.
The same works without jQuery, as well:
var doubleTouchStartTimestamp = 0;
document.addEventListener("touchstart", function (event) {
var now = +(new Date());
if (doubleTouchStartTimestamp + 500 > now) {
event.preventDefault();
}
doubleTouchStartTimestamp = now;
});
I wrote a jQuery plugin for the same purpose - selectively disabling double-tap zoom on given page elements (in my case, navigation buttons to flip pages) I want to respond to every tap (including double-tap) as a normal click event, with no iOS "touch magic", no matter how fast the user clicks it.
To use it, just run something like $('.prev,.next').nodoubletapzoom(); on the elements you care for. The principle it uses is to listen for consecutive touchstart events on a node within 500ms, and running event.preventDefault() on the second, unless other touches are active at the same time. As that preventDefault consumes both touches, we also synthesize the two "missed" click events for the node, so your intended touch action happens as many times as the user intended.
Apple has a lot of tips with specialized tags for webkit (Safari). View Official Docs
What iOS version/Safari browser are you using? That site most definitely does not let you double-tap. I found some CSS but haven't had time to try it as I'm about to step out:
body {
-webkit-text-size-adjust:none;
margin:0px;
}
div{
clear:both!important;
display:block!important;
width:100%!important;
float:none!important;
margin:0!important;
padding:0!important;
}
You will need to implement a double tap function and preventDefault on the second tap. Here is some tested code that uses global variables that should get you started:
<button id="test1">Double Tap Me!</button>
<div id="test2">EMPTY</div>
var elm1 = document.getElementById('test1');
var elm2 = document.getElementById('test2');
var timeout;
var lastTap = 0;
elm1.addEventListener('touchend', function(event) {
var currentTime = new Date().getTime();
var tapLength = currentTime - lastTap;
clearTimeout(timeout);
if (tapLength < 500 && tapLength > 0) {
elm2.innerHTML = 'Double Tap';
event.preventDefault();
} else {
elm2.innerHTML = 'Single Tap';
timeout = setTimeout(function() {
elm2.innerHTML = 'Single Tap (timeout)';
clearTimeout(timeout);
}, 500);
}
lastTap = currentTime;
});
And a fiddle: http://jsfiddle.net/brettwp/J4djY/
JQuery approach to disable Double Tap Zoom in MVC4
To Disable the double tap (double mouse down) functionality on iOS 1+ you need to catch the touchStart Event and create an override to prevent the zoom.
// Using Single script.js and JQuery.Mobile-1.2.0 UI each page in MVC gets assigned JQuery through delegates so you don't have to do a full refresh of the page allowing you to take advantage of the data-prefetch which loads the page in the DOM when the app loads for the first time
$(document).delegate("#CashRegister", "pageinit", function () {
// To Disable 'Pinch to Zoom' Note: don't implement gester event handlers if you want to
//keep pinch to zoom functionality NOTE: i use this as my pageinit is a delegate of a page
this.addEventListener("gesturestart", gestureStart, false);
this.addEventListener("gesturechange", gestureChange, false);
this.addEventListener("gestureend", gestureEnd, false);
//handle each event by disabling the defaults
function gestureStart(event) {
event.preventDefault();
}
function gestureChange(event) {
event.preventDefault();
}
function gestureEnd(event) {
event.preventDefault();
}
//Recreate Double Tap and preventDefault on it
$(this).bind('touchstart', function preventZoom(e) {
// recreate the double tab functionality
var t2 = e.timeStamp
, t1 = $(this).data('lastTouch') || t2
, dt = t2 - t1
, fingers = e.originalEvent.touches.length;
$(this).data('lastTouch', t2);
if (!dt || dt > 500 || fingers > 1) return; // not double-tap
e.preventDefault(); // double tap - prevent the zoom
// also synthesize click events we just swallowed up
$(this).trigger('click').trigger('click');
});
Actually, .preventDefault() definitely does work... using jQuery:
var InputHandler = {
startEventType : isTouch ? "touchstart" : "mousedown"
}
$(selector).bind(InputHandler.startEventType, function(evnt) {
evnt.preventDefault();
});
Your problem with trying to prevent on .click() is that the browser isn't throwing a "click" element. Safari only fires a click to help simulate a click... But when there's a double tab, Safair doesn't through a "click" element. Your event handler for .click() doesn't ever fire, and therefore the .preventDefault() doesn't fire either.
I'm creating a popup window that has a beforeunload handler installed. When the "Close" file menu item is used to close the popup, the beforeunload handler is called twice, resulting in two "Are you sure you want to close this window?" messages appearing.
This is a bug with Firefox, and I've reported it here, but I still would like a way to prevent this from happening. Can you think of a sane way of detecting double beforeunload to prevent the double message problem? The problem is that Firefox doesn't tell me which button in the dialog the user elected to click - OK or cancel.
<script type="text/javascript">
var onBeforeUnloadFired = false;
window.onbeforeunload = function ()
{
if (!onBeforeUnloadFired) {
onBeforeUnloadFired = true;
event.returnValue = "You have attempted to leave this page. If you have made any changes to the fields without clicking the Save button, your changes will be lost. Are you sure you want to exit this page?";
}
window.setTimeout("ResetOnBeforeUnloadFired()", 10);
}
function ResetOnBeforeUnloadFired() {
onBeforeUnloadFired = false;
}
</script>
Set a variable in the handler to prevent the dialog coming up the second time. Use setTimeout to reset it afterwards.
This is definitely a FF bug. I've reported it at https://bugzilla.mozilla.org/show_bug.cgi?id=531199
The best solution I've found is to use a flag global variable that is reset after so many milliseconds, say 500 (this ensures that the function can be called again, but not immediately after its appearance).
See last code in:
http://social.msdn.microsoft.com/Forums/en/sharepointinfopath/thread/13000cd8-5c50-4260-a0d2-bc404764966d
I've found this problem in Chrome 21, Firefox 14, IE 7-9, Safari 5 (on PC).
The following works on all of these browsers. If one removes the window.onbeforeunload function during the event this will prevent the second call. The trick is to reset the window.onbeforeunload function if the user decides to stay on the page.
var window_on_before_unload = function(e) {
var msg;
// Do here what you ever you need to do
msg = "Message for user";
// Prevent next "window.onbeforeunload" from re-running this code.
// Ensure that if the user decides to stay on the page that
// this code is run the next time the user tries to leave the page.
window.onbeforeunload = set_on_before_unload;
// Prepare message for user
if (msg) {
if (/irefox\/([4-9]|1\d+)/.test(navigator.userAgent))
alert(msg
+ '\n\nThe next dialog will allow you to stay here or continue\nSee Firefox bug #588292');
(e = e || window.event).returnValue = msg;
return msg;
}
};
// Set window.onbeforeunload to the above handler.
// #uses window_on_before_unload
// #param {Event} e
var set_on_before_unload = function(e) {
// Initialize the handler for window.onbeforeunload.
window.onbeforeunload = window_on_before_unload;
}
// Initialize the handler for window.onbeforeunload.
set_on_before_unload();
Create a global variable that is set to true inside the handler. Only show the alert/popup when this variable is false.
I use the following snippet to track the exitcount
When the page loads the following variable exitCount is initialized
if (typeof(MTG) == 'undefined') MTG = {};
MTG.exitCount=0;
and in the Window unload event
$(window).bind("beforeunload", function(){
if (MTG.exitCount<=0)
{
//do your thing, save etc
}
MTG.exitCount++;
});
I've found that instead of doing your own call to confirm(), just do even.preventDefault(); within the beforeunload event. Firefox throws up its own confirm dialog.
I'm not sure if this is the correct/standard thing to do, but that's how they're doing it.
I have a document opening another popup window with window.open. In the original window I have registered (with jquery) a listener for "unload" event like this:
var popup_window = window.open(...)
$(popup_window).on('unload', function(event) ...
I have came across this page because the event was effectively triggering twice. What I have found is that it is not a bug, it triggers twice because it fires once for "about:blank" page being replaced by your page and another for your page being unloaded.
All I have to do is to filter the event that I am interested in by querying the original event:
function (event) {
var original_url = e.originalEvent.originalTarget.URL;
if (original_url != 'about:blank')
{
... do cool things ...
}
}
I don't know if this applies to the original question, because it is a special case of a window opening another, but I hope it helps.