I gotta have my orange juice.

Jesu, Juva

Archive for the ‘Javascript’ Category

Unobtrusive Javascript: Self-labeling text inputs

with 5 comments

Some web sites have self-labeling text input boxes; for example, see the text box at the top right of the page on memberhub. When these self-labeling form fields are empty, they contain helpful text that labels or further explains their purpose, such as “Search” or “Enter your favorite color.” As soon as you click on these fields, the help text vanishes and you can type in a value.

Using unobtrusive Javascript (see introduction), we can add behavior to a text input element to automatically label it with this sort of help text contained within the element. We will take advantage of the title attribute of input elements to do this. The title element is already widely used in many browsers to provide help text in a tool-tip when you hover over an element with your mouse, and we will steal this title text from any input text field to use for self-labeling purposes. Here is a script that accomplishes that:

autolabel.js

Event.onReady(function() {
  $$('input[type="text"][title]').each(function(inputElement) {
    var e = inputElement;
    var color = e.getStyle('color');
    var fontStyle = e.getStyle('fontStyle');

    if(e.value == e.title) {            // FF reload behavior.
      e.value = '';
    }

    var blank = !$F(e);

    var blurHandler = function(ev) {
      blank = !$F(e);
      if(blank) {
        e.setStyle({ 'color'     : 'darkgray',
                     'fontStyle' : 'italic' });
        e.value = e.title;
      }
    }
    e.observe('focus', function(ev) {
      if(blank) {
        if($F(e) == e.title) {
          e.value = '';
        }
        e.setStyle({ 'color'     : color,
                     'fontStyle' : fontStyle });
      }
    });
    e.observe('blur', blurHandler);
    blurHandler(null);

    Event.observe(e.form, 'submit', function(ev) {
      if(blank) {
        e.value = '';
      }
    });
  });
});

Here’s how to use it. Note that you need to include the Prototype and Low Pro Javascript libraries:

example.html

<script src="/js/prototype.js" type="text/javascript"></script>
<script src="/js/lowpro.js" type="text/javascript"></script>
<script src="/js/autolabel.js" type="text/javascript"></script>
. . .
<input type="text" name="color" title="Enter your favorite color" />

How it works

