JavaScript - Detect whether a click event triggered a DOMNodeInserted event - javascript

I'm working on a chrome extension business gamification system that tracks users across various web applications, and tracks their progress within the applications to determine a user ranking in their workplace.
I know you can track which elements are created, using something like:
window.addEventListener('DOMNodeInserted', function(e){
console.log(e.target);
}, false);
But as this can be quite slow (noticeable lag within complex web applications), I would like to only detect elements that are created as a result of mouse clicks (on an element, for example).
I just tested this, locally:
<!DOCTYPE html>
<html>
<body>
<div id="creator">Click me</div>
<script>
var click_event = 0;
function add_element(e)
{
console.log(e);
click_event = e;
var div = document.createElement('div');
div.innerHTML = "created";
document.body.appendChild(div);
}
function element_created(e)
{
console.log(e);
if(e == click_event)
{
console.log("SAME EVENT");
}
}
document.getElementById('creator').addEventListener('click', add_element, false);
window.addEventListener('DOMNodeInserted', element_created, false);
</script>
</body>
</html>
in the hope that the events would be the same, or that upon inspecting the DOMNodeInserted event, I'd find some way of extracting the original click event from the insertion event's properties. I found nothing within the properties of the insertion event that linked it to the click event, though.
Is there any way of finding which event triggered a DOMNodeInserted event using JavaScript?

Use custom event when you are inserting elements from javascript, but only based on event "DOMNodeInserted", it impossible, still you can use a custom attribute on element <div insertedwith="click"> and check on "DOMNodeInserted" handler if e.target has that attribute. Anyway check the example, hope will help you.
console.clear();
function add_element_working(e)
{
var div = document.createElement('div');
div.onload = function() {
console.log("Script loaded and ready");
};
div.innerHTML = "This is tracked with custom event";
event = document.createEvent("HTMLEvents");
event.initEvent("InsertedFromClick", true, true);
document.body.appendChild(div);
div.dispatchEvent(event);
}
function add_element_notworking(e)
{
var div = document.createElement('div');
div.innerHTML = "This is not tracked";
document.body.appendChild(div);
}
function element_created(e)
{
console.log("Inserted element not knowing who trigered");
console.log(e);
}
function element_created_custom(e)
{
console.log("Click inserted element traked");
console.log(e);
}
document.getElementById('creator').addEventListener('click', add_element_working, false);
document.getElementById('creator2').addEventListener('click', add_element_notworking, false);
window.addEventListener('DOMNodeInserted', element_created, false);
window.addEventListener('InsertedFromClick', element_created_custom, false);
<div id="creator">Click me to check that tracked</div>
<div id="creator2">Click me to check that I'm not tracked</div>
<div>HTML</div>

Related

Appending a button on a div after div loads in the dom

I am trying to access an div (or here a tr tag) on which i want to append a button . But i am unable to access the tr tag because its loading after sometime and is not present in the DOM at that moment and getting error .
how to access a tag after loading something on the DOM
<script>
var btn = document.getElementById('btnn');
var tab = document.getElementsByClassName("scope")[0];
tab.append(btn)
</script>
I think your document.getElementById code should only be executed after everyting has been loaded. You could add an "load" Eventlistener and put your code inside it.
window.addEventListener("load", function(){
var btn = document.getElementById('btnn');
var tab = document.getElementsByClassName("scope")[0];
tab.append(btn)
});
By the way: I always use "defer" for my includes, like this:
<script src="{{ asset('/general/js/local.js') }}" defer></script>
This makes sure the "load" event will only be triggered after all includes have been loaded.
You could watch the DOM using the MutationObserver API. If the element you're observing is added, you could then apply your other code (e.g., append a button).
Basic Example:
let watchDOM = (function(){
let mo = window.MutationObserver;
return function(obj, callback){
if (!obj || !obj.nodeType === 1) {
return;
}
if (mo) {
let obs = new mo(function(mutations, observer) {
callback(mutations);
});
obs.observe(obj, { childList:true, subtree:true });
}
else if (window.addEventListener){
obj.addEventListener('DOMNodeInserted', callback, false);
obj.addEventListener('DOMNodeRemoved', callback, false);
}
}
})();
watchDOM(document.body, function(e) {
// This will notify you if a new DIV is added.
if (e[0].addedNodes[0].tagName === "DIV") {
// If the DIV is added, you can then take some action here.
// For example, you could append your button here.
console.log("div added");
}
});
// This adds a new DIV after 3 seconds of running the script
setTimeout(function() {
let newDiv = document.createElement("div");
document.body.appendChild(newDiv);
}, 3000);
Is this DIV getting created in the response of an AJAX call. If that is the case then you need to call you your button appending logic once the response of AJAX call has been received.
OR use this:
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", function(event) {
// - Code to execute when all DOM content is loaded.
// - including fonts, images, etc.
});
</script>

