How does one prevent the opening of a new tab or window when hyperlink behavior was created virtually by e.g. an HTML button's "click" event handling? - javascript

I can disable opening new tab or window in case of <a> tag by removing target attribute e.g. target='_blank'
In some cases instead of <a> website put <button> and add onClick() or submit() etc which programmatically generates a link and sets the target attribute.
<button id="653cde4c-1620-11ec-85b5-5ae26c154b46">
<div>click here</div>
</button>
In some webpack generated javascript file there would be something like which would not be easy to find and read minified code.
var button = document.querySelctor('653cde4c-1620-11ec-85b5-5ae26c154b46')
// Add whatever you want to add to this button element, e.g. add onClick() or submit() or something else which I am not sure of
We can see image at https://i.stack.imgur.com/8oG5w.png
In such case where <button> is providing link click functionality. I can not view and edit onClick() or submit() etc as its webpack generated javascript. I can only run my javascript code after loading of that webapge in devtools console.
How can I disable opening new tab or new window of browser in such case? Or What javascript code should I run to override <button> link behaviour?

Depending on how the webpack based code does manage the tab handling of resources one possible solution was to wrap own code around window.open or even entirely replace it with an own implementation.
As for the wrapping, based on the intercepted data, one could decide of whether one does suppress the url-handling entirely or, instead of opening a new tab/window, does a forwarding to location.href or even proceed with invoking the original window.open.
// untouchable 3rd party code
//
function alienLocationHandler({ currentTarget }) {
let { url, target } = currentTarget.dataset;
url = (url ?? '').trim();
if (url) {
target = (target ?? '').trim();
if (target) {
// due to SO's permission handling ...
//
// ... Blocked opening 'https://stackoverflow.com/' in a
// new window because the request was made in a sandboxed
// frame whose 'allow-popups' permission is not set.
//
window.open(url, target);
} else {
window.location.href = url;
}
}
}
document
.querySelectorAll('[data-url]')
.forEach(elmNode =>
elmNode.addEventListener('click', alienLocationHandler)
);
// one possible approach :: wrap around `window.open`
//
window.open = (function createAroundHandler(proceed, thisArg) {
return function aroundWindowOpen(url, target, ...rest) {
console.log('aroundWindowOpen ...', {
url,
target,
rest,
});
// - of cause all the code is just for demonstration purpose.
//
// - the OP has to come up with own handler logic which does
// fit the OP's needs best.
if (url !== 'https://stackoverflow.com') {
// invocation of the original `window.open`
// will be blocked by SO's permission handling.
proceed.call(thisArg, url, target, ...rest);
} else {
// delayed fowarding to `location.href`.
setTimeout(() => { window.location.href = url }, 5000);
}
};
}(window.open, window));
body { margin: 0; }
.as-console-wrapper { min-height: 87%; top: auto; }
<button data-url="https://stackoverflow.com">
no interception
</button>
<button
data-url="https://google.com"
data-target='google'
>
intercepted & open attempt
</button>
<button
id="_653cde4c-1620-11ec-85b5-5ae26c154b46"
data-url="https://stackoverflow.com"
data-target='_blank'
>
intercepted and forwarded with 5sec delay
</button>
Another approach was to make use of event delegation by listening to and handling click events at e.g. document.body level.
// untouchable 3rd party code
//
function alienLocationHandler({ currentTarget }) {
let { url, target } = currentTarget.dataset;
url = (url ?? '').trim();
if (url) {
target = (target ?? '').trim();
if (target) {
// due to SO's permission handling ...
//
// ... Blocked opening 'https://stackoverflow.com/' in a
// new window because the request was made in a sandboxed
// frame whose 'allow-popups' permission is not set.
//
window.open(url, target);
} else {
window.location.href = url;
}
}
}
document
.querySelectorAll('[data-url]')
.forEach(elmNode =>
elmNode.addEventListener('click', alienLocationHandler)
);
// another possible approach :: event delegation
//
// - [https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#event_delegation]
// - [https://davidwalsh.name/event-delegate]
//
// - [https://javascript.info/event-delegation]
// - [https://learn.jquery.com/events/event-delegation/]
//
function isButtonEvent(activeElement, targetNode) {
return (activeElement.tagName.toLowerCase() === 'button') && (
activeElement.isSameNode(targetNode) ||
activeElement.contains(targetNode)
);
}
function preventSpecificButtonClickBehavior(evt) {
const elmButton = document.activeElement;
if (isButtonEvent(elmButton, evt.target)) {
const url = (elmButton.dataset.url ?? '').trim();
const isUrlBasedPrevented = (url !== 'https://stackoverflow.com');
const isIdBasedPrevented = ((elmButton.id ?? '').trim() !== '');
if (isUrlBasedPrevented || isIdBasedPrevented) {
evt.stopImmediatePropagation();
evt.stopPropagation();
if (isUrlBasedPrevented) {
console.log('prevented button click behavior ... URL based')
}
if (isIdBasedPrevented) {
console.log('prevented button click behavior ... ID based')
}
}
}
}
document
.body
.addEventListener(
'click',
preventSpecificButtonClickBehavior,
// - [https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#parameters]
// - The boolean `useCapture` parameter/flag does the trick if set as `true` value.
true
);
body { margin: 0; }
.as-console-wrapper { min-height: 87%; top: auto; }
<button data-url="https://stackoverflow.com">
click behavior not prevented
</button>
<button
id="_653cde4c-1620-11ec-85b5-5ae26c154b46"
data-url="https://stackoverflow.com"
>
prevented behavior (id based)
</button>
<button
data-url="https://google.com"
data-target='google'
>
<span>
<span>
prevented behavior (url based)
</span>
</span>
</button>

