Wednesday, April 29, 2009

jQuery extensions to support highlight effects on hidden elements

I was disappointed with the lack of support for show, hide, fade and highlight effects on hidden elements in jQuery. By hidden I mean that they have visibility: hidden and not display: none (so they still take up space in the DOM but are not "visible"). Almost all of jQuery's functions to hide and show elements toggle their states between display: block; and display: none;. This is alright in some cases, but sometimes you want the element to still take up DOM space.

Even the :hidden selector doesn't work on visibility: hidden elements. So I've added an extension function called hidden() which returns a jQuery list of elements with visibility: hidden. So if you want to test if something is hidden you can do if ($('#element').hidden().length) { ... }, as well as call regular jQuery methods on the result like so $('#table td:hidden').show();.

Because most of the time I want to highlight elements when I show/hide, the extension functions I have written use the jQuery Highlight effect. So you will need that to run this code. You will also need the UI Core (required for all effects) and of course the jQuery library.

Another annoying thing is that the jQuery UI highlight effects don't work on table rows. I suspect it's because table rows have a display mode of display: table-row; as opposed to the usual display: none;. Because I often need to highlight show/hide table rows, I've added support for them in my extension functions.

There are four highlight methods:

  • highlightShow: highlight and fade in (show) an element or elements.
  • highlightFade: highlight and fade out (to visibility: hidden) an element or elements.
  • highlightHide: highlight, fade out and hide (display: none) an element or elements.
  • highlightRemove: highlight, fade out and remove from the DOM an element or elements.
Let me know if you've found this useful! Enjoy the slick JavaSripty goodness!

The Demo

A Regular DIV

Wrapper
This is the text we will fade in and out.

A Table

NameOccupationHeightRemoveFade
Karl VargaProgrammer6'
Karl VargaProgrammer6'
John EdwardStudent10' 5"

The JavaScript code for the table effects uses event delegation to bind events to the checkboxes. This means that you can bind an event handler to a top-level element. When sub-elements receive events they bubble up the DOM to the parent, which handles it. This way, I just have to write a handler for the remove and fade events on the table and when I add new rows to the table those new rows will also support the remove and fade events because the handler is not bound to each individual checkbox, but rather to the parent table. Neat!

It uses the jQuery Listen plugin and here is the JavaScript snippet:

<script type="text/javascript">

$().ready(function() {

  $('#demo-table').listen('click', 'input.remove', function(e) {
    $(this).closest('tr').highlightRemove(2000); 
  });
  
  $('#demo-table').listen('click', 'input.fade', function(e) {
    $(this).closest('tr').highlightFade(2000); 
  });
  
  $('#add-row').click(function() {
    var hidden = $('#demo-table tr:hidden'); 
    var clone = hidden.clone(); 
    hidden.after(clone); 
    clone.highlightShow(2000); 
  });
  
});
</script>

The Code

Here are my jQuery extensions. If you are using jQuery in no conflict mode you will have to include this JavaScript before your noConflict call, or do a search-and-replace on the $ function.

<script type="text/javascript">

jQuery.fn.extend({
  
  /**
   * Highlight and fade out an element to visibility: hidden so that it still takes up DOM space.
   * Works for table rows (which use display: table-row as opposed to the usual display: block).
   *
   */
  highlightFade: function(speed) {
    this.each(function() { 
    $(this).effect('highlight', { mode: 'hide' }, speed, function() {
      // retore the elements visibility and display type
      this.style.visibility = "hidden";
      if ($(this).is('tr')) {
        this.style.display = 'table-row';
      } else {
        this.style.display = 'block';
      }
    });
  });
  return this;
  },

  /**
   * Highlight and fade out an element to display: none.  Just a wrapper for effect('highlight', ...)
   * for completeness.  Use highlightFade() to highlight and fade but still maintain visibility.
   */
  highlightHide: function(speed) {
    this.effect('highlight', { mode: 'hide' }, speed);
  return this;
  },

  /**
   * Highlight and fade in an element.  Works for visibility: hidden elements as well 
   * as elements with display: none.  jQuery highlight doesn't work on table rows, so
   * we apply the effect to the row cells.
   */
  highlightShow: function(speed) {
    this.each(function() {
    if ($(this).hidden().length) {
      this.style.display = "none";         // highlight only works when display is none
      this.style.visibility = "visible";
    }
    var apply_to = $(this);
    if ($(this).is('tr')) {
      apply_to = apply_to.find('td');
    }
    apply_to.effect('highlight', {}, speed);
  });
  return this;
  },

  /**
   * Highlight fade out and remove.  jQuery highlight doesn't work on table rows, so
   * we apply the effect to the row cells.
   */
  highlightRemove: function(speed, callback) {
  this.each(function() {
    var original_target = $(this);
    var apply_to = original_target;
    if ($(this).is('tr')) {
      apply_to = apply_to.find('td');
    }
    apply_to.effect("highlight", {mode: 'hide'}, speed, function() { 
      original_target.remove();
      if (callback != undefined) {
        callback.call(this);
      }
    });
  });
  return this;
  },

  /**
   * Return elements which have visibility: hidden.
   * The jQuery :hidden selector only matches elements with display: none.
   */
  hidden: function() {
  var hidden = [];
  this.each(function() {
    if (this.style.visibility == 'hidden') {
      hidden.push(this);
    }
  });
  return $(hidden);
  }
});'

</script>

Wednesday, April 22, 2009

Output JavaScript date in human-readable format (Day Month DD YYYY HH:MM:SS PM)

You often need to display the user's current date and time on a website in a custom format. Unfortunately JavaScript doesn't have (many) date formatting functions built-in so you either have to use someone else's JS date formatting "library" or roll your own.

For simplicity I rolled my own. And since this is common thing that we all need to do from time to time I thought I would share my code in case you find it useful.

The Demo

(The format the date appears in is Day Month DD YYYY HH:MM:SS PM.)

The date and time right now is

The Code


Tuesday, April 21, 2009

Simple JavaScript expanding text input

Today my colleague had a requirement for a simple expanding text input on a website form. By expanding I don't mean that it gets bigger as you type, just that when you click/focus into the text input it appears to grow giving you a larger multi-line textarea in which to post a comment. Unfortunately I couldn't whip out jQuery (which is my new favourity JS library after using Prototype for some time) because we just needed this one bit of DHTML.

Requirements

  • When user clicks/focuses on the input show a larger textarea.
  • The textarea must appear above the other form elements but leave some of the submit button showing.

The Demo

Click in the input to see it grow.

The Code



<style type="text/css">

/**
 * Styles for the expanding / contracting input
 */

#smallInput {
 height: 30px;
 width: 250px;
 overflow: hidden;

}
#largeInput {
 display: none;
 height: 80px;
 width: 350px;
 overflow-x: hidden;
 overflow-y: auto;
 word-wrap: break-word;
 z-index: 1000;
}

</style>