5

Given the following markup, I want to detect when an editor has lost focus:

<div class="editor">
    <input type="text"/>
    <input type="text"/>
</div>
<div class="editor">
    <input type="text"/>
    <input type="text"/>
</div>
<button>GO</button>

EDIT: As the user tabs through the input elements and as each editor div loses focus (meaning they tabbed outside the div) add the loading class to the div that lost focus.

This bit of jquery is what I expected to work, but it does nothing:

$(".editor")
.blur(function(){
    $(this).addClass("loading");
});

This seems to work, until you add the console log and realize it is triggering on every focusout of the inputs.

$('div.editor input').focus( function() {
    $(this).parent()
        .addClass("focused")
        .focusout(function() {
            console.log('focusout');
            $(this).removeClass("focused")
                   .addClass("loading");        
        });
});

Here is a jsfiddle of my test case that I have been working on. I know I am missing something fundamental here. Can some one enlighten me?

EDIT: After some of the comments below, I have this almost working the way I want it. The problem now is detecting when focus changes to somewhere outside an editor div. Here is my current implementation:

function loadData() {
    console.log('loading data for editor ' + $(this).attr('id'));
    var $editor = $(this).removeClass('loaded')
        .addClass('loading');

    $.post('/echo/json/', {
        delay: 2
    })
        .done(function () {
        $editor.removeClass('loading')
            .addClass('loaded');
    });
}

$('div.editor input').on('focusin', function () {
    console.log('focus changed');
    $editor = $(this).closest('.editor');
    console.log('current editor is ' + $editor.attr('id'));
    if (!$editor.hasClass('focused')) {
        console.log('switched editors');

        $('.editor.focused')
            .removeClass('focused')
            .each(loadData);

        $editor.addClass('focused');
    }
})

A bit more complicated, and using classes for state. I have also added in the next bit of complexity which is to make an async call out when an editor loses focus. Here a my jsfiddle of my current work.

