RSS
 

jQuery Placeholder Fallback

01 Aug

One of the handy new features for <input> elements in HTML5 is the “placeholder” attribute. If you’re unfamiliar with this attribute, it allows you to pre-populate a text input with instructions which disappear when the element receives focus or there is any entry in the element.

For example, if you had a field prompting a user to enter their username, the placeholder text might be “Enter your username here.” With the introduction and support of the “placeholder” attribute, all that would be required is the use of an input tag like <input type=”text” placeholder=”Enter your username here.”/> and the browser will automatically display your text whenever there is no entry in the field and it does not have the keyboard focus.

The only problem is that older web browsers (and even those that are not-so-old, such as IE8) lack support for placeholders, so a standalone solution is required. After contemplating a couple of different fallback implementations, I decided that for my requirements, an extremely simple implementation would be best.

Other people have designed jQuery plugins mainly in one of two ways: 1) use the input’s own value to display the placeholder text when there is no user entry, and 2) float the placeholder text in front of the input.

Option 1 seems like the most obvious solution, but after brief contemplation, it is obviously a very sloppy one considering that overriding the input value means you’d have to design special handling to carry the true user entry. It also mucks things up that the input might be of type “password” which means you can’t just set the placeholder text as its value, since it will just show up obscured. Furthermore, if the input has a “maxlength” attribute that is shorter than the length of your placeholder text, you’d have to work around that as well. And as far as styling goes, if you wanted your placeholder text to appear differently than the input text, you’d need to also keep track of the styles of both. Clearly, a complete solution this isn’t.

Option 2 seems to be the much better implementation. Unfortunately, most all such solutions I’ve been able to find out there seem to be poorly designed, overlooking several key requirements for half-decent functionality. For example, they ignore the fact that the browser window might be resized (in which case the placeholder needs to move with the input) or the input might become hidden (in which case the placeholder should also be hidden).

And finally, here is the solution I came up with. You should be able to just drop it into your own page as-is, or minify it first if you’d like. (It’s a bit bloated here for readability and documentation.)

<script type="text/javascript">
$(document).ready(function(){// wait until the DOM is ready
  $(function(){// encapsulate our variables into their own scope
    // test if HTML5 placeholder is supported; if so, quit
    var el = document.createElement('input');
    if ('placeholder' in el)
      return;

    // traverse all inputs with a placeholder attribute
    $("input[placeholder],textarea[placeholder]").each(function(){
      var $ip = $(this),// the input itself (in a jQuery object)
          tx = $ip.attr('placeholder');// placeholder text

      // create a label and a span within it
      var $lb = $(''),
          $sp = $(''+tx+'').appendTo($lb);

      // the label surrounds the input
      $lb.insertBefore($ip).append($ip);

      // try getting the input's size
      var w = $ip.width(),
          h = $ip.height();

      // if it fails, it's likely because the input is not visible
      if (!w || !h) {
        // clone the input and make it have size/shape without showing it
        var $clone = $ip.clone().appendTo('body').css({
          position : 'absolute',
          visibility : 'hidden',
          display : 'block'
        });

        // fetch the correct size (hopefully)
        w = $clone.width();
        h = $clone.height();

        $clone.remove();
      }

      // copy the position, size, and font into the
      // placeholder text's span
      $sp.css({
        position : 'absolute',
        display : 'block',
        width : w+'px',
        height : h+'px',
        lineHeight : $ip.css('line-height'),
        paddingLeft : $ip.css('padding-left'),
        paddingTop : $ip.css('padding-top'),
        marginLeft : $ip.css('margin-left'),
        marginTop : $ip.css('margin-top'),
        fontSize : $ip.css('font-size'),
        fontWeight : $ip.css('font-weight'),
        overflow : 'hidden',
        cursor : 'text'
      });

      // in MSIE 7 the text is vertically misaligned
      if ($.browser.msie && parseInt($.browser.version) <= 7)
        $sp.css({marginTop:parseInt($sp.css('margin-top'))+2+'px'});

      // if the input is hidden or shown, so should the placeholder be
      $ip.bind('hide', function(){ $sp.hide(); });
      // when showing, ensure the text is the right size
      $ip.bind('show', function(){
        $sp.show().css({
          width:$ip.width()+'px',
          height:$ip.height()+'px'
        });
      });

      // if the input is starting out hidden or there is a default value in
      // the input already, hide the placeholder
      if (!$ip.is(':visible') || $ip.val().length)
        $sp.hide();

      // when input gets focus, hide the placeholder and
      // when we leave the input, if it has no user entry
      // show the placeholder
      $ip.focus(function(){ $sp.hide(); });
      $ip.blur(function(){ if ($(this).val() == '') $sp.show(); });

      // if the placeholder is focused, send focus to the input
      $sp.focus(function(){ $ip.focus(); });
    });

    // override jQuery.hide() to trigger the 'hide' event
    var hide = $.fn.hide;
    $.fn.hide = function() {
      $(this).trigger('hide');
      return hide.apply(this, arguments);
    };

    // override jQuery.show() to trigger the 'show' event
    var show = $.fn.show;
    $.fn.show = function() {
      $(this).trigger('show');
      return show.apply(this, arguments);
    };
  });
});
</script>

