I'm making a widget that slides in and out of view on hover with showTracker and hideTracker functions. I want to prevent it from sliding out of view if it contains a focussed form element though, so I've got this going:
function hideTracker(){
if($('#tracker').find(':focus').length == 0){
$('#tracker').stop().hide();
}
}
Cool. Now it doesn't hide if the mouse happens to move out if there's a field in focus. Unfortunately, that also means that when the field does lose focus (and it's time for the widget to hide again) it just stays there. The unHover event has been and gone.
So I added this:
$('#tracker *').blur(function(){
hideTracker();
});
And that works too - with one little bug that I need help with!
If the focus moves from one element within the tracker to another which is also within #tracker, the tracker hides. I figured that if($('#tracker').find(':focus').length == 0) would return false, given that the next form element has focus, but I guess it doesn't.
Is it the case that .blur() fires before the next element attains focus?
How can I get around this?
How about something like this?
$('body *').focus(function(){
if(!$(this).is('#tracker *') && $('#tracker:visible').length != 0) hideTracker();
});
Yikes. Tricky. Yes, what's happening is:
mousedown: old form element gets the blur event. $(':focus').length == 0.
mouseup: new form element gets the focus event. $newFormElement.is(':focus') == true.
This is an improvement:
$('#tracker').focusout(function() //basically like $('#tracker, #tracker *').blur(), but "this" is always '#tracker'
{
if(!$(this).is('#tracker:hover')) //for some reason plain old :hover doesn't work, at least on the latest OS X Chrome
hideTracker();
});
But it's not perfect. It only really works if you use the mouse. If you use tab to move between fields (or some other possible mechanism) while your mouse is not hovering over #tracker, it won't work.
Here's another attempt. It's a bit...hackier. The gist is that, instead of handling the blur event, you handle the focus event of the second thing that's focused. But! What if you click something that can't be focused? Blank space on your page? Then no focus event is fired.
Okay. So the trick is: put a tabindex="0" in your root <html> tag. This means that there is always something that can be focused. So there's no way to focus on nothing (at least, I don't think so).
Then you can do this:
$('*').live('focus', function(e)
{
if(!$.contains($('#tracker')[0], this)) //if the new thing you focused on is not a descendant of #tracker
hideTracker();
e.stopPropagation();
});
Eh? So yeah, that's a certified hack. But it's a tough problem, and that's the best I can come up with at this hour.
Thank you all for your answers. Utilising the .focus() event rather than .blur() was a clever way to look at it. Unfortunately, it does raise a couple of browser problems, and I couldn't get any of the above working very robustly.
In the end I decided to use setTimeout(hideTracker, 100); to allow the focus() event to take place before the count of focussed elements within tracker was evaluated. Not ideal, but it's working well and the delay is fairly imperceptible.
Thanks again.
Related
We have a one page app which uses iron pages and express-router to navigate. In the browser and on android it works perfectly, on iOS however, we have a bug. The problem occurs if we switch pages by a button press. If the button is exactly over an input text field element (read: in the same position, but on the next iron-page) the input element gains focus directly after the page switch.
We used to have this problem as well with two buttons in the same position but this was solved by changing all on-clicks to on-taps.
Things we have tried so far:
Adding event.stopPropagation to the on-tap event
Including fastclick.js to prevent click delays (this worked partially when on-clicks were still in place but was made obsolete with on-tap)
Note that we have experienced this problem since Polymer 1.0 all through 1.5.
I reproduced your symptoms on an iPad Air 2, and trying e.stopPropagation(), e.preventDefault(), and returning false all had no effect. I'm not sure whether this is actually a Polymer problem.
I have a couple [hacky] workarounds to employ during the page-switch:
Option 1: Delay the page-change by 400ms. If your button has a ripple effect, the delay is masked by the animation.
codepen
Option 2: Disable the input and re-enable it after a 400ms delay. This prevents the input from picking up the tap event, but has the disadvantage that the disabled state could be noticeable (perhaps a lesser evil than your current problem).
codepen
Thanks #tony19, for the input.
We wanted to avoid delays, so I researched a bit more and ultimately fixed the problem. To answer my own question: the ultimate solution did lie in the FastClick library.
Basically what happens is that the tap event is fired immediately, but it doesn't replace the click event. Rather, the click event still fires, but with the original 300ms delay. This delayed click event thus fires on the newly displayed 'page' and triggers the input-field (or button had it been there) at the same x-y coordinates.
Adding the FastClick library once again solves this thanks to some updates in the library. However, it breaks some items that need the original click, such as Google Autocomplete. A basic solution to exclude FastClick is to instead apply it as:
FastClick.attach(document.body, {
excludeNode: 'something', });
This, however, only works for that node and not possible children. As such, to fix everything for input fields with Google's Autocomplete as well is done using:
// Disable FastClick for the children of a google auto-
// complete component.
var needsClick = FastClick.prototype.needsClick;
FastClick.prototype.needsClick = function(target) {
if ( (target.className || '').indexOf('pac-item') > -1 ) {
return true;
} else if ( (target.parentNode.className || '').indexOf('pac-item') > -1) {
return true;
} else {
return needsClick.apply(this, arguments);
}
};
// Remove click delay on iOS. As such, removing differences
// in timing between click and tap, thereby avoiding the
// fall-through problem.
FastClick.attach(document.body);
I will now close this thread, but I thought it'd be nice to leave this as reference for anyone else experiencing the problem.
Understand that it affected Polymer 1.0 to 1.5. Just to confirm that we experienced the same behaviour in Polymer 1.6 and the following fixes it.
_onTap: function(event) {
event.preventDefault();
event.stopPropagation();
}
I have a webpage that has lots of 'windows', which aren't wrapped in iframes or anything silly. Each 'window' is its own 'App'.
One of these Apps has a ton DOM elements in it. I want to bind a listener to that App, which is encased in a DIV, and want to capture some hotkey presses such as the left and right arrows.
However, if some element that actually rightfully deserves focus (such as an input inside of this app) gets a left or right keypress, I want to ignore it.
Problems:
The above means I only want to capture keypress on DIVs, basically. I could add a tabindex, however the div isn't going to focus itself if someone clicks within that app, so i'm not sure what to do about that:
<div class='app-wrapper' tabindex='1'><!-- behold ye content --></div>
$('.app-wrapper').on('keypress', function(){
// ... I dont think this will work.
});
I am not going to tell every single valid input / textarea in my app to stop propagating on a keypress.
Is there any good solution to achieving the above without a major hack job?
You could look at e.target in your listener function:
$('.app-wrapper').on('keypress', function(e){
if(e.target.tagName === 'DIV'){ //Or, more jQuery-esque: $(e.target).is('div')
console.log("Wooo, I'm totally a div. Let's do stuff");
}
});
In that way you can ignore keypresses fired inside "valid" elements.
Okay, I managed to figure it out.
My first complaint actually did end up working, and then coupled with $(element)[0].nodeName, it handled the latter complaint:
HTML
<div class='app-wrapper'><!-- behold ye content --></div>
JS
$('.app-wrapper').attr('tabindex', 1);
$('.app-wrapper').on('keypress', function(e){
var nodeName = $(e.target)[0].nodeName;
if (nodeName == 'INPUT' || nodeName == 'TEXTAREA') {
// bug off...
return;
}
// handle key press now.
});
http://jsfiddle.net/NsRyr/1/
I am totally stumped on this. Here's what I'm trying to do : I have a div element (call it #keys) that I'm using to handle keypress events.
HTML:
<div id="#keys" tabindex="1">Focus</div>
JS:
$('#keys').keydown(function () {
$('#log').append('*'); // just note that an event happened
});
This works as expected -- as long as #keys is focused, I can receive keypress events and respond to them. In addition, I can set focus to other div elements and the keypress events will no longer be handled by #keys. I can then re-focus the div (e.g., by clicking on it directly, or by responding to a click event on another DOM element) and keypress events are handled as I expect.
So far, so good. The problem that I've come across is that if I focus an input element and then try to re-focus #keys by setting a blur handler that's activated after tabbing away from the input, the #keys div does not receive focus ! It works fine if I blur away from the input by clicking, but not if I use the keyboard.
$('#input').blur(function () {
$('#log').append('blur'); // note that a blur happened
$('#keys').focus();
});
I think the essence of this question is, why doesn't my blur handler on #input seem to work properly when tabbing away from the input (but it does work properly when clicking away) ? Is this just a browser quirk ? (I am using Chrome 30.0.1599.101 on Mac OS.)
This is my first SO question regarding JS, so please let me know what additional detail I can provide to describe the situation.
(Edit : Interestingly, it seems to work fine if I shift-tab away from #input. Still confused what's happening here ; appears to be some sort of tabindex-related issue ?)
I don't have commenting privileges yet, so I'll have to answer, but please mods should change this, because it's basically a duplicate.
Here's the fix to your fiddle:
http://jsfiddle.net/NsRyr/3/
The issue (as described in this answer) is that the blur event fires before the change is done, so the focus needs to be sent down the stack to happen after. Adding a timer deals with the issue.
The line I changed was:
setTimeout($('#keys').focus.bind($('#keys')), 0);
That makes it so it'll wait until the new focus event is completed before firing off the handler.
I hope someone can help me. I know this has been discussed here before but the example is prototype and foreign to me. I'm looking for a strict javascript or jquery solution. I have an example setup here. Click on the scrollbar in FF and you don't get an alert but click on it in IE and you do. Please help me, thanks!
After some searching I came up with this answer. From the best of my knowledge, you cannot actually cancel the blur event, nor can you call the focus event at the same time. This is what I don't get .. you can blur on focus but you cannot focus on blur .. Anyway my solution is use the setTimeout function to call the focus event 1ms after the focus was lost.
var o = this;
oTimeout = setTimeout(function(){
o.focus();
},1);
Using mouseenter and mouseleave events, I set a boolean to refer to on blur event
$("div#box").mouseenter(function(){
changeFocus(1);
}).mouseleave(function(){
changeFocus(0);
});
I've had the same problem and this works for what need it to do. Just force the focus back on the element.
$('#divWithScrollBar').scroll(
function () {
$('#elementThatLosesFocus').focus();
});
That event is somehow triggered after the element is blurred, but before the onblur event is kicked in. Haven't really looked in to it, but that's what seems to be going on.
The scroll does appear a bit slow, but it works.
IE owes me many hours of my life back.
I met one troublesome web page whose structure is complicated. If one DIV is clicked by mouse, everything is OK. However, if it is focus-ed by javascript(i.e. divElement.focus). The layout turns to messy. This only happens in IE7/8.
So, is there any difference between click-to-focus and focus-by-javascript in IE?
Firing a Javascript focus event does not fire a click event. Without seeing the relevant code, I'm led to guess that some click handler is in place that is not being called in the case where you fire a focus event.
You might try, instead, firing a click:
var clickEvent;
if(document.createEvent) {
clickEvent = document.createEvent('click');
clickEvent.initMouseEvent('click');
divElement.dispatchEvent(clickEvent);
} else {
// Semi-pseudocode for IE, not tested, consult documentation if it fails
clickEvent = document.createEventObject();
divElement.fireEvent('onclick');
}
Or if you're into the jQuery thing:
$(divElement).click();
There's similar solutions for Prototype as well (search for Event.simulate).
The definition of the Focus action is to bring the input (keyboard or mouse) to a certain element, usually an input field. When an element gains focus, an OnFocus event is fired. When it loses focus, an OnBlur event is fired.
What you usually get by clicking is the OnClick event, which is not necessarily related to the above two.
This only happens in IE7/8.
Hmm, then I'm sure it's an IE related bug. Not surprising. If there is legitimate Javascript events involved, then they should fire uniformly across all browsers.