Javascript: addEventListener only working in setTimeout

I'm trying to set event listeners but it's only working if I set them within setTimeout.
Doesn't work:
WebApp.setController('jobs', function() {
WebApp.setView('header', 'header');
WebApp.setView('nav', 'nav');
WebApp.setView('jobs', 'main');
var jobs = document.querySelectorAll('.jobs-category');
for(let i = 0; i < jobs.length; i++)
{
console.log('events added');
jobs[i].addEventListener("dragover", function( event ) {
console.log('drag over');
event.preventDefault();
});
jobs[i].addEventListener('drop', function(event) {
event.preventDefault();
console.log('dropped');
}, false);
}
});
Does work:
WebApp.setController('jobs', function() {
WebApp.setView('header', 'header');
WebApp.setView('nav', 'nav');
WebApp.setView('jobs', 'main');
window.setTimeout(function() {
var jobs = document.querySelectorAll('.jobs-category');
for(let i = 0; i < jobs.length; i++)
{
console.log('events added');
jobs[i].addEventListener("dragover", function( event ) {
console.log('drag over');
event.preventDefault();
});
jobs[i].addEventListener('drop', function(event) {
event.preventDefault();
console.log('dropped');
}, false);
}
}, 1);
});
(only setTimout is different/additionally)
setController() saves the function and executes it if the route get requested.
setView() binds HTML5-templates to DOM:
var Template = document.querySelector('#' + Name);
var Clone = document.importNode(Template.content, true);
var CloneElement = document.createElement('div');
CloneElement.appendChild(Clone);
CloneElement = this.replacePlaceholders(CloneElement);
document.querySelector(Element).innerHTML = CloneElement.innerHTML;
Why does this only work in setTimeout? I thought javascript is synchronous.
Addition: This is a single page app, which gets already loaded after DOM is ready.
Where is your Javascript within the page?
My guess is that the HTML is not ready yet when you try to register the event, this is why it only works with setTimeout.
Try either including your javascript at the bottom of the page (after the HTML) or listening to the page loaded event. See this question for info how to do that - $(document).ready equivalent without jQuery.
Per your assumption of synchronous javascript - yes it is (mostly) but also the way that the browser renders the page is (unless using async loading of scripts). Which means that HTML that is after a javascript will not be available yet.
UPDATE - per your comment of using a single page app, you must listen to the load complete/done/successful event. Another way you could bypass it will be listening to the events on the parent element (which is always there).
Hope this helps.
document.addEventListener("DOMContentLoaded", function(event) {
//do work
});
Most likely you are loading your JS file before your html content. You can't run your Javascript functions until the DOM is ready.

Is bubbling available for image load events?

