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>

4 comments:

Anonymous said...

Doesn't work in ie 8

Arnaud said...

Thanks for your extensions ! It was really useful for me !

Kai Vong said...

I considered doing using this... however the following line of jQuery does the same with less code!

.animate({'opacity':0});

Anonymous said...

heyyyy