Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I detect when a div has lost focus?

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.

like image 942
NotMyself Avatar asked Oct 10 '14 16:10

NotMyself


1 Answers

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);
});
like image 122
Gone Coding Avatar answered Nov 15 '22 11:11

Gone Coding