Can I use:
window.addEventListner();
in some way.
All my images have a display = 'none'.
Once the image has loaded,
I want to set display = 'inline'
This way I can normalize what is displayed while the image is being downloaded.
In this case, I can not pre-load my images.
Use capturing event listener on some DOM node other than window (body or other parent of image elements of interest):
document.body.addEventListener(
'load',
function(event){
var tgt = event.target;
if( tgt.tagName == 'IMG'){
tgt.style.display = 'inline';
}
},
true // <-- useCapture
)
With this you don't have to (re)attach event handlers while iterating through document.images.
And this will work for dynamically inserted images as well.
Same is true for image's error loading events. MDN: addEventListener
The load/onload event does not bubble (reference, reference), so what you're asking for is not possible. You'll have to attach an event handler to each image node, or intercept the event during the capture phase, as suggested in other answers.
Array.prototype.forEach.call(document.querySelectorAll('img'), function (elem) {
elem.addEventListener('load', function () {
this.style.display = 'inline';
});
if (elem.complete) {
elem.style.display = 'inline';
}
});
The "load" event will not trigger if the image is incidentally loaded already; thus, we check whether complete is already set.
You can use the Image.onload event handler but there's no bubbling involved.
var i = new Image;
i.onload = function() {
this.style.display = 'block';
}
Since the load event does not bubble, you can lauch your own bubbling event. An example with jQuery:
<img src="dog.jpg" onload="$(this).trigger('image-loaded')" />
$('img').on('load', function() {
$(this).show()
})
Without libraries:
window.onload = function() {
var imgs = document.querySelectorAll('img')
imgs.onload = function() {
this.style.display = 'inline';
}
}

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
}
});

Detect Click into Iframe using JavaScript

