StickyTable – jQuery fixed table header plugin

One of my client wanted to display financial reports data with better accessibility and printing usability. He asked me to retain table header fixed on the top while scrolling financial reports.  He also wanted to have MS Access VBA style multicolumn drop-down component for large customer database. In this post I am writing about that fixed table header component which I have coded in jQuery.

By default, If you print any HTML table…

  • the <TH> row get printed on top of every pages that contains the table.
  • <TFOOT> row get printed on every page’s footer.
  • <THEAD> row get printed on every page’s header.

For printing we have chosen fixed width fonts i.e. Courier New.

Demo tutorial page: Demo for jQuery stickyTable plugin

Download: jQuery stickyTable plugin zip file

Initializing jQuery stickyTable plugin

$(document).ready(function(){
	$('#report_table').stickyTable();
});

Above function binds StickyTable plugin to supplied table.

jQuery.stickyTable.js plugin source

(function($){
$.fn.stickyTable = function() {
  if ($(this).length == 0) {
    throw new Error('jQuery.stickyTable ERROR\n\nAttribute id="'+ $(this).selector.replace("#","") +'" NOT FOUND in your webpage.');
    return;
  }

  function equilize_width() {
    var ths = $('#report_table')[0].rows[0].cells;
    var thd = $('#stickyfixed')[0].rows[0].cells;
    for(var j=0; j < ths.length; j++) {
      $(thd[j]).width($(ths[j]).width()-1);
    }
    $('#stickyfixed').width($('#report_table').width()+4);
  }

  $(this).parent().append("<div id='stickyfixed_container' class='hide'><table id='stickyfixed' class='report_table' border='1'></table>&ly;/div>");

  $('#report_table tr:first').clone().appendTo('#stickyfixed');
  equilize_width();
  var cutoffTop      = $('#report_table').offset().top;
  var cutoffBottom  = $('#report_table').height(); + cutoffTop - $($('#report_table')[0].rows[0].cells[0]).height();
  var no_fixed_support = false;
  if ($('#stickyfixed').css('position') == "absolute") {
    // IE 6 hack
    no_fixed_support = true;
  }
  $('#report_table tr').hover(function() {
      $(this).addClass("highlight");
    },function() {
      $(this).removeClass("highlight");
  });

  //Handling windows resize
  $(window).resize(function(){
    equilize_width();
  });

  $(window).scroll(function(){
    var currentPosition = $(document).scrollTop();
      if (currentPosition > cutoffTop && currentPosition < cutoffBottom) {
        $('#stickyfixed_container').show();
          if (no_fixed_support == true) {
            // IE 6 hack
            document.getElementById('stickyfixed').style.top = parseInt(currentPosition) + "px";
          }
      } else {
    $('#stickyfixed_container').hide();
   }
  });
  return $(this);
 };
})(jQuery);

SCREEN.CSS

