2

Using jQuery 1.6.1, given I have the following HTML:

<div class="control">
    <label>My Control</label>
    <input type="text" />
    <input type="text" />
</div>

When an <input> in <div class="control"> (hereafter only control) is focused, the <label> (with position: relative;) animates:

$('.control :input').bind('focus', function(e){
    $(this).prevAll('label').animate({
        'left': '-50px'
    }, 250);
});

And when blurred, the <label> returns:

.bind('blur', function(e){
    $(this).prevAll('label').animate({
        'left': '0px'
    }, 250);
});

However, if one of the <input> elements gains focus, and then blurs as focus is switched to another <input> within the same control (via Tab or mouse click) the events of course still fire, and the <label> animates back and forth.

How can I force the blur event to trigger only when focus is lost from all inputs within a given control?

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Dan Lugg
  • 20,192
  • 19
  • 110
  • 174

5 Answers5

1

Edit: Updated the answer based on more inputs provided by the OP.

If a slight delay of one second to hide the label is fine then you can use setTimeout/clearTimeout combination.. Something like:

<div class="control">
    <label>My Control</label>
    <input type="text" />
    <input type="text" />
    <input type="text" />
</div>
<div class="control">
    <label>My Control</label>
    <input type="text" />
    <input type="text" />
    <input type="text" />
</div>

<div class="control">
    <label>My Control</label>
    <input type="text" />
    <input type="text" />
    <input type="text" />
</div>

<div class="control">
    <label>My Control</label>
    <input type="text" />
    <input type="text" />
    <input type="text" />
</div>

    <script type="text/javascript"> 
        var timeoutIds = [];
            $('.control').each(function(index, el){
                $(':input', el).bind('focus', function(e){     
                        clearTimeout(timeoutIds[index]);
                        $(this).prevAll('label').animate({        
                                'left': '-50px'     
                        }); 
                });

                $(':input', el).bind('blur', function(e){     
                        var that = this;
                        timeoutIds[index] = setTimeout(function(){
                            $(that).prevAll('label').animate({        
                                    'left': '0px' 
                            }); 
                    }, 500);
                });
            });
    </script>

Working example: http://jsfiddle.net/Tn9sV/2/

Chandu
  • 81,493
  • 19
  • 133
  • 134
  • Thanks @Cybernate - Almost works like a charm. Perhaps it's my fault, as I failed to mention there will be more than one `control`. When switching from `control` to `control`, the blur fails to trigger on the first because of the `clearTimeout()` called on the second. – Dan Lugg Jul 21 '11 at 21:53
  • @Tomcat: But the blur is first triggered and then the focus on the second. – Chandu Jul 21 '11 at 21:55
  • Thanks, but no @Cybernate, sorry it doesn't. This is what I meant http://jsfiddle.net/Tn9sV/1/ Any idea? – Dan Lugg Jul 21 '11 at 22:06
  • Working off your answer directly, I'd have to assign (*and manage*) an array of timeoutIds, perhaps assigning them to the given `control` elements as arbitrary `data()`, but that seems like an awful lot of overhead for something misleadingly simple. – Dan Lugg Jul 21 '11 at 22:10
  • @TomcatExodus: I see what you mean by multiple controls. Let me see if there is any simple alternative – Chandu Jul 21 '11 at 22:12
  • @TomcatExodus let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/1714/discussion-between-cybernate-and-tomcatexodus) – Chandu Jul 21 '11 at 22:14
  • Awesome @Cybernate - That works spectacularly. Thanks so much again for your persistence; very much appreciated. – Dan Lugg Jul 22 '11 at 00:41
1

In your blur callback I would look at something like the :focus selector that was introduced in 1.6:

Using jQuery to test if an input has focus

If $('.control :focus).length > 0 then return the function to stop it from running.

Community
  • 1
  • 1
Alex Mcp
  • 19,037
  • 12
  • 60
  • 93
  • Thanks @Alex Mcp - Unfortunately, it appears (*in my cursory testing*) that the blur event fires immediately prior to the next focus event, even when switching immediately from `input` to `input`; it always evaluates to 0. – Dan Lugg Jul 21 '11 at 21:44
  • Aaah, interesting. I think a slight delay would be in order then, according to the other answers. Give the focus a chance to register, then evaluate if there is a :focused element or not. – Alex Mcp Jul 21 '11 at 22:00
  • Perhaps, I'm hoping the answer @Cybernate proposed will get me there. – Dan Lugg Jul 21 '11 at 22:13
1

I have achieved something similar in the past by binding a new focus to the document or to the form instead and having that trigger the label to come back, instead of binding a blur to the inputs.

citizen conn
  • 15,300
  • 3
  • 58
  • 80
  • Thanks @citizen conn - This seems like a viable solution, however I can't seem to bind a `focus` event to `body`, `document`, `window`, etc. What element specifically did you attach the event handler to? – Dan Lugg Jul 21 '11 at 22:00
  • @TomcatExodus Sorry, bind a mousedown event, not focus – citizen conn Jul 21 '11 at 22:02
  • Thanks @citizen conn - Problem is though, if I `Tab` out of the control group, it won't trigger. – Dan Lugg Jul 21 '11 at 22:08
  • @TomcatExodus GP... then you could bind focus events to the following and previous fields. the plot thickens, I know, but otherwise it might call for more insane js instead of just jquery event handling. – citizen conn Jul 21 '11 at 22:12
1

In blur callback you can detect if any input element is focused now using this $("input:focus").length. If length>0 than do not animation

Update

What about this code?

    var control;
    var inTheSameControl=false;
    $('.control :input').bind('focus', function(e){
        if(control)
        {
            if(control.find(this).length>0)
                inTheSameControl=true;
            else
                inTheSameControl=false;
        }
        control=$(this).parent();
        if(!inTheSameControl)
        {
            console.log('focus');
        }
    });
    $('.control :input').bind('blur', function(e){
        if(!inTheSameControl)
        {
            console.log('blur');
        }
    });

It works with multiple div.control When you are switching focus to the input in another .contor, text 'focus' logs into console, if you are staying in the same .control - doesn't. Instead of console.log(...) you may write what you want. I haven't written your code(animation) because it is not the subject.

I hope it will be helpfull.

Ruslan Polutsygan
  • 4,401
  • 2
  • 26
  • 37
0

Two options I can think of:

  • Option 1 (using current scheme):

    • blur: .delay your animation by a fraction of a second

    • focus: .stop your existing animation

  • Option 2 (change blur item):

    • chage the blur item to be on your div instead of the label
rkw
  • 7,287
  • 4
  • 26
  • 39