I understand that it is not possible to tell what the user is doing inside an iframe if it is cross domain. What I would like to do is track if the user clicked at all in the iframe. I imagine a scenario where there is an invisible div on top of the iframe and the the div will just then pass the click event to the iframe.
Is something like this possible? If it is, then how would I go about it? The iframes are ads, so I have no control over the tags that are used.
This is certainly possible. This works in Chrome, Firefox, and IE 11 (and probably others).
const message = document.getElementById("message");
// main document must be focused in order for window blur to fire when the iframe is interacted with.
// There's still an issue that if user interacts outside of the page and then click iframe first without clicking page, the following logic won't run. But since the OP is only concerned about first click this shouldn't be a problem.
window.focus()
window.addEventListener("blur", () => {
setTimeout(() => {
if (document.activeElement.tagName === "IFRAME") {
message.textContent = "clicked " + Date.now();
console.log("clicked");
}
});
}, { once: true });
<div id="message"></div>
<iframe width="50%" height="300" src="//example.com"></iframe>
Caveat: This only detects the first click. As I understand, that is all you want.
This is small solution that works in all browsers even IE8:
var monitor = setInterval(function(){
var elem = document.activeElement;
if(elem && elem.tagName == 'IFRAME'){
clearInterval(monitor);
alert('clicked!');
}
}, 100);
You can test it here: http://jsfiddle.net/oqjgzsm0/
Based on Mohammed Radwan's answer I came up with the following jQuery solution. Basically what it does is keep track of what iFrame people are hovering. Then if the window blurs that most likely means the user clicked the iframe banner.
the iframe should be put in a div with an id, to make sure you know which iframe the user clicked on:
<div class='banner' bannerid='yyy'>
<iframe src='http://somedomain.com/whatever.html'></iframe>
<div>
so:
$(document).ready( function() {
var overiFrame = -1;
$('iframe').hover( function() {
overiFrame = $(this).closest('.banner').attr('bannerid');
}, function() {
overiFrame = -1
});
...
this keeps overiFrame at -1 when no iFrames are hovered, or the 'bannerid' set in the wrapping div when an iframe is hovered. All you have to do is check if 'overiFrame' is set when the window blurs, like so:
...
$(window).blur( function() {
if( overiFrame != -1 )
$.post('log.php', {id:overiFrame}); /* example, do your stats here */
});
});
Very elegant solution with a minor downside: if a user presses ALT-F4 when hovering the mouse over an iFrame it will log it as a click. This only happened in FireFox though, IE, Chrome and Safari didn't register it.
Thanks again Mohammed, very useful solution!
Is something like this possible?
No. All you can do is detect the mouse going into the iframe, and potentially (though not reliably) when it comes back out (ie. trying to work out the difference between the pointer passing over the ad on its way somewhere else versus lingering on the ad).
I imagine a scenario where there is an invisible div on top of the iframe and the the div will just then pass the click event to the iframe.
Nope, there is no way to fake a click event.
By catching the mousedown you'd prevent the original click from getting to the iframe. If you could determine when the mouse button was about to be pressed you could try to get the invisible div out of the way so that the click would go through... but there is also no event that fires just before a mousedown.
You could try to guess, for example by looking to see if the pointer has come to rest, guessing a click might be about to come. But it's totally unreliable, and if you fail you've just lost yourself a click-through.
The following code will show you if the user click/hover or move out of the iframe:-
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Detect IFrame Clicks</title>
<script type="text/javascript">
$(document).ready(function() {
var isOverIFrame = false;
function processMouseOut() {
log("IFrame mouse >> OUT << detected.");
isOverIFrame = false;
top.focus();
}
function processMouseOver() {
log("IFrame mouse >> OVER << detected.");
isOverIFrame = true;
}
function processIFrameClick() {
if(isOverIFrame) {
// replace with your function
log("IFrame >> CLICK << detected. ");
}
}
function log(message) {
var console = document.getElementById("console");
var text = console.value;
text = text + message + "\n";
console.value = text;
}
function attachOnloadEvent(func, obj) {
if(typeof window.addEventListener != 'undefined') {
window.addEventListener('load', func, false);
} else if (typeof document.addEventListener != 'undefined') {
document.addEventListener('load', func, false);
} else if (typeof window.attachEvent != 'undefined') {
window.attachEvent('onload', func);
} else {
if (typeof window.onload == 'function') {
var oldonload = onload;
window.onload = function() {
oldonload();
func();
};
} else {
window.onload = func;
}
}
}
function init() {
var element = document.getElementsByTagName("iframe");
for (var i=0; i<element.length; i++) {
element[i].onmouseover = processMouseOver;
element[i].onmouseout = processMouseOut;
}
if (typeof window.attachEvent != 'undefined') {
top.attachEvent('onblur', processIFrameClick);
}
else if (typeof window.addEventListener != 'undefined') {
top.addEventListener('blur', processIFrameClick, false);
}
}
attachOnloadEvent(init);
});
</script>
</head>
<body>
<iframe src="www.google.com" width="100%" height="1300px"></iframe>
<br></br>
<br></br>
<form name="form" id="form" action=""><textarea name="console"
id="console" style="width: 100%; height: 300px;" cols="" rows=""></textarea>
<button name="clear" id="clear" type="reset">Clear</button>
</form>
</body>
</html>
You need to replace the src in the iframe with your own link. Hope this'll help.
Regards,
Mo.
Just found this solution...
I tried it, I loved it..
Works for cross domain iframes for desktop and mobile!
Don't know if it is foolproof yet
window.focus();
window.addEventListener('blur',function(){
if(document.activeElement.id == 'CrossDomainiframeId'){
//do something :-)
}
});
Happy coding
You can achieve this by using the blur event on window element.
Here is a jQuery plugin for tracking click on iframes (it will fire a custom callback function when an iframe is clicked) :
https://github.com/finalclap/iframeTracker-jquery
Use it like this :
jQuery(document).ready(function($){
$('.iframe_wrap iframe').iframeTracker({
blurCallback: function(){
// Do something when iframe is clicked (like firing an XHR request)
}
});
});
see http://jsfiddle.net/Lcy797h2/ for my long winded solution that doesn't work reliably in IE
$(window).on('blur',function(e) {
if($(this).data('mouseIn') != 'yes')return;
$('iframe').filter(function(){
return $(this).data('mouseIn') == 'yes';
}).trigger('iframeclick');
});
$(window).mouseenter(function(){
$(this).data('mouseIn', 'yes');
}).mouseleave(function(){
$(this).data('mouseIn', 'no');
});
$('iframe').mouseenter(function(){
$(this).data('mouseIn', 'yes');
$(window).data('mouseIn', 'yes');
}).mouseleave(function(){
$(this).data('mouseIn', null);
});
$('iframe').on('iframeclick', function(){
console.log('Clicked inside iframe');
$('#result').text('Clicked inside iframe');
});
$(window).on('click', function(){
console.log('Clicked inside window');
$('#result').text('Clicked inside window');
}).blur(function(){
console.log('window blur');
});
$('<input type="text" style="position:absolute;opacity:0;height:0px;width:0px;"/>').appendTo(document.body).blur(function(){
$(window).trigger('blur');
}).focus();
http://jsfiddle.net/QcAee/406/
Just make a invisible layer over the iframe that go back when click and go up when mouseleave event will be fired !!
Need jQuery
this solution don't propagate first click inside iframe!
$("#invisible_layer").on("click",function(){
alert("click");
$("#invisible_layer").css("z-index",-11);
});
$("iframe").on("mouseleave",function(){
$("#invisible_layer").css("z-index",11);
});
iframe {
width: 500px;
height: 300px;
}
#invisible_layer{
position: absolute;
background-color:trasparent;
width: 500px;
height:300px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="message"></div>
<div id="invisible_layer">
</div>
<iframe id="iframe" src="//example.com"></iframe>
This works for me on all browsers (included Firefox)
https://gist.github.com/jaydson/1780598
https://jsfiddle.net/sidanmor/v6m9exsw/
var myConfObj = {
iframeMouseOver : false
}
window.addEventListener('blur',function(){
if(myConfObj.iframeMouseOver){
console.log('Wow! Iframe Click!');
}
});
document.getElementById('idanmorblog').addEventListener('mouseover',function(){
myConfObj.iframeMouseOver = true;
});
document.getElementById('idanmorblog').addEventListener('mouseout',function(){
myConfObj.iframeMouseOver = false;
});
<iframe id="idanmorblog" src="https://sidanmor.com/" style="width:400px;height:600px" ></iframe>
<iframe id="idanmorblog" src="https://sidanmor.com/" style="width:400px;height:600px" ></iframe>
Mohammed Radwan,
Your solution is elegant. To detect iframe clicks in Firefox and IE, you can use a simple method with document.activeElement and a timer, however... I have searched all over the interwebs for a method to detect clicks on an iframe in Chrome and Safari. At the brink of giving up, I find your answer. Thank you, sir!
Some tips:
I have found your solution to be more reliable when calling the init() function directly, rather than through attachOnloadEvent(). Of course to do that, you must call init() only after the iframe html. So it would look something like:
<script>
var isOverIFrame = false;
function processMouseOut() {
isOverIFrame = false;
top.focus();
}
function processMouseOver() { isOverIFrame = true; }
function processIFrameClick() {
if(isOverIFrame) {
//was clicked
}
}
function init() {
var element = document.getElementsByTagName("iframe");
for (var i=0; i<element.length; i++) {
element[i].onmouseover = processMouseOver;
element[i].onmouseout = processMouseOut;
}
if (typeof window.attachEvent != 'undefined') {
top.attachEvent('onblur', processIFrameClick);
}
else if (typeof window.addEventListener != 'undefined') {
top.addEventListener('blur', processIFrameClick, false);
}
}
</script>
<iframe src="http://google.com"></iframe>
<script>init();</script>
You can do this to bubble events to parent document:
$('iframe').load(function() {
var eventlist = 'click dblclick \
blur focus focusin focusout \
keydown keypress keyup \
mousedown mouseenter mouseleave mousemove mouseover mouseout mouseup mousemove \
touchstart touchend touchcancel touchleave touchmove';
var iframe = $('iframe').contents().find('html');
// Bubble events to parent
iframe.on(eventlist, function(event) {
$('html').trigger(event);
});
});
Just extend the eventlist for more events.
I ran into a situation where I had to track clicks on a social media button pulled in through an iframe. A new window would be opened when the button was clicked. Here was my solution:
var iframeClick = function () {
var isOverIframe = false,
windowLostBlur = function () {
if (isOverIframe === true) {
// DO STUFF
isOverIframe = false;
}
};
jQuery(window).focus();
jQuery('#iframe').mouseenter(function(){
isOverIframe = true;
console.log(isOverIframe);
});
jQuery('#iframe').mouseleave(function(){
isOverIframe = false;
console.log(isOverIframe);
});
jQuery(window).blur(function () {
windowLostBlur();
});
};
iframeClick();
Combining above answer with ability to click again and again without clicking outside iframe.
var eventListener = window.addEventListener('blur', function() {
if (document.activeElement === document.getElementById('contentIFrame')) {
toFunction(); //function you want to call on click
setTimeout(function(){ window.focus(); }, 0);
}
window.removeEventListener('blur', eventListener );
});
This definitely works if the iframe is from the same domain as your parent site. I have not tested it for cross-domain sites.
$(window.frames['YouriFrameId']).click(function(event){ /* do something here */ });
$(window.frames['YouriFrameId']).mousedown(function(event){ /* do something here */ });
$(window.frames['YouriFrameId']).mouseup(function(event){ /* do something here */ });
Without jQuery you could try something like this, but again I have not tried this.
window.frames['YouriFrameId'].onmousedown = function() { do something here }
You can even filter your results:
$(window.frames['YouriFrameId']).mousedown(function(event){
var eventId = $(event.target).attr('id');
if (eventId == 'the-id-you-want') {
// do something
}
});
We can catch all the clicks. The idea is to reset focus on an element outside the iFrame after each click:
<input type="text" style="position:fixed;top:-1000px;left:-1000px">
<div id="message"></div>
<iframe id="iframe" src="//example.com"></iframe>
<script>
focus();
addEventListener('blur', function() {
if(document.activeElement = document.getElementById('iframe')) {
message.innerHTML += 'Clicked';
setTimeout(function () {
document.querySelector("input").focus();
message.innerHTML += ' - Reset focus,';
}, 1000);
}
});
</script>
JSFiddle
Assumptions -
Your script runs outside the iframe BUT NOT in the outermost window.top window. (For outermost window, other blur solutions are good enough)
A new page is opened replacing the current page / a new page in a new tab and control is switched to new tab.
This works for both sourceful and sourceless iframes
var ifr = document.getElementById("my-iframe");
var isMouseIn;
ifr.addEventListener('mouseenter', () => {
isMouseIn = true;
});
ifr.addEventListener('mouseleave', () => {
isMouseIn = false;
});
window.document.addEventListener("visibilitychange", () => {
if (isMouseIn && document.hidden) {
console.log("Click Recorded By Visibility Change");
}
});
window.addEventListener("beforeunload", (event) => {
if (isMouseIn) {
console.log("Click Recorded By Before Unload");
}
});
If a new tab is opened / same page unloads and the mouse pointer is within the Iframe, a click is considered
Based in the answer of Paul Draper, I created a solution that work continuously when you have Iframes that open other tab in the browser. When you return the page continue to be active to detect the click over the framework, this is a very common situation:
focus();
$(window).blur(() => {
let frame = document.activeElement;
if (document.activeElement.tagName == "IFRAME") {
// Do you action.. here frame has the iframe clicked
let frameid = frame.getAttribute('id')
let frameurl = (frame.getAttribute('src'));
}
});
document.addEventListener("visibilitychange", function () {
if (document.hidden) {
} else {
focus();
}
});
The Code is simple, the blur event detect the lost of focus when the iframe is clicked, and test if the active element is the iframe (if you have several iframe you can know who was selected) this situation is frequently when you have publicity frames.
The second event trigger a focus method when you return to the page. it is used the visibility change event.
Here is solution using suggested approaches with hover+blur and active element tricks, not any libraries, just pure js. Works fine for FF/Chrome. Mostly approache is same as #Mohammed Radwan proposed, except that I use different method proposed by #zone117x to track iframe click for FF, because window.focus is not working without addition user settings:
Makes a request to bring the window to the front. It may fail due to
user settings and the window isn't guaranteed to be frontmost before
this method returns.
Here is compound method:
function () {
const state = {};
(function (setup) {
if (typeof window.addEventListener !== 'undefined') {
window.addEventListener('load', setup, false);
} else if (typeof document.addEventListener !== 'undefined') {
document.addEventListener('load', setup, false);
} else if (typeof window.attachEvent !== 'undefined') {
window.attachEvent('onload', setup);
} else {
if (typeof window.onload === 'function') {
const oldonload = onload;
window.onload = function () {
oldonload();
setup();
};
} else {
window.onload = setup;
}
}
})(function () {
state.isOverIFrame = false;
state.firstBlur = false;
state.hasFocusAcquired = false;
findIFramesAndBindListeners();
document.body.addEventListener('click', onClick);
if (typeof window.attachEvent !== 'undefined') {
top.attachEvent('onblur', function () {
state.firstBlur = true;
state.hasFocusAcquired = false;
onIFrameClick()
});
top.attachEvent('onfocus', function () {
state.hasFocusAcquired = true;
console.log('attachEvent.focus');
});
} else if (typeof window.addEventListener !== 'undefined') {
top.addEventListener('blur', function () {
state.firstBlur = true;
state.hasFocusAcquired = false;
onIFrameClick();
}, false);
top.addEventListener('focus', function () {
state.hasFocusAcquired = true;
console.log('addEventListener.focus');
});
}
setInterval(findIFramesAndBindListeners, 500);
});
function isFF() {
return navigator.userAgent.search(/firefox/i) !== -1;
}
function isActiveElementChanged() {
const prevActiveTag = document.activeElement.tagName.toUpperCase();
document.activeElement.blur();
const currActiveTag = document.activeElement.tagName.toUpperCase();
return !prevActiveTag.includes('BODY') && currActiveTag.includes('BODY');
}
function onMouseOut() {
if (!state.firstBlur && isFF() && isActiveElementChanged()) {
console.log('firefox first click');
onClick();
} else {
document.activeElement.blur();
top.focus();
}
state.isOverIFrame = false;
console.log(`onMouseOut`);
}
function onMouseOver() {
state.isOverIFrame = true;
console.log(`onMouseOver`);
}
function onIFrameClick() {
console.log(`onIFrameClick`);
if (state.isOverIFrame) {
onClick();
}
}
function onClick() {
console.log(`onClick`);
}
function findIFramesAndBindListeners() {
return Array.from(document.getElementsByTagName('iframe'))
.forEach(function (element) {
element.onmouseover = onMouseOver;
element.onmouseout = onMouseOut;
});
}
}
A colleague and I, we have a problem similar to that of Brian Trumpsett and found this thread very helpful.
Our kiosk has animations inside iframes and we need to track the page activity to set a timer.
As suggested here, rather than tracking the clicks, we now detect the focus change at each click and change it back
The following code is Okay on macOS with Safari and Chrome but does not work with FireFox (why?):
var eventListener = window.addEventListener('blur', function() {
if (document.activeElement.classList && document.activeElement.classList[0] == 'contentiFrame') {
refresh(); //function you want to call on click
setTimeout(function(){ window.focus(); }, 1);
}
window.removeEventListener('blur', eventListener );
});
The problem is that, on Windows, it works neither with Chrome nor with FireFox and thus, our kiosk is not functional.
Do you know why it is not working ?
Do you have a solution to make it work on Windows ?
As found there : Detect Click into Iframe using JavaScript
=> We can use iframeTracker-jquery :
$('.carousel-inner .item').each(function(e) {
var item = this;
var iFrame = $(item).find('iframe');
if (iFrame.length > 0) {
iFrame.iframeTracker({
blurCallback: function(){
// Do something when iFrame is clicked (like firing an XHR request)
onItemClick.bind(item)(); // calling regular click with right context
console.log('IFrameClick => OK');
}
});
console.log('IFrameTrackingRegistred => OK');
}
})
My approach was similar to that proposed by Paul Draper above. However, it didn't work in Firefox because activeElement did not update in time for the code to execute. So we wait a little bit.
This will also fire if you tab into the iframe. For my use case, it's fine, but you could filter for that keypress.
addEventListenerOnIframe() {
window.addEventListener('blur', this.onBlur);
}
onBlur = () => {
setTimeout(() => {
let activeElement = document.activeElement;
let iframeElement = document.querySelector('iframe');
if (activeElement === iframeElement) {
//execute your code here
//we only want to listen for the first time we click into the iframe
window.removeEventListener('blur', this.onBlur);
}
}, 500);
};
I believe you can do something like:
$('iframe').contents().click(function(){function to record click here });
using jQuery to accomplish this.

Categories

Resources