Probably the logic in the onClick() does something like window.open(url, [params]). You can change this to setting window.location.href = url; and the url will be loaded in the same window.

Related

How to have all external links on my website raise a confirm

I am trying to have all external links on my website raise a confirm saying "You are being redirected to an external site." I know how to write the JS to check for if a link is external and how to raise the confirm, but is there a way I can apply this to every link in my site without going through individually? The links will always have the format <a href=URL> Link </a>. An angular script would check if the URL subdomain is the same as my site, and if not it will add onclick=return confirm('You will now be redirected to an exteral site') and target="_blank" to the link HTML.
As you already said, this can be achieved by using confirm onclick, you can easily add an EventListener to all a Elements that are external (l.hostname !== location.hostname) in your page and only redirect after the user accepts the message, just like so:
Array.from(document.querySelectorAll("a")).filter(l => l.hostname !== location.hostname).forEach(el => el.addEventListener("click", evtL));
function evtL(e) {
if (confirm("Are you sure you want to leave the page?")) {
return; //redirect
} else {
e.preventDefault();
return false; //don't redirect
}
}
internal
external
also internal
It can be achieved by the regex matching.
Instead of adding multiple event listeners, single event listener can be added on the document object and detect the external links as follows. It will give more efficiency.
For reference, Handling events for multiple elements in a single listener
var regexp = /https?:\/\/(www.){0,1}((?:[\w\d-]+\.)+[\w\d]{2,})/i;
function isExternal(url)
{
return regexp.exec(location.href)[2] !== regexp.exec(url)[2];
}
document.addEventListener("click",function(){
if(event.target.tagName.toLowerCase()==="a")
{
if(isExternal(event.target.href))
{
if(confirm("Redirecting to an external site...."))
{
console.log("Going to external site...");
return;
}
else
{
event.preventDefault();
return;
}
}
else
{
console.log("Internal URL Clicked.")
}
}
});
External Site 1 - https://www.google.com<br>
External Site 2 - https://www.stackoverflow.com<br>
External Site 3 - http://www.facebook.com (Using HTTP)<br>
Internal Site (Using HTTP) - http://www.stacksnippets.net<br>
Internal Site (without www.) - http://stacksnippets.net<br>
Internal reference - /path/to/something (Internal reference)<br>
Thanks #pseudosavant for the regex.
Though #Luca's answer works, the hostname is not supported in IE, Safari and Opera. Browser Compatibility for reference.
Since you are using Angular.js, I would recommend taking a look at how they work with <a> since they already have a directive applied to all <a> to make ngHref work. The source would be here: <a> directive.
The basic idea is to put the logic that you use to change the href to the warning or display a modal or whatever in the element.on('click', function(event) {...}).
Because Angular.js already defined an <a> directive, you may need to fiddle with the priority of your directive so that you don't accidentally break the way Angular fiddles with <a>.
The directive would look something like this:
// Logic that dictactes whether the URL should show a warning
function isSuspectUrl(l) {
return false;
}
const app = angular
.module("aWarn", [])
.directive("a", function() {
return {
restrict: "E",
compile: function(el, attr) {
element.on("click", function(e) {
if (isSuspectUrl(attr.href)) {
// Logic that would display a warning
}
});
}
}
});
2021 version
es2015+
Works with SPA
Handles local links that look external
Subdomains support
const baseUrl = window.location.origin;
const absoluteUrlRegex = new RegExp('^(?:[a-z]+:)?//', 'i');
const isAbsoluteUrl = (url) => absoluteUrlRegex.test(url);
const isLocalUrl = (url) => url.startsWith(baseUrl) || !isAbsoluteUrl(url);
// https://gist.github.com/ -> github.com
// https://stackoverflow.com/ -> stackoverflow.com
const getDomain = (url) => {
const urlInstance = new URL(url);
const dividedUrl = urlInstance.hostname.split('.');
const urlDomainsCount = dividedUrl.length;
return urlDomainsCount === 2
? urlInstance.hostname
: `${dividedUrl[urlDomainsCount - 2]}.${dividedUrl[urlDomainsCount - 1]}`;
};
// example whitelist
const whitelist = [
'twitter.com',
'github.com',
];
// url has same domain or whitelisted
const isWhitelistedUrl = (url) => {
const domain = getDomain(url);
return domain === window.location.hostname || whitelist.includes(domain);
};
// bind listener
const confirmExternalLinks = (confirmationFn) => {
document.addEventListener('click', async (e) => {
const linkElement = e.target.closest('a');
if (!linkElement) return;
const link = linkElement.getAttribute('href');
if (isLocalUrl(link) || isWhitelistedUrl(link)) return;
e.preventDefault();
const confirmation = await confirmationFn(link);
if (confirmation) window.open(link);
});
};
// tip: replace confirmationFn with your custom handler which returns Promise
confirmExternalLinks((link) => confirm(`Proceed to ${link}?`));
<a target="_blank" href="https://stacksnippets.net/">https://stacksnippets.net/</a> is local for this snippet iframe
<br>
<a target="_blank" href="https://twitter.com/">https://twitter.com/</a> is whitelisted
<br>
<a target="_blank" href="https://developer.mozilla.org/">https://developer.mozilla.org/</a> is external

How to Detect Browser Back Button event - Cross Browser

