Monday, February 20, 2012

inputWrapper: default text and masks

I'll start with my usual disclaimer: this blog is meant to be notes to my future self along with stuff other developers might find useful, with code snippets that suffer from a heap of my inexperience and the usual "Get It Done!"/"Worse is Better" rush of my employer, and so isn't always representative of js and jquery best practices. (That said, the stuff we come up with is well-QA'd and I always aim to make robust and readable code, so there is that.)

A common UI trick is to have a text entry field that has a prompt in the body of the input itself that goes away when the box is clicked on, and returns if the box is left without text. (A UX note is in many cases you don't want the box to be otherwise unlabled (no matter how slick your designer is trying to make the form) since that label goes away when text is entered, and depending on the context, the user might not know what the data he or she entered means.)

A while back we experimented with different techniques for making this effect, and came up with the following. It's a function that you call as follows:
"Prompt To Put There","some_unique_identifier");

It's a little wonky... possibly it should be wrapped in as a proper chainable jquery function, and having the developer pick a unique identifier is a bit odd.

What the code does is make a sibling span that actually covers the input box. The js for that is

function makeInputDefault(sel, def, classForSpan){
wrapperIdCounter = wrapperIdCounter + 1;

var inputWrapperID = "inputWrapperId_" + wrapperIdCounter;
$(sel).wrap("<div class='inputWrapper' id='" + inputWrapperID + "'>");
$(sel).parent("div").append('<span class="unpicked" onclick="inputDefault_spanClick(this);" style="display: inline;">'+def+'</span>');

if(classForSpan != undefined){

if($(sel).val() != ""){
setTimeout(function() { inputDefault_inputLoad("#" + inputWrapperID) }, 500);

function inputDefault_spanClick(obj) {
var currentSpan = $(obj);
var currentInput = currentSpan.parent().find("input");
function inputDefault_inputFocus(obj) {
var currentInput = $(obj);
var currentSpan = currentInput.parent().find("span");
function inputDefault_inputBlur(sel) {
var currentInput = $(sel);
var currentSpan = currentInput.parent().find("span");
if(currentInput.val() == "") {

//this next function makes sure we initialize to the right state,
//in terms of showing the overlay or not...
function inputDefault_inputLoad(obj) {
var currentWrapper = $(obj);
var currentInput = currentWrapper.find("input");
var currentSpan = currentWrapper.find("span.unpicked");
if(currentInput.val() == "") {;
} else {
(I think the timeout is a hack to cope with browsers auto-filling the field after the sibling tag had been rendered...)

This goes with a jot of CSS:

.inputWrapper {
    padding: 2px;
    position: relative;
.inputWrapper input, select {
    border: 1px solid #D7D7D7;
    color: #666666;
    font-size: 16px;
    width: 150px;

.inputWrapper span{
position: absolute;

.inputWrapper .picked {

.inputWrapper .unpicked {

So that seems to work ok. But why is it so complex? this dailycoding article outlines a method that is much more straightforward, and keeps the default text with the HTML. Well, I found two reasons... one is we are also using this masked Input plugin to autofill the field with hyphens, and it kind of short circuits the simpler prompt method. Another is (and this is a guess) that the dailycoding technique means you have to check if the current input value is equal to the prompt, and ignore it accordingly, when the form data is actually used.

I guess my overall suggestion is to use the dailycoding technique first (nice site btw, I just subscribed to its feed) and then use something like this span technique if the straightforward way isn't up to par.


  1. Are you aware of HTML5's 'placeholder' attribute?

    It doesn't do all the clever stuff you want like masking with hyphens, but it super-straightforwardly does "prompt text" in most modern browsers..

  2. That's pretty good, thanks for the headsup!

    (Doesn't work in IE, what a surprise...)