Search code examples
jqueryfocustextareakeyboard-eventsevent-delegation

Delegated focus event on textarea not fired on tabbing to the element


I have some textareas with some placeholder text loaded via AJAX. I want the text to disappear on focus, so I delegated the focus event on a class. The focus event is fired if I click the textarea and also fired on clicking/ tabbing to other input elements. But it doesn't work when I tab to the textarea. This behavior is consistent across all browsers. Here is what I'm doing.

$('#forms-container').delegate('.hasInstructions', 'focus', clearInstructions)

hasInstructions is the class on my input elements with place holder text.

When I bound the event to the textarea directly as below it works just fine.

<textarea class="hasInstructions" onfocus="clearInstructions()">foo</textarea>

So my question is, aren't focus events on textareas bubbled up? If so what is the best way I can achieve this?

EDIT: I know it's not really like SO way to change the question after asking it. MikeD's answer is totally valid for the original question and I will accept it if I don't get a better one. So here's the thing,

I'm using jQuery 1.4.2 and I can't use a higher version because I'm not shipping the library to the page. I can't go with HTML5 placeholder for browser compatibility reasons.


Solution

  • You are quite correct that it doesn't work with delegate. This is quite possibly due to the reasons given here but that is just a guess. Here are a few suggested workarounds.

    Without jQuery

    You can make this work using event capturing instead of event bubbling as discussed in this blog entry (For IE support see caveat below).

    With the following HTML...

    <p id='parent'>
        Enter some values:<br/>
        <input type='text' id='requiredValue'/>
        <input type='text'/>
    </p>
    <div id='output'>
    </div>
    

    ...this JavaScript shows the events being caught in the correct locations.

    var outputDiv = document.getElementById('output'),
        parentDiv = document.getElementById('parent'),
        inputDiv = document.getElementById('requiredValue');
    
    parentDiv.addEventListener('focus', function() {
        outputDiv.innerHTML = outputDiv.innerHTML + "<p>parent</p>";
    }, true);
    inputDiv.addEventListener('focus', function() {
        outputDiv.innerHTML = outputDiv.innerHTML + "<p>input</p>";
    }, true);
    

    Making this work with IE

    Event capture doesn't work in IE however in the blog mentioned above we can see that IE supports the focusin and focusout events which do what you want.

    It means that with the addition of two more event handlers then you can handle this scenario in all cases:

    parentDiv.onfocusin = function() {
        outputDiv.innerHTML = outputDiv.innerHTML + "<p>parent</p>";
    };
    inputDiv.onfocusout = function() {
        outputDiv.innerHTML = outputDiv.innerHTML + "<p>input</p>";
    };
    

    Using latest version of jQuery

    This functionality works as expected using the 'on' function.

    .on( events [, selector] [, data], handler(eventObject) )

    Specifying a selector in the function parameters for 'on' means that 'on' uses event delegation. From the documentation:

    When a selector is provided, the event handler is referred to as delegated. The handler is not called when the event occurs directly on the bound element, but only for descendants (inner elements) that match the selector. jQuery bubbles the event from the event target up to the element where the handler is attached (i.e., innermost to outermost element) and runs the handler for any elements along that path matching the selector.

    For the following HTML

    <p id='parent'>
        Enter some values:<br/>
        <input type='text' class='requiredValue'/>
        <input type='text'/>
    </p>
    

    the JavaScript code below works as expected

    $(document).ready(function() {
        var initialText = "Enter a value";
        $('.requiredValue').val(initialText);
    
        $('#parent').on('focus', '.requiredValue', function() {
            var contents = $(this).val();
            if (contents === initialText) {
                $(this).val("");
            }
        });
    
        $('#parent').on('blur', '.requiredValue', function() {
            var contents = $(this).val();
            if (contents.length == 0) {
                $(this).val(initialText);
            }
        });
    });
    

    HTML 5 technique

    A better technique that is HTML 5 compliant can be found here. From the blog:

    <input type="text" name="first_name" placeholder="Your first name...">
    

    You'll notice that all you need to do is add a placeholder attribute with the generic text of your choice. Nice that you don't need JavaScript to create this effect, huh?

    Since placeholder is a new capability, it's important to check for support in the user's browser:

    function hasPlaceholderSupport() {
        var input = document.createElement('input');
        return ('placeholder' in input);
    }
    

    If the user's browser doesn't support the placeholder feature, you will need to employ a fallback with MooTools, Dojo, or the JavaScript toolkit of your choice:

    /* mootools ftw! */
    var firstNameBox = $('first_name'),
    message = firstNameBox.get('placeholder');
    firstNameBox.addEvents({
        focus: function() {
            if(firstNameBox.value == message) { searchBox.value = ''; }
        },
        blur: function() {
            if(firstNameBox.value == '') { searchBox.value = message; }
        }
    });