To style the placeholder text, just use CSS selector .placeholder. Keep in mind that if you are specifying any style attributes that have been assigned in the JS above, you must include !important in your CSS for the style to take effect.

.placeholder { color: #bbb; margin-top: 5px !important; }
/* you should use it alongside, and identically to, your style rules for HTML5
placeholder text, which as of this writing can be done in Mozilla and Webkit
browsers as follows: */
::-webkit-input-placeholder { color: #bbb; margin-top: 5px !important; }
/* single-colon for older Mozilla */
:-moz-placeholder { color: #bbb; margin-top: 5px !important; }
/* double-colon for newer Mozilla */
::-moz-placeholder { color: #bbb; margin-top: 5px !important; }


Three notes about my implementation:

1) I could obviously make it into a plugin, but for my purposes, I’m just using it as-is.

2) I opted to override hide() and show() in jQuery. This might not be preferable and could possibly conflict with any other overrides of those same functions that might be present in your scripts. If this is an issue, you can just remove them.

3) The issue with some inputs is they may start out hidden when the page is loaded. If so, the width/height will not be known. To help mitigate this issue, we clone the input and make it take shape so we can determine its size. This helps, but it can still be inaccurate when the size of the input depends on CSS specific to its hierarchy (because we’re appending the clone to “body”, not to the input’s parent, who might also be non-visible due to some ancestor).

For example, if the CSS for your input is something like:

#some-panel { display: none; }
#some-panel .some-box input { width: 500px; }

Then we’ll clone the input, append the clone to “body”, and apply its width to the placeholder text. But since the CSS width depends on the input being a descendant of #some-panel and then .some-box, the width of our clone will very certainly not be the 500px we’re after.

The complete solution is: when showing the container of the input, always explicitly call something like $("#some-panel .some-box input[placeholder]").show() to trigger our input’s show() event, which triggers its placeholder’s show() event, which in turn adjusts the text to match the input’s size. It’s not elegant, but it works.

As always, if you encounter any issues or have any questions. Please comment below.

 

92,187 views

Tags: , , , , ,

Leave a Reply

 

 
  1. Niceoutput

    September 30, 2011 at 2:59 pm

    This solution unfortunately causes extra vertical space in the html document in all ie versions. works well in ff, safari, chrome and opera, but not in ie.

     
  2. rommel

    September 30, 2011 at 3:07 pm

    Do you happen to have an example of this in action? I’ve tested in IE7, IE8, and IE9, but have not been able to replicate the issue you described.

    Thanks for the feedback.

     
  3. Paul

    February 24, 2012 at 5:30 am

    I’m using this in a .net application. on postback the input already has text in it, but your label still shows too. how can I adjust your above code to first test to see if the input has a value, and if so, don’t show the placeholder label?

     
  4. Paul

    February 24, 2012 at 5:46 am

    sorry, already figured it out. for anyone else interested:

    add:

    if ($ip.val() != “”) { $sp.hide(); }

    right before the line with comment:
    // if the input is starting out hidden, hide the placeholder

     
  5. rommel

    February 24, 2012 at 6:05 am

    Hi Paul- Thanks for pointing out this issue. I’m kind of surprised I overlooked that obvious requirement. I’ve added a fix to the code above. Thanks!