0

I am trying to detect when a text input field is active. I had originally implemented using onfocus, however, when the window is no longer the foreground window, the onblur event is triggered. This has unintended consequences in my application. There id the DOMActivate event which is fired, but in firefox this is NOT fired on text:input fields.

Only one element can be active at a time in a document. An active element does not necessarily have focus, but an element with focus is always the active element in a document. For example, an active element within a window that is not the foreground window has no focus.

Fires: button, input:button, input:checkbox, input:file, input:image, input:password, input:radio, input:reset, input:submit

Does not fire: input:text, select, textarea

I have also tested using the input event. However, this event is only fired if you actually type in the box. For my application, I need to capture when the text input field is active before anything it typed and it cannot change status when when then window is not in foreground.

I think there may be a way to do this with a mutation observer and then checking if that mutated element has activeElement, but that seems like it will need more overhead. I do not think there is an event for lostActiveElement, so that would also add some extra tracking. Is there a more direct / efficient way to do this?

This is in a firefox add-on using addon sdk.

Eric G
  • 907
  • 1
  • 9
  • 30
  • You need to listen for the window's `blur` event (or whatever it may be to know when the window is no longer in the foreground). In addition, handling logic with the textbox's `focus` and `blur` should give you everything you need. For example, if the textbox is focused, then the window is sent to the background, the window's `blur` event and the textbox's `blur` event would fire. But you would just ignore it because the window's `blur` event occurred (meaning the textbox wasn't literally blurred). – Ian Aug 24 '14 at 23:27
  • Sounds like a possibility, but I need to unset when when the item has lost "active". I follow the logic, but it requires coordinating multiple events. – Eric G Aug 24 '14 at 23:39
  • Right, so you would only unset when the window wasn't blurred, but the textbox has been. Unfortunately, you're right, and I'm pretty sure it's the only way unless you tapped into watching the `activeElement` – Ian Aug 24 '14 at 23:53
  • in the top of your blur event: if(e.target!=this||e.target!=document.activeElement){return;} – dandavis Aug 25 '14 at 00:23
  • @dandavis this does not quite do what I need. When I run this in my blur handler for the text field, it does prevent the blur event from triggering if I move the window to the background, but the blur event never fires when I click elsewhere, - like if I click on just the background of the page, and I am no longer active inside of the field, it should still blur. – Eric G Aug 25 '14 at 01:53
  • I also tried testing for document.hasFocus() [https://developer.mozilla.org/en-US/docs/Web/API/document.hasFocus] in my text field's blur event, but it still returns true even if the window is in the background. window.hasfocus gives me a "not a function error" - which seems true from http://stackoverflow.com/questions/5363803/javascript-document-window-has-focus – Eric G Aug 25 '14 at 02:05
  • @EricG: sounds like it's partially working. in your input blur event, i see two possible solutions for the last part. one would be to poll activeElement and if it's not the input or window, fire the rest of the blur(). the other would be to subscribe to a self-unsubscribing window.focus() event that calls the rest of the blur() if needed. – dandavis Aug 25 '14 at 02:29
  • @dandavis can you provide some more info on self-unsubscribing, I am not familiar with this concept. – Eric G Aug 25 '14 at 21:23
  • you would just call removeEventListener("eventName", arguments.callee) inside the handler. – dandavis Aug 25 '14 at 23:43

2 Answers2

1

I have gotten useable results using the mutationobsever as follows:

var activeInputObserver = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
        if(document.activeElement.tagName === "INPUT"){     
            // do stuff based on this being the active input                
        }else{  
            // the else event is being used like a lost focus event now
            // however, it now makes it global as opposed to per input  
        }       
    });    
});

var activeInputConfig = { attributes: true, childList: true, characterData: true, subtree: true }
var activeInputTarget = document.querySelector('body'); 

if(target != null){ 
    activeInputObserver.observe(activeInputTarget, activeInputConfig);
}

I am using a mutation observer on the body and monitor all descendants using subtree: true. This is not ideal because now anytime an element gets active it will run all my logic, as opposed to only on the input fields. It will requires making some variable and tracking it instead of just relying on a per element event. There is no way to detect that the element no longe has active this way. This kind of works, but is not really ideal.

I tried to put and mutation observer on each input field, but becoming the active element does not trigger the mutation:

var activeInputObservers = [];
inputFields = document.querySelectorAll("input");
for each (var inputField in inputFields){

    var activeInputConfig = { attributes: true, childList: true, characterData: true, subtree: true }
    var activeInputTarget = inputField;

    var thisObserver = new MutationObserver(function(mutations) {
    activeInputObservers.push(thisObserver);
        mutations.forEach(function(mutation) {
            console.log("active element:" + JSON.stringify(document.activeElement)   );         
        });    
    });

    thisObserver.observe(activeInputTarget, activeInputConfig);     
}

With the above code, simply clicking into the element or tabbing into it does not trigger an observable change (typing in the field does).

The activeElement is a property of document, so you would need to monitor document for changes; again, you are still not able to capture and event in a specific element, so you need to run an action each time anytime the active element changes. It does not look like anything on the input field itself is changed.

Eric G
  • 907
  • 1
  • 9
  • 30
0

Following some of the advice from @dandavis and playing around, I found that the document.activeElement will not unset when the window is not in focus. If I set a condition in the input fields blur event, it will not bubble up to the window. I am concerned that this could break some other script which the window to receive a blur when the page is in the background, however, for the purposes of my addon, this prevents the blur action from happening.

inputFields = document.querySelectorAll("input");
for each (var inputField in inputFields){

    inputField.onfocus = function(){
        //do something when the input field get's focus
    }   

    inputField.onblur = function(e){

        inputStillActive = false;
        if(this === document.activeElement){ //this will be the input field element
            inputStillActive = true;
            return; //or do something else knowing that inputStillActive will be true 
        }

    }

}

Instead of returning early, it should be possible to just take some action. If you only do the else or the not equal you should be able to consider that "really lost focus" and not "still active but the window lost focus".

In my case, if I have a input element active, I want to capture the values for use with KeePass autotype, so I add the current input fields name and id to the window title, so a simplified version is something like this:

var activeInputAttributes = {};

inputFields = document.querySelectorAll("input");
for each (var inputField in inputFields){

    inputField.onfocus = function(){

        activeInputAttributes["name"] = this.getAttribute("name");
        activeInputAttributes["id"] = this.getAttribute("id");

        console.log(activeInputAttributes);

    }   

    inputField.onblur = function(e){

        inputStillActive = false;
        if(this !== document.activeElement){
            activeInputAttributes["name"] = null;
            activeInputAttributes["id"] = null; 
        }

        console.log(activeInputAttributes);

    }

}

The activeInputAttributes only print to null when I actually leave the input field, and are not null when I just put the window in the background, which means the values are still available for auto type. I think this is more ideal then observing as in my original answer.

Eric G
  • 907
  • 1
  • 9
  • 30