NotMyself
  • 29,209
  • 17
  • 56
  • 74
  • Probably duplicated: http://stackoverflow.com/questions/769135/run-javascript-when-an-element-loses-focus – bitoiu Oct 10 '14 at 16:19
  • 2
    While this question is about detecting focus loss, it is far more specific than the linked question. – NotMyself Oct 10 '14 at 16:21
  • you should use capture instead of bubbling, and prevent propagation on the div, you are getting all the children events I believe, let me try. – bitoiu Oct 10 '14 at 16:24
  • As the user tabs through the input elements, as each editor div loses focus, meaning they tabbed outside the div, add the loading class to the div that lost focus. – NotMyself Oct 10 '14 at 16:24
  • If you use `focusout` instead of `blur`, it works: http://jsfiddle.net/MelanciaUK/8s8ayv52/8/ – emerson.marini Oct 10 '14 at 16:25
  • @MelanciaUK sadly, that triggers on focusout of the inputs. Check the console out put of this one. http://jsfiddle.net/8s8ayv52/10/ – NotMyself Oct 10 '14 at 16:27
  • 1
    Yes, because like the documentation says: _The focusout event is sent to an element when it, or any element inside of it, loses focus. This is distinct from the blur event in that it supports detecting the loss of focus on descendant elements (in other words, it supports event bubbling)._ – emerson.marini Oct 10 '14 at 16:29
  • 1
    And you can't use `blur` because a `div` is not an element that would be part of a `form`. – emerson.marini Oct 10 '14 at 16:30
  • @MelanciaUK yes I read that documentation as well. My question is how do I get the desired effect of having my class added when the div is no longer in focus. – NotMyself Oct 10 '14 at 16:30
  • possible duplicate of [jquery need alternative to focusout()](http://stackoverflow.com/questions/3088738/jquery-need-alternative-to-focusout) – emerson.marini Oct 10 '14 at 16:30
  • You may find a solution in the question above. – emerson.marini Oct 10 '14 at 16:31
  • You can try something like [this fiddle](http://jsfiddle.net/8s8ayv52/11/). – Regent Oct 10 '14 at 16:32
  • Using a bit of more markup: http://jsfiddle.net/8s8ayv52/12/ – bitoiu Oct 10 '14 at 16:36
  • Is it your intention for each pair of fields to act as a single field and only want to know when focus leaves the pair of fields? If so I just had to solve this for a custom date picker (with 3 fields). Let me know if that is the problem and I will dig out some code. – iCollect.it Ltd Oct 10 '14 at 16:51
  • @TrueBlueAussie my intention is for each "editor" div to act like a control, when you leave the editor to anywhere else on the page do some thing... in this example add the loading class. – NotMyself Oct 10 '14 at 17:47
  • You need to have a condition inside the blur listener that checks if input with focus has different parent as item that lost focus. Or just check if current focused input and blurred input are siblings. – Anthony Oct 11 '14 at 01:17
  • @Anthony: That is most of the correct approach, however the input *gaining* focus is not available until *after* the blur completes (hence the solution requiring a `setTimeout` to wait for the focus to complete). – iCollect.it Ltd Oct 11 '14 at 23:26

3 Answers3

6

If you wish to treat entry and exit of the pairs of inputs as if they were combined into a single control, you need to see if the element gaining focus is in the same editor. You can do this be delaying the check by one cycle using a setTimeout of 0 (which waits until all current tasks have completed).

$('div.editor input').focusout(function () {
    var $editor = $(this).closest('.editor');
    // wait for the new element to be focused
    setTimeout(function () {
        // See if the new focused element is in the editor
        if ($.contains($editor[0], document.activeElement)) {
            $editor.addClass("focused").removeClass("loading");
        }
        else
        {
            $editor.removeClass("focused").addClass("loading");
        }
    }, 1);
});

JSFiddle: http://jsfiddle.net/TrueBlueAussie/8s8ayv52/18/

To complete the puzzle (get your initial green state) you will also need to also catch the focusin event and see if it is coming from the same editor or not (save the previous focused element in a global etc).

Side note: I recently had to write a jQuery plugin that did all this for groups of elements. It generates custom groupfocus and groupblur events to make the rest of the code easier to work with.

Update 1: http://jsfiddle.net/TrueBlueAussie/0y2dvxpf/4/

Based on your new example, you can catch the focusin repeatedly without damage, so tracking the previous focus is not necessary after all. Using my previous setTimeout example resolves the problem you have with clicking outside the divs.

$('div.editor input').focusin(function(){
    var $editor = $(this).closest('.editor');
    $editor.addClass("focused").removeClass("loading");

}).focusout(function () {
    var $editor = $(this).closest('.editor');
    // wait for the new element to be focused
    setTimeout(function () {
        // See if the new focused element is in the editor
        if (!$.contains($editor[0], document.activeElement)) {
            $editor.removeClass("focused").each(loadData);
        }
    }, 0);
});
iCollect.it Ltd
  • 92,391
  • 25
  • 181
  • 202
  • huh this is closer to what I was going for. Let me mess with it some and see if I can make it workable. Thanks. – NotMyself Oct 10 '14 at 17:50
  • Ok I am getting closer to what I am after here. This is my latest code and it works as expected until you try to click somewhere outside of the editor divs. http://jsfiddle.net/0y2dvxpf/​ – NotMyself Oct 11 '14 at 01:05
  • Very nice solution. Enjoy your accepted correct answer. And thank you for your help. – NotMyself Oct 13 '14 at 15:37
2

Here's what worked for me:

$(".editor").on("focusout", function() {
    var $this = $(this);
    setTimeout(function() {
        $this.toggleClass("loading", !($this.find(":focus").length));        
    }, 0);
});

Example:

http://jsfiddle.net/Meligy/Lxm6720k/

Meligy
  • 35,654
  • 11
  • 85
  • 109
-1

I think you can do this. this is an exemple I did. Check it out: http://jsfiddle.net/igoralves1/j9soL21x/

$( "#divTest" ).focusout(function() {
alert("focusout");
});
IgorAlves
  • 5,086
  • 10
  • 52
  • 83
  • well, the question is clear: "How do I detect when a div has lost focus?" It is what I did. – IgorAlves Dec 03 '14 at 16:44
  • Just tried your JSFiddle. I see the problem. They want to know when you leave the div completely. This version reports it has left *even if you click on another input in the same div*. That is not what they wanted. You may want to rework this answer to match the requirements :) – iCollect.it Ltd Oct 13 '15 at 09:18