The script first searches for all input elements in the document that are of type “text” and also have the title attribute. For each of these, it executes a function to add the self-labeling behavior to the element.  This function does a number of things:

  1. When you refresh a page in Firefox, as long as you don’t do a full reload, Firefox preserves whatever values were previously in form fields.  The function tests if the current value of the form field is equal to the title attribute (meaning that someone refreshed the page while the self-labeling description was present in the field, and if so, the function clears the form field.
  2. For all other purposes, the function uses the variable blank to track whether the field is blank and should have the label inserted.  We do this rather than comparing the field’s content to the title tag, in case the user actually types in the value of the title tag (for something like “Search”).
  3. The function adds a handler for the focus event (cursor enters field).  If the field is blank, it clears the label text so the user can enter their own text, and restores the field’s color and font style to their original values.  Otherwise, the field contains user text so it is left unchanged.
  4. The function adds a handler for the blur event (cursor leaves field).  If the field is blank, it remembers the fact that it is blank, sets the style of the field so that the text is italic and dark gray (you may modify this as you wish), and then inserts the title text.
  5. The function adds a handler for the submit event on the field’s form.  If the form is submitted and the field is blank, it will be cleared so that the correct value is submitted for the form contents.

Written by Scott Moonen

July 18, 2008 at 3:28 pm

Unobtrusive Javascript: Expandable textareas

with 8 comments

Using unobtrusive Javascript (see introduction), we can add behavior to textareas to make them automatically expand or contract as text is entered into them. Here is a script that accomplishes that:

autosize.js

Event.onReady(function() {
  $$('textarea').each(function(inputElement) {
    var textarea = inputElement;
    var initialHeight = textarea.getHeight();
    var currentHeight = -1;
    var currentTimer = false;
    var div = $div({id: textarea.id + '_hidden'});

    textarea.insert({'after': div});
    div.setStyle({'display'       : 'none',
                  'width'         : textarea.getWidth() ?
                                      (textarea.getWidth() + "px") :
                                      textarea.getStyle('width'),
                  'whiteSpace'    : 'pre-wrap',
                  'fontFamily'    : textarea.getStyle('fontFamily'),
                  'fontSize'      : textarea.getStyle('fontSize'),
                  'lineHeight'    : textarea.getStyle('lineHeight'),
                  'paddingTop'    : textarea.getStyle('paddingTop'),
                  'paddingLeft'   : textarea.getStyle('paddingLeft'),
                  'paddingRight'  : textarea.getStyle('paddingRight'),
                  'paddingBottom' : textarea.getStyle('paddingBottom'),
                  'marginTop'     : textarea.getStyle('marginTop'),
                  'marginLeft'    : textarea.getStyle('marginLeft'),
                  'marginRight'   : textarea.getStyle('marginRight'),
                  'marginBottom'  : textarea.getStyle('marginBottom'),
                  'borderTop'     : textarea.getStyle('borderTop'),
                  'borderLeft'    : textarea.getStyle('borderLeft'),
                  'borderRight'   : textarea.getStyle('borderRight'),
                  'borderBottom'  : textarea.getStyle('borderBottom')
                 });

    var timerHandler = function() {
      currentTimer = false;
      if(initialHeight == 0) {
        initialHeight = textarea.getHeight();
      }
      div.innerHTML = $F(textarea).replace(/&/g, '&amp;')
                                  .replace(/</g, '&lt;')
                                  .replace(/\n/g, '<br />') +
                      '<br />z';
      var newHeight = Math.max(initialHeight, div.getHeight());
      if(newHeight != currentHeight && newHeight != 0) {
        textarea.setStyle({ 'height': newHeight + 'px' });
        currentHeight = newHeight;
      }
    }
    var eventHandler = function(ev) {
      if(!currentTimer) {
        setTimeout(timerHandler, 250);
      }
    }
    textarea.observe('change', eventHandler);
    textarea.observe('keyup', eventHandler);
    timerHandler();
  });
});

Here’s how you would use it. You don’t need to include any explicit Javascript or even styling within your document; the script automatically locates all textareas and adds the stretch behavior to them. Note that you need to include the Prototype and Low Pro Javascript libraries:

example.html

<script src="/js/prototype.js" type="text/javascript"></script>
<script src="/js/lowpro.js" type="text/javascript"></script>
<script src="/js/autosize.js" type="text/javascript"></script>
. . .
<textarea name="comment">blah blah . . .</textarea>

How it works

The script first searches for all textareas in the document, and then executes a function for each textarea to add the stretch behavior to the element. This function creates a hidden div element associated with the textarea, and copies much of the style information from the textarea to the div. Then, it associates a function with the onkeyup and onchange events for the textarea. This event handler function copies the textarea text into the hidden div, measures the size of the div, and adjusts the size of the textarea to fit the size of the div. This means that the textarea grows or shrinks (never smaller than its original size) based on the size of the text contained within it.

Additional notes

The onchange and onkeyup handlers don’t directly copy the text into the div and resize the textarea. I found that doing that immediately on every key press slowed typing down considerably. Instead, the event handlers set a timer to expire 1/4s after the textarea is changed, and this timer handler itself does the resizing. I do not notice any lags in typing responsiveness with this approach.

The timer handler remembers the last measured size of the div so that it doesn’t need to resize the textarea if the div hasn’t changed in size. There are also some places where we check to be sure that a measured height is not zero — I found that IE6 sometimes reports a height of zero even though the DOM has loaded at the point that these functions are called.

If you have any unobtrusive Javascript code that hides your textareas, you should make sure that the autosize.js code runs before your textareas are hidden, so that it can measure their size while they are still visible. We’ll consider something like this in a later post, where you can have some text on your page with an edit link that automatically reveals a textarea to edit the text’s content.

Limitations

For unknown reasons, IE6 doesn’t seem to correctly report the fontFamily for an unstyled textarea. In cases where the textarea is clearly a monospace font, IE6 will report the body’s fontFamily (e.g., ‘arial’) instead of ‘monospace’ when retrieving the textarea’s fontFamily style. The result of this is that the div’s styling doesn’t match the textarea’s styling, and so the textarea will not necessarily be sized properly if it holds a lot of text. The workaround for this problem is to explicitly style your textareas using ‘font-family: monospace’; IE6 correctly reports the fontFamily in this case.

As indicated above, in some cases IE6 incorrectly reports a height of 0 for the textarea or div. The result of this is that in IE6 the textarea’s initial size may not stretch to fit its contents, but as soon as a character is typed into it it will expand to the correct size. I’m not aware of any universal workarounds for this. However, if you have textareas that are not auto-hidden on page load then you may be able to modify the code above so that instead of calling timerHandler() directly at DOM load time, it is scheduled to be called by a timer shortly thereafter.

Written by Scott Moonen

July 8, 2008 at 8:58 am

Unobtrusive Javascript: Introduction

with 5 comments

Unobtrusive Javascript is a way of using Javascript that cleanly separates your Javascript from your HTML code.  Instead of coding Javascript actions within your document (e.g., using an onchange=”…” handler), your Javascript actions are contained within an external script that inserts actions into your document based on CSS class names or element ids that you have pre-determined.

For example, you might have a link that toggles the presence of another element:

<script src="/js/prototype.js" type="text/javascript"></script>
. . .
<a href="#" onclick="$('toggle').toggle(); return false;">Toggle</a>
<div id="toggle" style="display:none">Some content here</div>

There are some problems with this, however.  First, the code is cluttered with messy Javascript.  Second, our code doesn’t fail gracefully if Javascript is not enabled — the “display:none” style will cause our div not to be displayed, but it would be better if the default were for it to be displayed when Javascript is not active.  Of course, we could create a body onload action to workaround this problem, but that clutters our HTML with even more Javascript and also causes the toggle behavior to be spread across several elements.

Unobtrusive Javascript solves these problems by defining and collecting the dynamic behavior in a single separate Javascript file.  The HTML document now contains only HTML, plus a few additional CSS class selectors that our Unobtrusive Javascript uses to locate elements that it should take action on.  Using the above example, we might create an Unobtrusive Javascript solution that looks like this:

autotoggle.js

Event.onReady(function() {
    $$('.auto_toggle').each(function(elem) {
        var element = elem;
        var anchor = $(elem.id + "_anchor");
        element.hide()
        anchor.observe('click', function(ev) { element.toggle(); ev.stop(); });
      });
  });

example.html

<script src="/js/prototype.js" type="text/javascript"></script>
<script src="/js/lowpro.js" type="text/javascript"></script>
<script src="/js/autotoggle.js" type="text/javascript"></script>
. . .
<a href="#" id="toggle_anchor">Toggle</a>
<div id="toggle" class="auto_toggle">Some content here</div>

Before we defined an onclick action on our anchor, but now we set a special CSS class on the div.  By convention, we give the anchor an id based on the id for the div.  Our external Javascript code then finds all divs with the class “auto_toggle”, and inserts an onclick action into the anchor whose id matches the div’s id, with the suffix “_anchor”.  If Javascript is not active, the div will be displayed.

Going into more detail, here is what our unobtrusive toggle handler does:

  1. For DOM help, it uses the Prototype and Low Pro libraries (although it is possible to write unobtrusive Javascript using other libraries like jQuery).
  2. The call to Low Pro’s Event.onReady() registers a function to be called when the page’s DOM is loaded.  We’ve provided an anonymous function to this call that performs our unobtrusive action.
  3. Our function calls Prototype’s $$() function to locate the list of all elements matching the CSS selector specification “.auto_toggle”; i.e., any element having a class named “auto_toggle”.  It then iterates over this list using the Prototype Array.each() method, providing an anonymous element handler function to be called for each element.
  4. Our element handler creates variables referencing the matching element, and also locating the anchor element that has the id matching the current element’s id with the added suffix “_anchor”.  It then calls the Prototype Element.hide() method to ensure that the toggle element starts out hidden.
  5. Our element handler then calls the Prototype Element.observe() method to register a handler for whenever the toggle anchor is clicked.  For our click handler we provide an anonymous function that calls the Prototype Element.toggle() method to toggle the visibility of the target element and then stop the event by calling Event.stop() (stopping the event is the same as returning false in an onclick event; this causes the anchor target URL not to be visited).  You may notice that our click handler function is a Javascript closure — it references variables in the calling function, and these variables will be available to it even when the calling function has ended.

This is a simple example, but we could take it further.  For example, we could define the anchor as a span element, and our unobtrusive code could replace it with an anchor element only if Javascript is enabled.  That way, users without Javascript won’t see anything clickable on the toggle action.  Or, if we knew that every toggle-able div had the text “Toggle” for its anchor, we could remove the toggle anchor altogether and have our unobtrusive code insert it.

Apart from keeping our HTML code clean and simple, one of the great advantages of Unobtrusive Javascript is that we can build up a library of powerful unobtrusive actions that we can reuse with minimal changes to our HTML.  Growing more complex than the example we’ve considered here, we could insert dynamic pop-up calendars for every form input element that has a “date_calendar” CSS class.  Or we could create code to find every textarea element in the document and cause these textareas to be dynamically resized as users type text into them.  We’ll start to build up our Unobtrusive Javascript library by considering examples like these in subsequent posts.

Written by Scott Moonen

June 23, 2008 at 10:55 am

Follow

Get every new post delivered to your Inbox.

Join 116 other followers