I have an issue where a form is embedded in an iframe and after the form is submitted, the iframe is deleted from the DOM. Immediately after the form is deleted (the form was the last thing to have focus) I am unable to detect events that are bound to the root window element.
The frame is being loaded from a separate domain, although it does not matter for this example, it only matters that I have no control over the scripts on that page.
I understand that I cannot detect DOM events in the iframe, but all events are lost until the user clicks back on the DOM after iframe removal. This happens in both Firefox and Chrome. IE appears to return focus back to the original DOM as expected. I have not tested in other browsers.
$(window).keydown(function(e){
console.log (e.keyCode);
});
var $iframe = $("<iframe src='www.example.com'>");
$("body").append($iframe);
window.setTimeout(function(){
$iframe.remove();
}, 1000);
(The code above is just an example, i have included a working codepen to illustrate further- http://codepen.io/anon/pen/WQroqe)
To use the codepen -
Click the "click to load iframe button".
Make sure you click in the iframe so it has focus
After the iframe deletes, type anything and notice the DOM does not log your key strokes.
Click on the DOM and notice that your keystrokes are being logged properly.
Use Case: Form opens an iframe and submits, then removes itself from the DOM. I want to be able to detect keyboard events after the form is submitted without the user needing to use their mouse.
Question: I thought the top most DOM element was "window" and if this is not capturing the keyboard events, what is? There are no other DOMs currently present (i.e. iframes) as far as I know. I tested this is both firefox and chrome. Any explanation as to what is happening and why what I am trying to do is not possible or a way to capture the events would be greatly appreciated.
(My current solution is to use a MutationObserver to watch for the iframe to disappear and force focus back on the window. I also know I could use a setInterval to continually check for the iframe. Both solution feel like I am doing extra work).
after closing the iframe, focus the window using $(window).focus(); if you must
in the sample you'd do it like
$iframe.load(function(){
window.setTimeout(function(){
console.log("deleting");
$("#deleteMe iframe").remove();
$(window).focus(); // <======
}, 5000);
});
Related
I've come across some strange functionality on chrome, mobile.
When I try to focus on an element on chrome, it doesn't work when you try to just load the input, getItById and do .focus(). However, if you wrap it in an event listener attached to a button, and click the button with your mouse, it works fine.
So, I tried to trick it by seeing if you could call btn.click(), but that doesn't activate the .focus()
Have a go below: On mobile, chrome (at least for iOS), load the page. You should get an alert 'Clicked', but it won't focus on the input. Then, try clicking on the button. You will get both the alert AND the focus works.
I found this interesting and wanted to see if people knew of a workaround.
Link here - jsfiddle
const btn = document.getElementById('button')
btn.addEventListener('click', () => {
alert('clicked')
const input = document.getElementById('input')
input.focus()
})
btn.click()
<input id="input" type="text">
<button id="button">Button</button>
Edit
Another thing I've noticed, is that if you put your phone go to sleep, and open it again, the focus() works without the click.
Edit 2 - added link for mobile
Why does this happen?
Because mobile devices usually need to change the screen dimensions when a keyboard is shown, they come with some restrictions on when a focus event can be triggered. This is intentionally limited browser behavior on touch devices to guarantee a good UX and avoid performance issues of resizing the browser window ( = potentially very expensive style recalculation).
There's some cross browser differences in how far these restrictions go, and some browsers have bugs on top of that.
But in general they all require an actual user interaction to have happened not too long before the focus is programmatically triggered. Using btn.click() is not the same as an actual touch event, and so the browser will ignore it seeing there was no recent touch event.
On a tracking issue on this behavior for Webkit, Apple provides a motivation:
We (Apple) like the current behavior and do not want programmatic focus to bring up the keyboard when you do not have a hardware keyboard attached and the programmatic focus was not invoked in response to a user gesture. Why you may ask...because auto bringing up the software keyboard can be seen as annoying and a distraction to a user (not for your customers, but for everyone not using your app) given that:
We bring up the keyboard, which takes up valuable real estate on screen.
When we intent to bring up the software keyboard we zoom and scroll the page to give a pleasing input experience (or at least we hope it is pleasing; file bugs if not).
This similar issue with the autoplay attribute, which also requires a gesture (or page load) to have happened recently enough, could be helpful in understanding the kind of problem.
How to work around these limitations?
Most of the time
Likely you don't need to, as most code that would trigger focus is running as a response to a user action and will be allowed to trigger focus. I didn't find data yet on how big the window of time is, but I guess it's big enough that in regular cases it's not a problem (unless you set timeouts of course).
A possible problem for those cases is the script would run so long that the event is already considered too long ago. In fact that's what happened in the related autoplay issue, where the code would request some network resource, and only after the response would trigger the video to play. Depending on the speed of the network/device the video would sometimes auto play, and sometimes not.
Technically the same could happen with an input that is only focused after a network delay making it not show the keyboard. As long as your code doesn't do any network requests you won't have this kind of problem.
On page load
This is definitely not a "fix" for the problem, but you can do a best effort to manage focus on page load by using the attribute specifically made for it. This still won't make a keyboard appear when it otherwise wouldn't.
The autofocus attribute is at least partially supported on all browsers.
Perhaps just calling focus() directly in a script on page load works, but again there's a chance that this code runs too long after the page started being displayed, especially on slower devices. This probably also happens when adding HTML with the autofocus attribute programmatically (e.g. React). If the initial HTML contains the autofocus attribute on the right element that shouldn't occur.
There is deinfitely something weird happening here.
It seems "part" of the DOM thinks that the input element is in focus initially, but chrome does not complete the focusing. For exmaple, if the background is set with:
document.activeElement.style.backgroundColor = "pink";
Then the input element's background is pink. So, some part of chrome's DOM thinks the input element is in focus.
Initially, in my case, the input element is rendered by chrome in its default non-focus styling.
The alert() seems to be interfering with the process. In my case, taking the alert out had the effect of changing the styling of the input element from non-focus styling to in-focus styling, but the cursor did not appear in the input element, nor did the pop up keyboard appear.
By:
removing the alert
wrapping the click inside a function
calling that function on load
fixed the problem in my case. Initially now the element is in-focus styled, the cursor appears in the element's box, and the keyboard pops up.
The solution to the posting by #HJo, January 2019, is to replace:
btn.click();
with:
function load() { btn.click(); }
remove the alert(),
and make the body tag as:
<body onload="load()">
#Peter. If this does not fix your problem too, can you please provide minimal code for your context.
const event = new Event('tap');
const btn = document.getElementById('button')
const input = document.getElementById('input')
btn.addEventListener('click', () => {
prompt('clicked')
input.focus();
})
window.onload = function(){
btn.dispatchEvent(event);
}
}
The code above works when the website tab is just opened, but not on reload for some reason. I tested it on an Ipad and Iphone so hopefully it works well on your end too.
Here's a link to a repl:
https://r.hackinggo306.repl.co
Edit: This seems to be the closest I can get, but also acknowledge the fact that mobile devices might be deliberately preventing this. (It would be annoying if a website looped this.)
On the mobile, you will need to use touchstart event instead of click event.
You can find it here. Hope this helps.
I think, this problem can occur, because of the your JavaScript script is executed before the DOM is Mounted.
so you can do different things,
One Thing
add defer keyword to script tag. Basically it does is, that script is only run after the DOM is mounted. See
Second Thing
Wrap window.onload event with your input focus functionality.
<script type="text/javascript">
window.onload = function () {
const btn = document.getElementById('button');
const input = document.getElementById('input')
input.focus()
}
</script>
window.onload event is only fired when the DOM is finishing mounting.so that may be your JavaScript script run before the input element is mounting.so add onload event to your code.see this
I've got dialog that I wrote and it closes when clicking outside (no overlay/backdrop).
It works nicely unless there's an iframe, in which case my listener on outside clicks is never called.
Here is a JSBIN to illustrate the problem. (http://jsbin.com/vuneyopedu/edit?js,console,output)
To briefly explain in the below screenshot:
Clicking RED Dialog Increments.
Clicking Outside Dialog (YELLOW and GREEN) should Decrement but only YELLOW works.
The event listener for outside clicks is never called when clicking iframe (GREEN)
Question is - How do I make clicking anything outside the RED square (specifically clicking the iframe) decrement the number. (or close the dialog, in the "real" world)
How about binding event to iframes' document.
iframes = document.getElementsByTagName('iframe');
iframesArray = Array.prototype.slice.apply(iframes);
iframesArray.forEach(function(frame) {
frame.contentWindow.document.addEventListener('click', function() {
inc();
}, true);
});
The correct approach here is to use a modal and one of the strongest use-cases for the modal. By design modals prevent clicks from falling through to elements below hence there is no need to handle side effects like your iframe issue or e.stopPropagation() or anything else. It also makes positioning very clean.
As a side note, the currently accepted answer is a very poor approach since it relies on adding an event handler inside the iFrame, then binding that onto the parent container window. This is wrong on many levels: no separation of concerns, iframe from different domains will be blocked due to CORS, iframe will register with any and all parents even when not needed, etc.
I am having an issue in firefox. My research so far is telling me that there is not really a reliable way to deal with my problem, but I am wanting to ask just in case.
I have the following javascript/jquery to open a new window triggered by pressing a button on my page:
alipayTransactionModalTrigger.click(function() {
NIWindow=window.open("<!--ALIPAY_CONF_REDIRECT_URL-->", "NI payment");
alipayTransactionModal.dialog("open");
});
In Chrome and Firefox, this opens a new frame, and the frame gets focus. This is fine. However, if a user leaves this new frame open, and comes back to my original page. They are able to hit the button to trigger this event again. If this occurs, the new frame reloads as it should, but in Firefox the new frame does NOT pull focus this on this refresh. I am not sure why it would pull focus on the initial new frame load, but not pull focus if the frame is refreshed with the same event trigger. I haven't had the courage yet to check this in IE...
Is this one of those things that you cannot reliably control? Or is there a way to do this? Note, I have tried using NIWindow.focus() after the initial window open line of code above. No luck.
I believe I read something about possibly using alerts, but I was not sure how to implement the described solution, and I read that it was a bit of a hacky solution.
I could just always open a new window "_blank", and that would assure focus, but the newly opened frame url uses parameters that are based on the parent window. I have no access to that code, so there is no way for me to add checks to make sure data matches up, until I get a response back from this redirect
Give each new window a new name. Do you have a reason for reusing the "NI payment" name?.
If your code has dependencies on this window name you should consider passing the window reference (NIWindow) directly instead of the name. Injecting/passing your dependencies instead of relying on a magic super global is a better approach in general.
My browser (firefox) prevents any popup from loading, and loads links that open new windows in the current tab, unless I explicitly say I want the link to load on a new tab or window, with the appropriate shortcuts (for example, middle click on the link, or left click with ctrl pressed causes the link to open on a new tab, and shift + left click on a new window).
I would like to create a javascript function f() that runs some code (meant to create the link address) when the link is pressed, and then loads the link that has been created, without removing the user experience described above.
Right now what I have is something like <a href="javascript:void(0)" onclick="f()"/>, but middle click doesn't work (it instead loads the url javascript:void(0)) and neither do the other features described above.
Do you have any idea as how to solve my problem ?
Thanks.
have you tried window.open('url')?
see: http://www.javascript-coder.com/window-popup/javascript-window-open.phtml
Also, as far as I know, you can't control whether or not the browser opens in a new tab or new window. That is a browser setting that is different for every user.
You might also try removing the onclick, and using
EDIT
There seems to be issues with using middle click with opening new tabs instead of executing the javascript: middle click (new tabs) and javascript links
As that site says, you can instead create an id for the element and bind it through javascript.
**Taken from that link:
...
And then in your JS, hook the link via it's ID to do the AJAX call.
Remember that you need to stop the click event from bubbling up. Most
frameworks have an event killer built in that you can call (just look
at its Event class).
Here's the event handling and event-killer in jquery:
$("#thisLink").click(function(ev, ob) {
alert("thisLink was clicked");
ev.stopPropagation();
});
Without jQuery, it might look like this:
document.getElementById('thisLink').onclick = function(e)
{
//do someting
e.stopPropagation();
}
Other browsers may vary, but by default Firefox doesn't tell the web page that it has been middle-clicked (unless you set the hidden preference to enable the feature). You might be able to create a workaround based on the focus and/or mouseover events instead.
When the user clicks on an external iframe, all event listeners on my BODY tag stop working and I need to prompt the user to click outside to regain the "focus" on the body tag.
But knowing when the user clicks on an iframe is actually very hard.
I know of two ways to know when there is a click on an iframe:
1- Overlapping a transparent div on the iframe (with the downside that the user has to click twice to actually click the iframe).
2- A very rough workaround which is having an input autofocused all the time and detect when it loses focus. But it's just stupid to even consider implementing it.
Is there another way to know when the user "loses focus" of the main body by clicking on an external iframe?
This is super easy, but not necessarily effecient/ethical.
setTimeout(function(){
document.body.focus()
}, 100 );
I would in all honesty use option #2 with some jQuery.
$(el).focusout(function(){
$(this).focus();
})
The easiest method and the only one I'm aware of actually is to make an infinite loop that gets current focused element.id.
Then it's simply a matter of comparing that id with your iframes to know which has been clicked.
Note that you will only be able to know for sure the user clicked once in the iframe, but there is no way to count clicks afterwards. Also notes that iframes can focus themselves with this method will be indistinguishable from a click.