/* CSS for screen */
body,*{font-family:Tahoma}
h1{padding:0px;margin:5px;font-size:22px}
h1{padding:0px;margin:5px;font-size:22px}
#header{padding:10px;font-size:24pt;font-weight:bold;font-family:Arial,sans-serif;border-bottom:1px solid #DDD;text-align:left;margin:0px}
#report *{font-family:Tahoma;font-size:8pt}
table.report_table{border-collapse:collapse;border-spacing: 2px;border:2px solid #FFF;width:100%;overflow:scroll;background-color:#FFF;font-size:10pt;}
table.report_table th{padding:5px;background-color:#3B5998;color:#FFF}
table.report_table tr td{padding:5px;background-color:#F3F3F3;border:1px solid #FFF;}
table.report_table tr.highlight td{padding:5px;background-color:#B0D4F7;color:#000;}
table#stickyfixed{z-index:10;position:fixed;_position:absolute;top:0;font-size:10pt;}
table#stickyfixed th{}
.hide{display:none;}

PRINT.CSS

/* CSS for print */
body,*{font-family:Courier New;font-size:11px}
h1{display:none;font-family:Times New Roman;padding:0px;margin:5px;font-size:18px}
#report *{font-family:Courier New}
table.report_table tr th{font-size:14pt}
table.report_table tr td h3{font-size:12pt}
table.report_table td{overflow:hidden;white-space:nowrap;}
#member_links{display:none}
table#report_form{display:none;}
table#stickyfixed{display:none;}

Handling Thickbox and Facebox jQuery plugins with LiveQuery for AJAX

I found an interesting JQuery plugin to code painless AJAX modules.

It’s called LiveQuery which binds events/callbacks/plugins to the elements, will be loaded into DOM through AJAX requests.

Where to use LiveQuery?

We know that jQuery has most famous ready(); method that executes after DOM completion. So, what’s the point of LiveQuery?

Okay, let’s take an example.

We need to load some healthy amount of HTML elements using AJAX which includes hyperlinks, table, forms etc. and then we want to bind events to them. How would we achieve that?

Simple solution would be write a callback function.

$.get('ajax_url',serialize_params_map,function(data){
// manipulate data variable;
// execute callback to bind events;
});

We may think, it would be great we don’t have to attach manual callback after each and every ajax call and should be simple as

$(document).ready(
  function(){
  //execute callback to bind events;
  }
);

To understand my point more clearly, take a practical example.

  1. ThickBox Problem
    // thickbox.js (jQuery plugin)
    //on page load call tb_init
    $(document).ready(
      function(){
        //pass where to apply thickbox
        tb_init('a.thickbox, area.thickbox, input.thickbox');
        imgLoader = new Image();// preload image
        imgLoader.src = tb_pathToImage;
      }
    );

    Look at line6.
    It only executes one time when DOM get completed.

    What If we need to bind thickbox to new elements, inserted through AJAX request?

    One solution could be call tb_init(‘..’) as “callback function” to every AJAX request

    $.get('ajax_url',serialize_params_map,function(data){
    // manipulate data
      tb_init('a.thickbox, area.thickbox, input.thickbox');
    //pass where to apply thickbox
    });

    or use LiveQuery ONLY ONCE i.e.

    Thickbox Demo

    $('a.thickbox').livequery(
      function(){
        tb_init('a.thickbox');
      }
    );

    So, what would above code do?
    It simulates $(document).ready(); function. Also observers for any “set of newly matched elements” inserted in DOM. So whenever “AJAX request” insert new elements in DOM, above function will bind thickbox to them.

  2. FaceBox Problem
    The same case with facebox… and could be with many more jquery plugins. To make facebox work properly with post loaded HTML elements, use…

    jQuery(document).ready(function() {
    $('a[rel*=facebox]').livequery(
      function(){
        $(this).facebox();
      });
    });

    instead of

    jQuery(document).ready(
      function($) {
        $('a[rel*=facebox]').facebox();
      }
    );

The conclusion is…

jQuery + painless AJAX modules => efficient use of LiveQuery.

Facebox hack for Internet Explorer (IE 5,6,7)

Along with jQuery, we have used third party module facebox for hindi lyrics. During testing different browsers, I got problem with placement of facebox.

In internet explorer (IE 6 & 7) it got aligned left.

So, finally I tried some cross browser hack for it. I have done some little change in facebox.js

At the end of function,

$.facebox.reveal = function(data, klass) {

I have added followng line,

if(jQuery.browser.msie){
b = $("BODY").width();
pl = (b - $('#facebox .content')[0].offsetWidth)/2;
$('#facebox')[0].style.paddingLeft = pl;
}
}

Explanation:-

[1] If browser is Internet Explorer, it gets width of view port into b variable
[2] Now facebox width is $(‘#facebox .content’)[0].offsetWidth
[3] So, required left padding is (total screen width – box with)/2
[4] $(‘#facebox’)[0].style.paddingLeft = pl line properly aligns the facebox.