Detecting a form.submit() performed via JavaScript - javascript

In my page there is a frame that belongs to the same domain. The content of this frame is varied and relatively unpredictable. Whenever a user clicks a button (inside the frame) that performs a post, I need to execute a function that performs some UI tasks. The problem is that I cannot edit the source of these frames for reasons beyond my control. Some of these buttons are simple form submit buttons, but others do not directly submit the form, but instead have an onclick handler that performs some checks and might submit.
Here is the problem: How do I detect if one of these onclick handlers called form.submit()? If there's no handler, then obviously I can set up a handler for onsubmit(), but is not the case for all of these buttons.
This is my code so far:
function addEventBefore(element, type, before, after) {
var old = element['on' + type] || function() {};
before = before || function() {};
after = after || function() {};
element['on' + type] = function () {
before();
old();//I can't modify this old onclick handler
after();
};
}
function setup() {
console.log('setup');
}
function takedown() {
// In this method, I want to know if old() caused a form submit
console.log('takedown');
}
function $includeFrames(jQuery, selector) {
return jQuery(selector).add(jQuery('iframe').contents().find(selector));
}
var a = $includeFrames($, 'input[type="submit"], input[type="button"]').each(function() {
var elem = $(this)[0];
addEventBefore(elem, 'click', setup, takedown);
});