How do you definitively detect whether or not the user has pressed the back button in the browser?
How do you enforce the use of an in-page back button inside a single page web application using a #URL system?
Why on earth don't browser back buttons fire their own events!?
(Note: As per Sharky's feedback, I've included code to detect backspaces)
So, I've seen these questions frequently on SO, and have recently run into the issue of controlling back button functionality myself. After a few days of searching for the best solution for my application (Single-Page with Hash Navigation), I've come up with a simple, cross-browser, library-less system for detecting the back button.
Most people recommend using:
window.onhashchange = function() {
//blah blah blah
}
However, this function will also be called when a user uses on in-page element that changes the location hash. Not the best user experience when your user clicks and the page goes backwards or forwards.
To give you a general outline of my system, I'm filling up an array with previous hashes as my user moves through the interface. It looks something like this:
function updateHistory(curr) {
window.location.lasthash.push(window.location.hash);
window.location.hash = curr;
}
Pretty straight forward. I do this to ensure cross-browser support, as well as support for older browsers. Simply pass the new hash to the function, and it'll store it for you and then change the hash (which is then put into the browser's history).
I also utilise an in-page back button that moves the user between pages using the lasthash array. It looks like this:
function goBack() {
window.location.hash = window.location.lasthash[window.location.lasthash.length-1];
//blah blah blah
window.location.lasthash.pop();
}
So this will move the user back to the last hash, and remove that last hash from the array (I have no forward button right now).
So. How do I detect whether or not a user has used my in-page back button, or the browser button?
At first I looked at window.onbeforeunload, but to no avail - that is only called if the user is going to change pages. This does not happen in a single-page-application using hash navigation.
So, after some more digging, I saw recommendations for trying to set a flag variable. The issue with this in my case, is that I would try to set it, but as everything is asynchronous, it wouldn't always be set in time for the if statement in the hash change. .onMouseDown wasn't always called in click, and adding it to an onclick wouldn't ever trigger it fast enough.
This is when I started to look at the difference between document, and window. My final solution was to set the flag using document.onmouseover, and disable it using document.onmouseleave.
What happens is that while the user's mouse is inside the document area (read: the rendered page, but excluding the browser frame), my boolean is set to true. As soon as the mouse leaves the document area, the boolean flips to false.
This way, I can change my window.onhashchange to:
window.onhashchange = function() {
if (window.innerDocClick) {
window.innerDocClick = false;
} else {
if (window.location.hash != '#undefined') {
goBack();
} else {
history.pushState("", document.title, window.location.pathname);
location.reload();
}
}
}
You'll note the check for #undefined. This is because if there is no history available in my array, it returns undefined. I use this to ask the user if they want to leave using a window.onbeforeunload event.
So, in short, and for people that aren't necessarily using an in-page back button or an array to store the history:
document.onmouseover = function() {
//User's mouse is inside the page.
window.innerDocClick = true;
}
document.onmouseleave = function() {
//User's mouse has left the page.
window.innerDocClick = false;
}
window.onhashchange = function() {
if (window.innerDocClick) {
//Your own in-page mechanism triggered the hash change
} else {
//Browser back button was clicked
}
}
And there you have it. a simple, three-part way to detect back button usage vs in-page elements with regards to hash navigation.
EDIT:
To ensure that the user doesn't use backspace to trigger the back event, you can also include the following (Thanks to #thetoolman on this Question):
$(function(){
/*
* this swallows backspace keys on any non-input element.
* stops backspace -> back
*/
var rx = /INPUT|SELECT|TEXTAREA/i;
$(document).bind("keydown keypress", function(e){
if( e.which == 8 ){ // 8 == backspace
if(!rx.test(e.target.tagName) || e.target.disabled || e.target.readOnly ){
e.preventDefault();
}
}
});
});
You can try popstate event handler, e.g:
window.addEventListener('popstate', function(event) {
// The popstate event is fired each time when the current history entry changes.
var r = confirm("You pressed a Back button! Are you sure?!");
if (r == true) {
// Call Back button programmatically as per user confirmation.
history.back();
// Uncomment below line to redirect to the previous page instead.
// window.location = document.referrer // Note: IE11 is not supporting this.
} else {
// Stay on the current page.
history.pushState(null, null, window.location.pathname);
}
history.pushState(null, null, window.location.pathname);
}, false);
Note: For the best results, you should load this code only on specific pages where you want to implement the logic to avoid any other unexpected issues.
The popstate event is fired each time when the current history entry changes (user navigates to a new state). That happens when user clicks on browser's Back/Forward buttons or when history.back(), history.forward(), history.go() methods are programatically called.
The event.state is property of the event is equal to the history state object.
For jQuery syntax, wrap it around (to add even listener after document is ready):
(function($) {
// Above code here.
})(jQuery);
See also: window.onpopstate on page load
See also the examples on Single-Page Apps and HTML5 pushState page:
<script>
// jQuery
$(window).on('popstate', function (e) {
var state = e.originalEvent.state;
if (state !== null) {
//load content with ajax
}
});
// Vanilla javascript
window.addEventListener('popstate', function (e) {
var state = e.state;
if (state !== null) {
//load content with ajax
}
});
</script>
This should be compatible with Chrome 5+, Firefox 4+, IE 10+, Safari 6+, Opera 11.5+ and similar.
if (window.performance && window.performance.navigation.type == window.performance.navigation.TYPE_BACK_FORWARD) {
alert('hello world');
}
This is the only one solution that worked for me (it's not a onepage website).
It's working with Chrome, Firefox and Safari.
I had been struggling with this requirement for quite a while and took some of the solutions above to implement it. However, I stumbled upon an observation and it seems to work across Chrome, Firefox and Safari browsers + Android and iPhone
On page load:
window.history.pushState({page: 1}, "", "");
window.onpopstate = function(event) {
// "event" object seems to contain value only when the back button is clicked
// and if the pop state event fires due to clicks on a button
// or a link it comes up as "undefined"
if(event){
// Code to handle back button or prevent from navigation
}
else{
// Continue user action through link or button
}
}
Let me know if this helps. If am missing something, I will be happy to understand.
In javascript, navigation type 2 means browser's back or forward button clicked and the browser is actually taking content from cache.
if(performance.navigation.type == 2)
{
//Do your code here
}
Correct answer is already there to answer the question. I want to mention new JavaScript API PerformanceNavigationTiming, it's replacing deprecated performance.navigation.
Following code will log in console "back_forward" if user landed on your page using back or forward button. Take a look at compatibility table before using it in your project.
var perfEntries = performance.getEntriesByType("navigation");
for (var i = 0; i < perfEntries.length; i++) {
console.log(perfEntries[i].type);
}
This will definitely work (For detecting back button click)
$(window).on('popstate', function(event) {
alert("pop");
});
My variant:
const inFromBack = performance && performance.getEntriesByType( 'navigation' ).map( nav => nav.type ).includes( 'back_forward' )
Browser: https://jsfiddle.net/Limitlessisa/axt1Lqoz/
For mobile control: https://jsfiddle.net/Limitlessisa/axt1Lqoz/show/
$(document).ready(function() {
$('body').on('click touch', '#share', function(e) {
$('.share').fadeIn();
});
});
// geri butonunu yakalama
window.onhashchange = function(e) {
var oldURL = e.oldURL.split('#')[1];
var newURL = e.newURL.split('#')[1];
if (oldURL == 'share') {
$('.share').fadeOut();
e.preventDefault();
return false;
}
//console.log('old:'+oldURL+' new:'+newURL);
}
.share{position:fixed; display:none; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,.8); color:white; padding:20px;
<!DOCTYPE html>
<html>
<head>
<title>Back Button Example</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
</head>
<body style="text-align:center; padding:0;">
Share
<div class="share" style="">
<h1>Test Page</h1>
<p> Back button press please for control.</p>
</div>
</body>
</html>
See this:
history.pushState(null, null, location.href);
window.onpopstate = function () {
history.go(1);
};
it works fine...
I was able to use some of the answers in this thread and others to get it working in IE and Chrome/Edge. history.pushState for me wasn't supported in IE11.
if (history.pushState) {
//Chrome and modern browsers
history.pushState(null, document.title, location.href);
window.addEventListener('popstate', function (event) {
history.pushState(null, document.title, location.href);
});
}
else {
//IE
history.forward();
}
A full-fledged component can be implemented only if you redefine the API (change the methods of object ' history ')
I will share the class just written.
Tested on Chrome and Mozilla
Support only HTML5 and ECMAScript5-6
class HistoryNavigation {
static init()
{
if(HistoryNavigation.is_init===true){
return;
}
HistoryNavigation.is_init=true;
let history_stack=[];
let n=0;
let current_state={timestamp:Date.now()+n};
n++;
let init_HNState;
if(history.state!==null){
current_state=history.state.HNState;
history_stack=history.state.HNState.history_stack;
init_HNState=history.state.HNState;
} else {
init_HNState={timestamp:current_state.timestamp,history_stack};
}
let listenerPushState=function(params){
params=Object.assign({state:null},params);
params.state=params.state!==null?Object.assign({},params.state):{};
let h_state={ timestamp:Date.now()+n};
n++;
let key = history_stack.indexOf(current_state.timestamp);
key=key+1;
history_stack.splice(key);
history_stack.push(h_state.timestamp);
h_state.history_stack=history_stack;
params.state.HNState=h_state;
current_state=h_state;
return params;
};
let listenerReplaceState=function(params){
params=Object.assign({state:null},params);
params.state=params.state!==null?Object.assign({},params.state):null;
let h_state=Object.assign({},current_state);
h_state.history_stack=history_stack;
params.state.HNState=h_state;
return params;
};
let desc=Object.getOwnPropertyDescriptors(History.prototype);
delete desc.constructor;
Object.defineProperties(History.prototype,{
replaceState:Object.assign({},desc.replaceState,{
value:function(state,title,url){
let params={state,title,url};
HistoryNavigation.dispatchEvent('history.state.replace',params);
params=Object.assign({state,title,url},params);
params=listenerReplaceState(params);
desc.replaceState.value.call(this,params.state,params.title,params.url);
}
}),
pushState:Object.assign({},desc.pushState,{
value:function(state,title,url){
let params={state,title,url};
HistoryNavigation.dispatchEvent('history.state.push',params);
params=Object.assign({state,title,url},params);
params=listenerPushState(params);
return desc.pushState.value.call(this, params.state, params.title, params.url);
}
})
});
HistoryNavigation.addEventListener('popstate',function(event){
let HNState;
if(event.state==null){
HNState=init_HNState;
} else {
HNState=event.state.HNState;
}
let key_prev=history_stack.indexOf(current_state.timestamp);
let key_state=history_stack.indexOf(HNState.timestamp);
let delta=key_state-key_prev;
let params={delta,event,state:Object.assign({},event.state)};
delete params.state.HNState;
HNState.history_stack=history_stack;
if(event.state!==null){
event.state.HNState=HNState;
}
current_state=HNState;
HistoryNavigation.dispatchEvent('history.go',params);
});
}
static addEventListener(...arg)
{
window.addEventListener(...arg);
}
static removeEventListener(...arg)
{
window.removeEventListener(...arg);
}
static dispatchEvent(event,params)
{
if(!(event instanceof Event)){
event=new Event(event,{cancelable:true});
}
event.params=params;
window.dispatchEvent(event);
};
}
HistoryNavigation.init();
// exemple
HistoryNavigation.addEventListener('popstate',function(event){
console.log('Will not start because they blocked the work');
});
HistoryNavigation.addEventListener('history.go',function(event){
event.params.event.stopImmediatePropagation();// blocked popstate listeners
console.log(event.params);
// back or forward - see event.params.delta
});
HistoryNavigation.addEventListener('history.state.push',function(event){
console.log(event);
});
HistoryNavigation.addEventListener('history.state.replace',function(event){
console.log(event);
});
history.pushState({h:'hello'},'','');
history.pushState({h:'hello2'},'','');
history.pushState({h:'hello3'},'','');
history.back();
```
Here's my take at it. The assumption is, when the URL changes but there has no click within the document detected, it's a browser back (yes, or forward). A users click is reset after 2 seconds to make this work on pages that load content via Ajax:
(function(window, $) {
var anyClick, consoleLog, debug, delay;
delay = function(sec, func) {
return setTimeout(func, sec * 1000);
};
debug = true;
anyClick = false;
consoleLog = function(type, message) {
if (debug) {
return console[type](message);
}
};
$(window.document).click(function() {
anyClick = true;
consoleLog("info", "clicked");
return delay(2, function() {
consoleLog("info", "reset click state");
return anyClick = false;
});
});
return window.addEventListener("popstate", function(e) {
if (anyClick !== true) {
consoleLog("info", "Back clicked");
return window.dataLayer.push({
event: 'analyticsEvent',
eventCategory: 'test',
eventAction: 'test'
});
}
});
})(window, jQuery);
The document.mouseover does not work for IE and FireFox.
However I have tried this :
$(document).ready(function () {
setInterval(function () {
var $sample = $("body");
if ($sample.is(":hover")) {
window.innerDocClick = true;
} else {
window.innerDocClick = false;
}
});
});
window.onhashchange = function () {
if (window.innerDocClick) {
//Your own in-page mechanism triggered the hash change
} else {
//Browser back or forward button was pressed
}
};
This works for Chrome and IE and not FireFox. Still working to get FireFox right. Any easy way on detecting Browser back/forward button click are welcome, not particularly in JQuery but also AngularJS or plain Javascript.
I solved it by keeping track of the original event that triggered the hashchange (be it a swipe, a click or a wheel), so that the event wouldn't be mistaken for a simple landing-on-page, and using an additional flag in each of my event bindings. The browser won't set the flag again to false when hitting the back button:
var evt = null,
canGoBackToThePast = true;
$('#next-slide').on('click touch', function(e) {
evt = e;
canGobackToThePast = false;
// your logic (remember to set the 'canGoBackToThePast' flag back to 'true' at the end of it)
}
<input style="display:none" id="__pageLoaded" value=""/>
$(document).ready(function () {
if ($("#__pageLoaded").val() != 1) {
$("#__pageLoaded").val(1);
} else {
shared.isBackLoad = true;
$("#__pageLoaded").val(1);
// Call any function that handles your back event
}
});
The above code worked for me. On mobile browsers, when the user clicked on the back button, we wanted to restore the page state as per his previous visit.
Solution for Kotlin/JS (React):
import org.w3c.dom.events.Event
import kotlin.browser.document
import kotlin.browser.window
...
override fun componentDidMount() {
window.history.pushState(null, document.title, window.location.href)
window.addEventListener("popstate", actionHandler)
}
...
val actionHandler: (Event?) -> Unit = {
window.history.pushState(
null,
document.title,
window.location.href
)
// add your actions here
}
Was looking for a solution for this issue and put together a simple skeleton test html based on a few answers here and the MDN Web Doc pages for History.pushState() and WindowEventHandlers.onpopstate.
The following HTML and JavaScript is easy enough to copy and paste and test.
Works with back and forward browser buttons, shortcut keys, adds a change to the URL (which is important in some cases).
Simply enough to add to existing code key points and should be expandable too.
<html>
<body>
<div id="p1">Option 1</div>
<div id="p2">Option 2</div>
<div id="p3">Option 3</div>
<div id="p4">Option 4</div>
<div id="c"></div>
<script>
var chg={
set:function(str){
var d=document.getElementById("c");
d.textContent=str;
},
go:function(e){
var s={"p":this.id};
chg.set(s.p);
hstry.add(s);
}
};
var hstry={
add:function(s){
var u=new URL(window.location);
u.searchParams.set("x",s.p);
window.history.pushState(s,"",u);
},
adjust:function(state){
if(state.p){
chg.set(state.p);
}
}
};
window.onpopstate=function(e){
console.log("popstate, e.state:["+ JSON.stringify(e.state) +"]");
hstry.adjust(e.state);
}
window.onload=function(){
var i,d,a=["p1","p2","p3","p4"];
for(i=0;i<a.length;i++){
d=document.getElementById(a[i]);
d.addEventListener("click",chg.go,false);
}
}
</script>
</body>
</html>
browser will emit popstate event if you navigate through your app with calling
window.history.pushState({},'','/to')
If you manually enter the addresses into the address bar and click on the back button, popstate event will NOT be fired.
If you navigate in your app with this simplified function
const navigate = (to) => {
window.history.pushState({}, ",", to);
};
then this will work
const handlePopstate = () => {
console.log("popped");
};
window.addEventListener("popstate", handlePopstate);
I tried the above options but none of them is working for me. Here is the solution
if(window.event)
{
if(window.event.clientX < 40 && window.event.clientY < 0)
{
alert("Browser back button is clicked...");
}
else
{
alert("Browser refresh button is clicked...");
}
}
Refer this link http://www.codeproject.com/Articles/696526/Solution-to-Browser-Back-Button-Click-Event-Handli for more details

stop firing popstate on hashchange

I am working with the History API and using push and pop state. I want to stop the popstate event from firing in some scenario where I am only appending the hash to URL. for example in some cases on click of anchor it appends # to the URL and popstate is immediately fired) I want to avoid all the scenarios where # or #somehasvalue is appended to the URL and stop popstate from firing. I am mainitaing the URL's with query parameters and I do not have any scenario where I need the popstate event to fire with # in the URL.
Here is my code.
if (supportsHistoryApi()) {
window.onpopstate = function (event) {
var d = event.state || {state_param1: param1, state_param2: param2};
var pathName = window.location.pathname,
params = window.location.search;
loadData(event, pathName + params, d.state_param1, d.state_param2);
}
As far as I found you cannot stop the popstate from firing unfortunately.
What you can do is check for the event.state object. That will be null on a hash change.
So I'd suggest adding a
if(event.state === null) {
event.preventDefault();
return false;
}
To your popstate event handler at the very beginning.
I think that's the best way to prevent firing of the popstate handling code on your side when the hash changes, but i'd be interested to know if there are other solutions.
I have a solution!
When popstate event calls, check the pathname if it has changed, or just the hash changed. Watch below how it works for me:
window.pop_old = document.location.pathname;
window.pop_new = '';
window.onpopstate = function (event) {
window.pop_new = document.location.pathname;
if(pop_new != pop_old){
//diferent path: not just the hash has changed
} else {
//same path: just diferent hash
}
window.pop_old = pop_new; //save for the next interaction
};
In case someone still need this:
var is_hashed = false;
$(window).on('hashchange', function() {
is_hashed = true;
});
window.addEventListener('popstate', function(e){
// if hashchange
if (is_hashed) {
e.preventDefault();
// reset
is_hashed = false;
return false;
}
// Your code to handle popstate here
..................
});
This is pretty simple.
To prevent the popstate event to fire after you click a link with hash you have to eliminate the concequence of clicking the link - which is the addition of the hash to the browser address bar.
Basically you have to create the handler for click event, check if the click is on the element you whant to prevent a hash to appear in the URL and prevent hash to appear by calling event.preventDefault(); in the handler.
Here is the code example:
/**
* This your existing `onpopstate` handler.
*/
window.onpopstate = function(e) {
// You could want to prevent defaut behaviour for `popstate` event too.
// e.preventDefault();
// Your logic here to do things.
// The following reload is just an example of what you could want to make
// in this event handler.
window.location.reload();
};
/**
* This is the `click` event handler that conditionally prevents the default
* click behaviour.
*/
document.addEventListener('click', function(e) {
// Here you check if the clicked element is that special link of yours.
if (e.target.tagName === "A" && e.target.hash.indexOf('#the-link-i-want-make-discernable') > -1) {
// The 'e.preventDefault()' is what prevent the hash to be added to
// the URL and therefore prevents your 'popstate' handler to fire.
e.preventDefault();
processMySpecialLink(e, e.target);
}
});
/**
* Just an example of the link click processor in case you want making something
* more on the link click (e.g. smooth scroll to the hash).
*/
function processMySpecialLink(e, target) {
// Make things here you want at user clicking your discernable link.
}
Here is the matching HTML markup:
<!-- Somewhere in the markup -->
<span id="the-link-i-want-make-discernable"></span>
<!-- Some markup -->
My Special Link
<!-- Another markup -->
Any other link
This all does what is described above: prevents the default behaviour for a special hash link. As a side effect it makes no popstate event to fire as no hash is added to URL for the special case of clicking the #the-link-i-want-make-discernable hash link.
I had the same problem in SPA.
I fixed this by checking if current URL and new URL are the same - technically I don't prevent popstate but I prevent fetch if hash only changed.
So, I get current URL when page loaded. It must be var to be global:
var currentURL = window.location.origin + window.location.pathname + window.location.search;
Then when history change event fired (click by link, click native browser buttons 'back' 'forward') I check if current URL and new URL are the same (hash ignored).
If URLs are the same I make return, otherwise I update current URL.
let newURL = window.location.origin + window.location.pathname + window.location.search;
if ( currentURL == newURL ) return;
currentURL = newURL;
Additionally you can to see controller code. I added it to be able stop loading when user click fast a few times 'back' or 'forward' so a few requests starter - but I need to load last one only.
Full solution of load html file when URL changed (ignore when hash only changed), with push to history, and workable 'back' and 'forward' buttons.
// get current URL when page loaded.
var currentURL = window.location.origin + window.location.pathname + window.location.search;
// function which load html.
window.xhrRequestDoc = ( currentLink, pushToHistory = true, scrollTo = 'body' ) => {
if ( pushToHistory ) {
history.pushState( null, null, currentLink );
}
// get new URL
let newURL = window.location.origin + window.location.pathname + window.location.search;
// return if hash only changed
if ( currentURL == newURL ) return;
// update URL
currentURL = newURL;
document.body.classList.add( 'main_loading', 'xhr_in_progress' );
// create controler to stop loading - used when user clicked a few times 'back' or 'forward'.
controller = new AbortController();
const signal = controller.signal;
fetch( currentLink, { signal: signal })
.then( response => response.text() )
.then( function( html ) {
// initialize the DOM parser and parse as html
let parser = new DOMParser();
let doc = parser.parseFromString( html, "text/html" );
// insert data and classes to 'body'
document.body.innerHTML = doc.querySelector( 'body' ).innerHTML;
} );
}
window.addEventListener( 'popstate', () => {
// if user clicked a few times 'back' or 'forward' - process last only
if ( document.querySelector( '.xhr_in_progress' ) ) controller.abort();
// run xhr
xhrRequestDoc( location.href, false );
})

how to call window onunload event for iFrame button click where parent page and iFrame resides in different domain

I have button which opens an iFrame (which reside in domain say 'xyz') the iFrame loads the page which resides in another domain (say 'lmn')
$("#templateSelectionFrame").get(0).contentWindow.location.href = url;
url is from another domain (I communicate in different domain with Jquery 'POSTMESSAGE')
when user clicks cancel button from iFrame i need to close the iFrame but before that it is necessary to show the page leaving message (ie. stay on page / leave page - this is alreday called on window.onbeforeunload).
var warnNavigateAway = true;
window.onbeforeunload = confirmBrowseAway;
// Called then user leave window without saving content.
function confirmBrowseAway() {
if (warnNavigateAway) {
return "If you leave this page, your work will be lost ...";
}
}
function documentSaved() {
warnNavigateAway = false;
}
function documentDirty() {
warnNavigateAway = true;
}
<div id="bottomBar">
<button class="cancelBtn" onclick="GoBack()">Cancel</button>
</div>
how do i call the same page leaving message so that i can close the iFrame when user clicks leave page message.
function GoBack()
{
var operationType = '<%: (Model.OperationType)%>';
if (operationType == "Create")
{
window.history.back();
}
else {
$.postMessage(
'closeIFrame',
parentUrl,
parent
);
}
}
(navigation: cancel button -> page leave message -> if(stay on page then 'nothing to do') if(leave page then ) -> close the iFrame)
Pure javascript
As it seems you have control of the code in both domains - despite them being on different hosts - you should be able to use the following from within the iframe:
var warnNavigateAway = true;
var confirmBrowseAway = function(){
/// if warn is enabled, then ask the user. otherwise assume "browse away"
var v = (
warnNavigateAway
?
/// use the built-in confirm dialog to notify the user, this will also
/// halt execution - meaning the page close is prevented until an
/// answer is given.
confirm('If you leave this page, your work will be lost ...')
:
true
);
if ( v ) {
/// disable the warn just in case our deleting of the
/// iframe triggers the onunload
warnNavigateAway = false;
}
return v;
}
/// i've kept the use of "onevent" handlers as in your example, but you
/// should use addEventListener and attachEvent in the same way as I have
/// for the second part of the code in the outer frame.
/// apply as a listener in case the user navigates without using the button
window.onbeforeunload = confirmBrowseAway;
/// apply the same method for the onclick of the button
document.getElementById('cancelButton').onclick = function(){
if ( confirmBrowseAway() ) {
window.parent.postMessage('close_iframe', 'http://xyz/');
}
}
and the following in the host frame:
var messageListener = function(e){
if ( e.origin !== "http://lmn/" ) {
return;
}
if ( e.data == 'close_iframe' ) {
/// however you prefer to close your iframe
document.getElementById('myIframe').style.display = 'none';
/// or removal...
document.getElementById('myIframe').parentNode.removeChild(
document.getElementById('myIframe')
);
}
};
if ( window.addEventListener ) {
window.addEventListener("message", messageListener, false);
}
else if( window.attachEvent ) {
window.attachEvent("onmessage", messageListener);
}
(The above code has been manually typed, errors may be possible, it is meant as an illustrative example)
With the above you'll need modify your markup slightly, for the iframe and for the button.
<button id="cancelButton" class="cancelBtn">Cancel</button>
and
<iframe id="myIframe" ... />
jQuery
Because I failed to spot you were using jQuery here is a jQuery version :)
iframe:
var warnNavigateAway = true;
var confirmBrowseAway = function(){
/// if warn is enabled, then ask the user. otherwise assume "browse away"
var v = (
warnNavigateAway
?
/// use the built-in confirm dialog to notify the user, this will also
/// halt execution - meaning the page close is prevented until an
/// answer is given.
confirm('If you leave this page, your work will be lost ...')
:
true
);
if ( v ) {
/// disable the warn just in case our deleting of the
/// iframe triggers the onunload
warnNavigateAway = false;
}
return v;
}
$(window).bind('beforeunload', confirmBrowseAway);
$('.cancelBtn').bind('click', function(){
if ( confirmBrowseAway() ) {
$.postMessage( 'close_iframe', 'http://xyz/' )
}
});
in the host:
$.receiveMessage(
function(e){
if ( e.data == 'close_iframe' ) {
/// however you prefer to close your iframe
$('.myIframe').hide();
/// or removal...
$('.myIframe').remove();
}
},
"http://lmn/"
);

Detecting when an iframe gets or loses focus

What's the correct way of detecting when an iframe gets or loses focus (i.e. will or will not receive keyboard events)? The following is not working in Fx4:
var iframe = /* my iframe */;
iframe.addEventListener("focus", function() { /* never gets called */ }, false);
You can poll "document.activeElement" to determine if it matches the iframe. Polling isn't ideal, but it works:
function checkFocus() {
if(document.activeElement == document.getElementsByTagName("iframe")[0]) {
console.log('iframe has focus');
} else {
console.log('iframe not focused');
}
}
window.setInterval(checkFocus, 1000);
i know it's old, but i also had the same problem.
i ended up using this little pice of code:
$(document).on('focusout', function(){
setTimeout(function(){
// using the 'setTimout' to let the event pass the run loop
if (document.activeElement instanceof HTMLIFrameElement) {
// Do your logic here..
}
},0);
});
Turns out it's not really possible. I had to change the logic of my page to avoid the need of tracking if the iframe has focus.
How to check when an iframe has been clicked in or out of as well as hover-state.
Note: I would highly recommend you don't choose a polling method and go with an event driven method such as this.
Disclaimer
It is not possible to use the focus or blur events directly on an iframe but you can use them on the window to provide an event driven method of checking the document.activeElement. Thus you can accomplish what you're after.
Although we're now in 2018, my code is being implemented in GTM and tries to be cross browser compatible back to IE 11. This means there's more efficient code if you're utilizing newer ES/ECMAScript features.
Setup
I'm going to take this a few steps further to show that we can also get the iframe's src attribute as well as determine if it's being hovered.
Code
You would ideally need to put this in a document ready event, or at least encapsulate it so that the variables aren't global [maybe use an IIFE]. I did not wrap it in a document ready because it's handled by GTM. It may also depend where you place this or how you're loading it such as in the footer.
https://jsfiddle.net/9285tbsm/9/
I have noticed in the JSFiddle preview that it's already an iframe, sometimes you have to focus it first before events start to capture. Other issues can be that your browser window isn't yet focused either.
// Helpers
var iframeClickedLast;
function eventFromIframe(event) {
var el = event.target;
return el && el.tagName && el.tagName.toLowerCase() == 'iframe';
}
function getIframeSrc(event) {
var el = event.target;
return eventFromIframe(event) ? el.getAttribute('src') : '';
}
// Events
function windowBlurred(e) {
var el = document.activeElement;
if (el.tagName.toLowerCase() == 'iframe') {
console.log('Blurred: iframe CLICKED ON', 'SRC:', el.getAttribute('src'), e);
iframeClickedLast = true;
}
else {
console.log('Blurred', e);
}
}
function windowFocussed(e) {
if (iframeClickedLast) {
var el = document.activeElement;
iframeClickedLast = false;
console.log('Focussed: iframe CLICKED OFF', 'SRC:', el.getAttribute('src'), e);
}
else {
console.log('Focussed', e);
}
}
function iframeMouseOver(e) {
console.log('Mouse Over', 'SRC:', getIframeSrc(e), e);
}
function iframeMouseOut(e) {
console.log('Mouse Out', 'SRC:', getIframeSrc(e), e);
}
// Attach Events
window.addEventListener('focus', windowFocussed, true);
window.addEventListener('blur', windowBlurred, true);
var iframes = document.getElementsByTagName("iframe");
for (var i = 0; i < iframes.length; i++) {
iframes[i].addEventListener('mouseover', iframeMouseOver, true);
iframes[i].addEventListener('mouseout', iframeMouseOut, true);
}
I have solved this by using contentWindow instead of contentDocument.
The good thing about contentWindow is
it works also in case user clicks another window (another application) or another browser tab. If using activeElement, if user clicks away from the entire window to go to another application, then that logic still think the iframe is in focus, while it is not
and we don't need to poll and do a setInterval at all. This uses the normal addEventListener
let iframe = document.getElementsByTagName("iframe")[0];
// or whatever way you do to grab that iFrame, say you have an `id`, then it's even more precise
if(iframe){
iframeWindow = iframe.contentWindow;
iframeWindow.addEventListener('focus', handleIframeFocused);
iframeWindow.addEventListener('blur', handleIframeBlurred);
}
function handleIframeFocused(){
console.log('iframe focused');
// Additional logic that you need to implement here when focused
}
function handleIframeBlurred(){
console.log('iframe blurred');
// Additional logic that you need to implement here when blurred
}
This solution is working for me on both mobile and desktop:
;(function pollForIframe() {
var myIframe = document.querySelector('#my_iframe');
if (!myIframe) return setTimeout(pollForIframe, 50);
window.addEventListener('blur', function () {
if (document.activeElement == myIframe) {
console.log('myIframe clicked!');
}
});
})();
The solution is to inject a javascript event on the parent page like this :
var script = document.createElement('script');
script.type = 'text/javascript';
script.innerHTML =
"document.addEventListener('click', function()" +
"{ if(document.getElementById('iframe')) {" +
// What you want
"}});";
head.appendChild(script);
Here is the code to Detecting when an iframe gets or loses focus
// This code can be used to verify Iframe gets focus/loses.
function CheckFocus(){
if (document.activeElement.id == $(':focus').context.activeElement.id) {
// here do something
}
else{
//do something
}
}
A compact function that accepts callbacks you want to run when iframe gets or loses focus.
/* eslint-disable no-unused-vars */
export default function watchIframeFocus(onFocus, onBlur) {
let iframeClickedLast;
function windowBlurred(e) {
const el = document.activeElement;
if (el.tagName.toLowerCase() == 'iframe') {
iframeClickedLast = true;
onFocus();
}
}
function windowFocussed(e) {
if (iframeClickedLast) {
iframeClickedLast = false;
onBlur();
}
}
window.addEventListener('focus', windowFocussed, true);
window.addEventListener('blur', windowBlurred, true);
}
This might work
document.addEventListener('click', function(event) {
var frame= document.getElementById("yourFrameID");
var isClickInsideFrame = frame.contains(event.target);
if (!isClickInsideFrame ) {
//exec code
}
});

Categories

Resources