JavaScript FocusOut - get the element that receives focus
Asked Answered
J

5

16

When the FocusOut event is raised, how do you know which element receives the focus?

The correct way seems to be to use the event's relatedTarget property. However, this seems not to work in all browsers:

  • in Google Chrome, it works
  • in Firefox and Internet Explorer, the relatedTarget is null
  • in Safari, the relatedTarget property doesn't even exist

I have found a workaround which works only in IE (using document.activeElement), but I'm wondering if there isn't a general solution that has proven to work in all major browsers.

Although I can find similar questions and answers, I haven't found any solution which really works in all browsers.

EDIT: the example below shows what I mean.

Code:

document.getElementById('text1').addEventListener('focusout', function(e) {
  // At this point, I want to know which element is receiving the focus (i.e. text2)
  console.log(e.relatedTarget); // works in Chrome
  console.log(document.activeElement); // works in IE
  // both do not work in Firefox or Safari
});
<input id="text1" type="text" value="first" />
<input id="text2" type="text" value="second" />
Jezebel answered 5/4, 2014 at 10:22 Comment(0)
D
1

As you hinted at, FocusEvents come with a relatedTarget property which, for focusout events, points at the element that is about to receive focus (at the next tick of the event loop). As of 2024, this property seems to be supported by all major browsers, see: https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent/relatedTarget

Distant answered 24/6 at 15:1 Comment(0)
S
8

I have a hypothesis and workaround for firefox. document.activeElement seems to work. Then focusout hits, so it gets removed. By the time focusin hits (or maybe immediately after) there will be a focused element again. But between out and in there is nothing focused, so no element is being reported as active.

My workaround is a stupid setTimeout hack. setTimeout( function() {console.log(document.activeElement)}, 1); reliably gets me an active element. Granted I've only tested in one machine and spent all of 90 seconds doing so, but it's the best I've found so far.

Snipe answered 19/5, 2014 at 20:59 Comment(2)
Yeah, it is kind of a trick... But it seems to work in all major browsers!Jezebel
As far as I can tell, this will only work if the element loses focus to another element that can receive keystrokes (e.g. input, textarea). For anything else, document.activeElement == document.body. developer.mozilla.org/en-US/docs/Web/API/document.activeElementNewsmonger
D
1

As you hinted at, FocusEvents come with a relatedTarget property which, for focusout events, points at the element that is about to receive focus (at the next tick of the event loop). As of 2024, this property seems to be supported by all major browsers, see: https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent/relatedTarget

Distant answered 24/6 at 15:1 Comment(0)
T
0

I believe what you're looking for is document.activeElement.

Returns the currently focused element, that is, the element that will get keystroke events if the user types any. This attribute is read only.

https://developer.mozilla.org/en-US/docs/Web/API/document.activeElement

Theisen answered 5/4, 2014 at 10:42 Comment(7)
That would seem logical, but within the FocusOut event handler, this returns the Body element in some browsers (even when the actual focus switches from one input element to the next).Jezebel
Also, I just noticed that you've already tried document.activeElement. Completely missed that.Theisen
Well, not really. My specific situation is that I want to know when a DIV containing other elements loses its focus. I'd like to use the DIV's FocusOut event and check whether the element receiving the focus is another element within this DIV. But apart from the specific problem, I'm just curious if there isn't a cross-browser solution for such a simple problem.Jezebel
Ah... then maybe a fiddle with an example would help.Theisen
On that note, for what its worth, this fiddle works consistently in Chrome, Firefox, Safari, IE, and Opera.Theisen
See this example: link Just click on the first textbox and press Tab. Both solutions don't work in Firefox or Safari.Jezebel
@PeterM. Your fiddle (the e.relatedTarget part) seems to work for me in Firefox 126.0.2. Looking at MDN, it seems relatedTarget has been supported by all major browsers for a long time.Distant
N
0
//attach a focus out handler
$("#id").focusOut($El_FocusOut);

//display the id of new element being focused onto
function $El_FocusOut($eventArgs){
    //firefox specific focusout active element logi
    var activeElement = $eventArgs.originalEvent.explicitOriginalTarget;
    alert($(activeElement).attr("id"))
}
Nygaard answered 2/9, 2015 at 20:47 Comment(0)
B
0

I have encounter the same problem using textEditor PrimeFaces element.

I use the following html code.

<div contenteditable="true" class="div-focus"> 
    <h:outputText
        styleClass="OutputField" 
        value="#{oController.panelTpl.getComment()}"  
        escape="false"
        />
</div>    
<div class="text-editor"> 
    <p:textEditor 
       value="#{oController.panelTpl.txtComment.text}"
       />
</div>    

The TextArea is defined twice so that when form is displayed, the user see only the first <div> widget without seeing a specific toolbar.

When user click on text displayed, the focusin() event hide the <div class="div-focus"> element and display the <p:textEditor> widget contained in <div class="text-editor"> parent element.

To do that, I have defined the following javascript code

function onLoadDialog()
    {
    jQuery(".text-editor").hide();

    jQuery(".div-focus").focusin(function()
        {
        $(this).hide();
        $(this).next(".text-editor").show();
        $(this).next(".text-editor").focus();
        });            

    jQuery(".text-editor").focusout(function(e)
        {
        if ($(e.relatedTarget).closest(".text-editor").size() == 0)
            {
            $(this).hide();
            $(this).prev(".div-focus").show();
            $(this).prev(".div-focus").text($(this).text());
            }
        });            
    }

The onLoadDialog function() is called when page is loaded and is used to hide <div class="text-editor> element and to defined focusin() and focusout() events.

The focusin() event hide <div class="div-focus"> element and display <div class="text-editor"> element. When the user clicks on text in <div class="div-focus"> element, this element is hidden and the following hidden element is display. The <div class="text-editor"> element gains focus.

The focusout() event hide <div class="text-editor"> element and display <div class="div-focus"> element ... only when element that gains focus is not defines in <div class="text-editor"> element.

The problem with element.focusout() is that it is triggered each time an element included in main element even if element that gains focus is defined in same element.

When you enter in a house, you can enter passing by garage or living room or kitchen. When you enter in living room (by external door), you enter in the building. But when you quit living room to kitchen, you stay in house !!! If you quit living room to go in garden, you quit the house (building).

focusin() function implement the same principe, but not focusout() !

if ($(e.relatedTarget).closest(".text-editor").size() == 0) line used in focusout() event correct this little difference !

CAUTION: the solution is not perfect because I have some little problems to solve when copying text from textEditor widget to outputText widget without formatting !

Bertrambertrand answered 11/7, 2017 at 9:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.