In the onload event of the iframe you'll need to hook up an event listener to each form in the iframed page. You need to do this on every load, as each fresh page needs new listeners.
$("#someIframe").on('load',function() {
$(this).contents().find("form").each(function() {
$(this).on('submit',function() {... your code...})
})
}

The solution that worked for me came from a friend of mine. The solution is to shim the form.submit() function.
$(function() {
var el = document.getElementById('myform');
el.submit = function(fn) {
return function() {
myFunctionGoesHere();
fn.apply(this, arguments);
};
}(el.submit);
});
Here is a working example: http://jsfiddle.net/hW6Z4/9/

Related

escape from setInterval at end of game level

I'm writing a game using HTML5 canvas and Javascript. I'm using setInterval to animate the game and check for events at regular intervals. I've written multiple levels for the game, but I can't seem to escape the setInterval when a given level ends in order to start the next one. The code accurately detects when a level is won or lost and successfully clears the interval and renders the button, but the button does not fire.
Adding a button was my latest idea. I've also tried removing the canvas using jQuery and inserting a new one. I've also tried using clearRect on the canvas but it doesn't fire either.
Given that I can't return a value from setInterval, what are my options, if any? Is there another way to accomplish the same thing? Is there a separate error with my code that I've overlooked? Thanks!
Game.prototype.win = function(int) {
clearInterval(int);
var content = "<p style='color:white;'>You win</p><br><button id='next-level'>Next Level</button></menu>"
$('#controls').append(content)
};
Game.prototype.lose = function(int) {
clearInterval(int);
var content = "<p style='color:white;'>You Lose</p><br><button id='next-level'>Start Over?</button></menu>"
$('#controls').append(content)
};
Game.prototype.run = funtion () {
$('#start').click( function () {
$('#controls').empty();
var waveOne = new window.CrystalQuest.Wave(this.X_DIM, this.Y_DIM, this, Game.WAVE_ONE)
var game = this
var int = setInterval( function () {
if (waveOne.step() === "lost" ) {
game.lose(int);
} else if (waveOne.step() === "won") {
game.win(int);
}
waveOne.draw(this.ctx)
}, 20)
this.bindKeyHandlers(waveOne);
}.bind(this));
$('#next-level').click( function () {
$('#controls').empty();
...more code...
});
};
To stop a setInterval() you have to store the returned value from the original call to setInterval() in some persistent location and then call clearInterval() on that value.
Because you declared your interval with var as in var int, it was local only to that method and was not available anywhere else in the code.
There are a number of ways to do that in your code. I would probably suggest storing it as an instance variable like this:
Game.prototype.run = funtion () {
$('#start').click( function () {
$('#controls').empty();
var waveOne = new window.CrystalQuest.Wave(this.X_DIM, this.Y_DIM, this, Game.WAVE_ONE)
var game = this;
this.stop();
this.interval = setInterval( function () {
if (waveOne.step() === "lost" ) {
game.lose(int);
} else if (waveOne.step() === "won") {
game.win(int);
}
waveOne.draw(this.ctx)
}, 20)
this.bindKeyHandlers(waveOne);
}.bind(this));
$('#next-level').click( function () {
$('#controls').empty();
...more code...
});
};
Then, you can make a method that will stop the interval like this:
Game.prototype.stop = function() {
if (this.interval) {
clearInterval(this.interval);
this.interval = null;
}
}
And, change your other methods like this:
Game.prototype.win = function(int) {
this.stop();
var content = "<p style='color:white;'>You win</p><br><button id='next-level'>Next Level</button></menu>"
$('#controls').append(content)
};
Game.prototype.lose = function(int) {
this.stop();
var content = "<p style='color:white;'>You Lose</p><br><button id='next-level'>Start Over?</button></menu>"
$('#controls').append(content)
};
For your event handling issues, if you are destroying and recreating a button, then you will lose any event handlers that were attached to any DOM elements that got replaced.
You have two choices for how to fix it:
After you create the new DOM elements, you can again set the event handlers on the new DOM elements.
You can use "delegated event handling". This attaches the event handlers to a parent DOM object that is itself not replaced. The click event bubbles up to the parent and is handled there. The child can be replaced as many times as you want and the event handler will still work.
See these references for how to use delegated event handling with jQuery:
Does jQuery.on() work for elements that are added after the event handler is created?
jQuery .live() vs .on() method for adding a click event after loading dynamic html
JQuery Event Handlers - What's the "Best" method

Catching all audio end events from dynamic content in jquery

I have seen similar questions - but not that fix my problem!
I have audio on my page and when one ends, I want the next to start, but I can't even get the ended to trigger...
I cut the code down to this:
function DaisyChainAudio() {
$().on('ended', 'audio','' ,function () {
alert('done');
});
}
This is called from my page/code (and is executed, setting a break point shows that).
As far as I understand this should set the handler at the document level, so any 'ended' events from any 'audio' tag (even if added dynamically) should be trapped and show me that alert...
But it never fires.
edit
With some borrowing from Çağatay Gürtürk's suggestion so far have this...
function DaisyChainAudio() {
$(function () {
$('audio').on('ended', function (e) {
$(e.target).load();
var next = $(e.target).nextAll('audio');
if (!next.length) next = $(e.target).parent().nextAll().find('audio');
if (!next.length) next = $(e.target).parent().parent().nextAll().find('audio');
if (next.length) $(next[0]).trigger('play');
});
});
}
I'd still like to set this at the document level so I don't need to worry about adding it when dynamic elements are added...
The reason it does not fire is, media events( those specifically belonging to audio or video like play, pause, timeupdate, etc) do not get bubbled. you can find the explanation for that in the answer to this question.
So using their solution, I captured the ended event, and this would allow setting triggers for dynamically added audio elements.
$.createEventCapturing(['ended']); // add all the triggers for which you like to catch.
$('body').on('ended', 'audio', onEnded); // now this would work.
JSFiddle demo
the code for event capturing( taken from the other SO answer):
$.createEventCapturing = (function () {
var special = $.event.special;
return function (names) {
if (!document.addEventListener) {
return;
}
if (typeof names == 'string') {
names = [names];
}
$.each(names, function (i, name) {
var handler = function (e) {
e = $.event.fix(e);
return $.event.dispatch.call(this, e);
};
special[name] = special[name] || {};
if (special[name].setup || special[name].teardown) {
return;
}
$.extend(special[name], {
setup: function () {
this.addEventListener(name, handler, true);
},
teardown: function () {
this.removeEventListener(name, handler, true);
}
});
});
};
})();
Try this:
$('audio').on('ended', function (e) {
alert('done');
var endedTag=e.target; //this gives the ended audio, so you can find the next one and play it.
});
Note that when you create a new audio dynamically, you should assign the events. A quick and dirty solution would be:
function bindEvents(){
$('audio').off('ended').on('ended', function (e) {
alert('done');
var endedTag=e.target; //this gives the ended audio, so you can find the next one and play it.
});
}
and run bindEvents whenever you create/delete an audio element.

GetElementById of ASP.NET Control keeps returning 'null'

I'm desperate having spent well over an hour trying to troubleshoot this. I am trying to access a node in the DOM which is created from an ASP.NET control. I'm using exactly the same id and I can see that they match up when looking at the HTML source code after the page has rendered. Here's my [MODIFIED according to suggestions, but still not working] code:
ASP.NET Header
<asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent">
<script type="text/javascript">
$(document).ready(
var el = document.getElementById('<%= txtBox.ClientID %>');
el.onchange = alert('test!!');
)
</script>
</asp:Content>
ASP.NET Body
<asp:TextBox ID="txtBox" runat="server"></asp:TextBox>
Resulting Javascript & HTML from above
<script type="text/javascript">
$(document).ready(
var el = document.getElementById('MainContent_txtBox');
el.onchange = alert('test!!');
)
</script>
...
<textarea name="ctl00$MainContent$txtBox" id="MainContent_txtBox"></textarea>
I can only assume that the script is loading before the control id has been resolved, yet when I look at the timeline with Chrome's "Inspect Element" feature, it appears that is not the case. When I created a regular textarea box to test and implement the identical code (different id of course), the alert box fires.
What on earth am I missing here? This is driving me crazy >.<
EDIT: Wierd code that works, but only on the initial page load; firing onload rather than onchange. Even jQuery says that .ready doesn't work properly apparently. Ugh!!
$(document).ready(function() {
document.getElementById('<%= txtBox.ClientID %>').onchange = alert('WORKING!');
})
Assuming the rendered markup does appear in that order, the problem is that the element doesn't yet exist at the time your JavaScript is attempting to locate it.
Either move that JS below the element (preferably right at the end of the body) or wrap it in something like jQuery's document ready event handler.
Update:
In response to your edits, you're almost there but (as others have mentioned) you need to assign a function to the onchange event, not the return result of alert(). Something like this:
$(document).ready(function() {
// Might as well use jQuery to attach the event since you're already using
// it for the document ready event.
$('#<%= txtBox.ClientID %>').change(function() {
alert('Working!');
});
});
By writing onchange = alert('Working');, you were asking JavaScript to assign the result of the alert() method to the onchange property. That's why it was executing it immediately on page load, but never actually in response to the onchange event (because you hadn't assigned that a function to run onchange).
Pick up jQuery.
Then you can
$(function()
{
var el = document.getElementById('<%= txtBox.ClientID %>');
el.onclick() { alert('test!!'); }
});
Other answers have pointed out the error (attempting to access DOM nodes before they are in the document), I'll just point out alternative solutions.
Simple method
Add the script element in the HTML below the closing tag of the element you wish to access. In its easiest form, put it just before the closing body tag. This strategy can also make the page appear faster as the browser doesn't pause loading HTML for script. Overall load time is the same however, scripts still have to be loaded an executed, it's just that this order makes it seem faseter to the user.
Use window.onload or <body onload="..." ...>
This method is supported by every browser, but it fires after all content is loaded so the page may appear inactive for a short time (or perhaps a long time if loading is dealyed). It is very robust though.
Use a DOM ready function
Others have suggested jQuery, but you may not want 4,000 lines and 90kb of code just for a DOM ready function. jQuery's is quite convoluted so hard to remove from the library. David Mark's MyLibrary however is very modular and quite easy to extract just the bits you want. The code quality is also excellent, at least the equal of any other library.
Here is an example of a DOM ready function extracted from MyLibrary:
var API = API || {};
(function(global) {
var doc = (typeof global.document == 'object')? global.document : null;
var attachDocumentReadyListener, bReady, documentReady,
documentReadyListener, readyListeners = [];
var canAddDocumentReadyListener, canAddWindowLoadListener,
canAttachWindowLoadListener;
if (doc) {
canAddDocumentReadyListener = !!doc.addEventListener;
canAddWindowLoadListener = !!global.addEventListener;
canAttachWindowLoadListener = !!global.attachEvent;
bReady = false;
documentReady = function() { return bReady; };
documentReadyListener = function(e) {
if (!bReady) {
bReady = true;
var i = readyListeners.length;
var m = i - 1;
// NOTE: e may be undefined (not always called by event handler)
while (i--) { readyListeners[m - i](e); }
}
};
attachDocumentReadyListener = function(fn, docNode) {
docNode = docNode || global.document;
if (docNode == global.document) {
if (!readyListeners.length) {
if (canAddDocumentReadyListener) {
docNode.addEventListener('DOMContentLoaded',
documentReadyListener, false);
}
if (canAddWindowLoadListener) {
global.addEventListener('load', documentReadyListener, false);
}
else if (canAttachWindowLoadListener) {
global.attachEvent('onload', documentReadyListener);
} else {
var oldOnLoad = global.onload;
global.onload = function(e) {
if (oldOnLoad) {
oldOnLoad(e);
}
documentReadyListener();
};
}
}
readyListeners[readyListeners.length] = fn;
return true;
}
// NOTE: no special handling for other documents
// It might be useful to add additional queues for frames/objects
else {
if (canAddDocumentReadyListener) {
docNode.addEventListener('DOMContentLoaded', fn, false);
return true;
}
return false;
}
};
API.documentReady = documentReady;
API.documentReadyListener = documentReadyListener;
API.attachDocumentReadyListener = attachDocumentReadyListener;
}
}(this));
Using it for your case:
function someFn() {
var el = document.getElementById('MainContent_txtBox');
el.onclick = function() { alert('test!!');
}
API.attachDocumentReadyListener(someFn);
or an anonymous function can be supplied:
API.attachDocumentReadyListener(function(){
var el = document.getElementById('MainContent_txtBox');
el.onclick = function() { alert('test!!');
};
Very simple DOM ready functions can be done in 10 lines of code if you just want one for a specific case, but of course they are less robust and not as reusable.

How to detect if URL has changed after hash in JavaScript

How can I check if a URL has changed in JavaScript? For example, websites like GitHub, which use AJAX, will append page information after a # symbol to create a unique URL without reloading the page. What is the best way to detect if this URL changes?
Is the onload event called again?
Is there an event handler for the URL?
Or must the URL be checked every second to detect a change?
I wanted to be able to add locationchange event listeners. After the modification below, we'll be able to do it, like this
window.addEventListener('locationchange', function () {
console.log('location changed!');
});
In contrast, window.addEventListener('hashchange',() => {}) would only fire if the part after a hashtag in a url changes, and window.addEventListener('popstate',() => {}) doesn't always work.
This modification, similar to Christian's answer, modifies the history object to add some functionality.
By default, before these modifications, there's a popstate event, but there are no events for pushstate, and replacestate.
This modifies these three functions so that all fire a custom locationchange event for you to use, and also pushstate and replacestate events if you want to use those.
These are the modifications:
(() => {
let oldPushState = history.pushState;
history.pushState = function pushState() {
let ret = oldPushState.apply(this, arguments);
window.dispatchEvent(new Event('pushstate'));
window.dispatchEvent(new Event('locationchange'));
return ret;
};
let oldReplaceState = history.replaceState;
history.replaceState = function replaceState() {
let ret = oldReplaceState.apply(this, arguments);
window.dispatchEvent(new Event('replacestate'));
window.dispatchEvent(new Event('locationchange'));
return ret;
};
window.addEventListener('popstate', () => {
window.dispatchEvent(new Event('locationchange'));
});
})();
Note, we're creating a closure, to save the old function as part of the new one, so that it gets called whenever the new one is called.
In modern browsers (IE8+, FF3.6+, Chrome), you can just listen to the hashchange event on window.
In some old browsers, you need a timer that continually checks location.hash. If you're using jQuery, there is a plugin that does exactly that.
Example
Below I undo any URL change, to keep just the scrolling:
<script type="text/javascript">
if (window.history) {
var myOldUrl = window.location.href;
window.addEventListener('hashchange', function(){
window.history.pushState({}, null, myOldUrl);
});
}
</script>
Note that above used history-API is available in Chrome, Safari, Firefox 4+, and Internet Explorer 10pp4+
window.onhashchange = function() {
//code
}
window.onpopstate = function() {
//code
}
or
window.addEventListener('hashchange', function() {
//code
});
window.addEventListener('popstate', function() {
//code
});
with jQuery
$(window).bind('hashchange', function() {
//code
});
$(window).bind('popstate', function() {
//code
});
EDIT after a bit of researching:
It somehow seems that I have been fooled by the documentation present on Mozilla docs. The popstate event (and its callback function onpopstate) are not triggered whenever the pushState() or replaceState() are called in code. Therefore the original answer does not apply in all cases.
However there is a way to circumvent this by monkey-patching the functions according to #alpha123:
var pushState = history.pushState;
history.pushState = function () {
pushState.apply(history, arguments);
fireEvents('pushState', arguments); // Some event-handling function
};
Original answer
Given that the title of this question is "How to detect URL change" the answer, when you want to know when the full path changes (and not just the hash anchor), is that you can listen for the popstate event:
window.onpopstate = function(event) {
console.log("location: " + document.location + ", state: " + JSON.stringify(event.state));
};
Reference for popstate in Mozilla Docs
Currently (Jan 2017) there is support for popstate from 92% of browsers worldwide.
With jquery (and a plug-in) you can do
$(window).bind('hashchange', function() {
/* things */
});
http://benalman.com/projects/jquery-hashchange-plugin/
Otherwise yes, you would have to use setInterval and check for a change in the hash event (window.location.hash)
Update! A simple draft
function hashHandler(){
this.oldHash = window.location.hash;
this.Check;
var that = this;
var detect = function(){
if(that.oldHash!=window.location.hash){
alert("HASH CHANGED - new has" + window.location.hash);
that.oldHash = window.location.hash;
}
};
this.Check = setInterval(function(){ detect() }, 100);
}
var hashDetection = new hashHandler();
Add a hash change event listener!
window.addEventListener('hashchange', function(e){console.log('hash changed')});
Or, to listen to all URL changes:
window.addEventListener('popstate', function(e){console.log('url changed')});
This is better than something like the code below because only one thing can exist in window.onhashchange and you'll possibly be overwriting someone else's code.
// Bad code example
window.onhashchange = function() {
// Code that overwrites whatever was previously in window.onhashchange
}
this solution worked for me:
function checkURLchange(){
if(window.location.href != oldURL){
alert("url changed!");
oldURL = window.location.href;
}
}
var oldURL = window.location.href;
setInterval(checkURLchange, 1000);
None of these seem to work when a link is clicked that which redirects you to a different page on the same domain. Hence, I made my own solution:
let pathname = location.pathname;
window.addEventListener("click", function() {
if (location.pathname != pathname) {
pathname = location.pathname;
// code
}
});
Edit: You can also check for the popstate event (if a user goes back a page)
window.addEventListener("popstate", function() {
// code
});
Best wishes,
Calculus
If none of the window events are working for you (as they aren't in my case), you can also use a MutationObserver that looks at the root element (non-recursively).
// capture the location at page load
let currentLocation = document.location.href;
const observer = new MutationObserver((mutationList) => {
if (currentLocation !== document.location.href) {
// location changed!
currentLocation = document.location.href;
// (do your event logic here)
}
});
observer.observe(
document.getElementById('root'),
{
childList: true,
// important for performance
subtree: false
});
This may not always be feasible, but typically, if the URL changes, the root element's contents change as well.
I have not profiled, but theoretically this has less overhead than a timer because the Observer pattern is typically implemented so that it just loops through the subscriptions when a change occurs. We only added one subscription here. The timer on the other hand would have to check very frequently in order to ensure that the event was triggered immediately after URL change.
Also, this has a good chance of being more reliable than a timer since it eliminates timing issues.
Although an old question, the Location-bar project is very useful.
var LocationBar = require("location-bar");
var locationBar = new LocationBar();
// listen to all changes to the location bar
locationBar.onChange(function (path) {
console.log("the current url is", path);
});
// listen to a specific change to location bar
// e.g. Backbone builds on top of this method to implement
// it's simple parametrized Backbone.Router
locationBar.route(/some\-regex/, function () {
// only called when the current url matches the regex
});
locationBar.start({
pushState: true
});
// update the address bar and add a new entry in browsers history
locationBar.update("/some/url?param=123");
// update the address bar but don't add the entry in history
locationBar.update("/some/url", {replace: true});
// update the address bar and call the `change` callback
locationBar.update("/some/url", {trigger: true});
To listen to url changes, see below:
window.onpopstate = function(event) {
console.log("location: " + document.location + ", state: " + JSON.stringify(event.state));
};
Use this style if you intend to stop/remove listener after some certain condition.
window.addEventListener('popstate', function(e) {
console.log('url changed')
});
The answer below comes from here(with old javascript syntax(no arrow function, support IE 10+)):
https://stackoverflow.com/a/52809105/9168962
(function() {
if (typeof window.CustomEvent === "function") return false; // If not IE
function CustomEvent(event, params) {
params = params || {bubbles: false, cancelable: false, detail: null};
var evt = document.createEvent("CustomEvent");
evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
return evt;
}
window.CustomEvent = CustomEvent;
})();
(function() {
history.pushState = function (f) {
return function pushState() {
var ret = f.apply(this, arguments);
window.dispatchEvent(new CustomEvent("pushState"));
window.dispatchEvent(new CustomEvent("locationchange"));
return ret;
};
}(history.pushState);
history.replaceState = function (f) {
return function replaceState() {
var ret = f.apply(this, arguments);
window.dispatchEvent(new CustomEvent("replaceState"));
window.dispatchEvent(new CustomEvent("locationchange"));
return ret;
};
}(history.replaceState);
window.addEventListener("popstate", function() {
window.dispatchEvent(new CustomEvent("locationchange"));
});
})();
While doing a little chrome extension, I faced the same problem with an additionnal problem : Sometimes, the page change but not the URL.
For instance, just go to the Facebook Homepage, and click on the 'Home' button. You will reload the page but the URL won't change (one-page app style).
99% of the time, we are developping websites so we can get those events from Frameworks like Angular, React, Vue etc..
BUT, in my case of a Chrome extension (in Vanilla JS), I had to listen to an event that will trigger for each "page change", which can generally be caught by URL changed, but sometimes it doesn't.
My homemade solution was the following :
listen(window.history.length);
var oldLength = -1;
function listen(currentLength) {
if (currentLength != oldLength) {
// Do your stuff here
}
oldLength = window.history.length;
setTimeout(function () {
listen(window.history.length);
}, 1000);
}
So basically the leoneckert solution, applied to window history, which will change when a page changes in a single page app.
Not rocket science, but cleanest solution I found, considering we are only checking an integer equality here, and not bigger objects or the whole DOM.
Found a working answer in a separate thread:
There's no one event that will always work, and monkey patching the pushState event is pretty hit or miss for most major SPAs.
So smart polling is what's worked best for me. You can add as many event types as you like, but these seem to be doing a really good job for me.
Written for TS, but easily modifiable:
const locationChangeEventType = "MY_APP-location-change";
// called on creation and every url change
export function observeUrlChanges(cb: (loc: Location) => any) {
assertLocationChangeObserver();
window.addEventListener(locationChangeEventType, () => cb(window.location));
cb(window.location);
}
function assertLocationChangeObserver() {
const state = window as any as { MY_APP_locationWatchSetup: any };
if (state.MY_APP_locationWatchSetup) { return; }
state.MY_APP_locationWatchSetup = true;
let lastHref = location.href;
["popstate", "click", "keydown", "keyup", "touchstart", "touchend"].forEach((eventType) => {
window.addEventListener(eventType, () => {
requestAnimationFrame(() => {
const currentHref = location.href;
if (currentHref !== lastHref) {
lastHref = currentHref;
window.dispatchEvent(new Event(locationChangeEventType));
}
})
})
});
}
Usage
observeUrlChanges((loc) => {
console.log(loc.href)
})
I created this event that is very similar to the hashchange event
// onurlchange-event.js v1.0.1
(() => {
const hasNativeEvent = Object.keys(window).includes('onurlchange')
if (!hasNativeEvent) {
let oldURL = location.href
setInterval(() => {
const newURL = location.href
if (oldURL === newURL) {
return
}
const urlChangeEvent = new CustomEvent('urlchange', {
detail: {
oldURL,
newURL
}
})
oldURL = newURL
dispatchEvent(urlChangeEvent)
}, 25)
addEventListener('urlchange', event => {
if (typeof(onurlchange) === 'function') {
onurlchange(event)
}
})
}
})()
Example of use:
window.onurlchange = event => {
console.log(event)
console.log(event.detail.oldURL)
console.log(event.detail.newURL)
}
addEventListener('urlchange', event => {
console.log(event)
console.log(event.detail.oldURL)
console.log(event.detail.newURL)
})
for Chrome 102+ (2022-05-24)
navigation.addEventListener("navigate", e => {
console.log(`navigate ->`,e.destination.url)
});
API references WICG/navigation-api
Look at the jQuery unload function. It handles all the things.
https://api.jquery.com/unload/
The unload event is sent to the window element when the user navigates away from the page. This could mean one of many things. The user could have clicked on a link to leave the page, or typed in a new URL in the address bar. The forward and back buttons will trigger the event. Closing the browser window will cause the event to be triggered. Even a page reload will first create an unload event.
$(window).unload(
function(event) {
alert("navigating");
}
);
window.addEventListener("beforeunload", function (e) {
// do something
}, false);
You are starting a new setInterval at each call, without cancelling the previous one - probably you only meant to have a setTimeout
Enjoy!
var previousUrl = '';
var observer = new MutationObserver(function(mutations) {
if (location.href !== previousUrl) {
previousUrl = location.href;
console.log(`URL changed to ${location.href}`);
}
});
Another simple way you can do this is by adding a click event, through a class name to the anchor tags on the page to detect when it has been clicked, then you can now use the window.location.href to get the url data which you can use to run your ajax request to the server. Simple and Easy.

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