/*
  This plugin will take a table and create an area with fixed column or row headers.
  These headers will scroll in relation to how the table scrolls.

  Created By:   Shawn Grover (http://grover.open2space.com)
  Created On:   30 Dec 2007
*/
var $jj = jQuery.noConflict();
(function ($jj) {
  /*
      Possible options:
        height            - the height of the table area (default 98%)
        width             - the width of the table area (default 98%)
        scrollDelay       - number of milliseconds to pause before adjusting the headers (default 10)
        forceFooter       - boolean value.  If true, use the thead as the footer (default true)
                                            If false, use the tfoot (if available).
        rowHeaders        - the number of columns from the left hand side to use as row headers (default 0)
  */

  /*
      Layout:
        - Starting with a plain table, we want to modify the structure to look something like this:

        ------------------------------------------------------
        | Working Area  DIV                                  |
        |            -----------------------------------------
        |            | Column Header DIV                     |
        |            -----------------------------------------
        | ---------- -----------------------------------------
        | | Row    | | Table Area DIV                        |
        | | Headers| |                                       |
        | |        | |                                       |
        | ---------- -----------------------------------------
        |            -----------------------------------------
        |            | Column Footer DIV                     |
        |            -----------------------------------------
        ------------------------------------------------------

        And the original table would be contained in teh Table Area DIV, with the corresponding thead, tfoot, and X number of columns hidden.
        The table area div would have it's overflow set to AUTO, and the scrolling event would be intercepted to adjsut the offset of the header/footer areas.
  */

  /* **************************************************/
  //    Main Plugin
  /* **************************************************/
  jQuery.fn.fixedTableHeaders = function (options) {
    var opts = $jj.extend({}, options);
    var me = this;

    //Add div wrapper around working area
    var workarea = "<div id=\"fixedHeaderArea\"></div>";
    $jj(this).wrap(workarea);
    workarea = $jj("#fixedHeaderArea");

    //Add div around table (for scrolling purposes
    var tblarea = "<div id=\"fixedHeaderTable\"></div>";
    $jj(this).wrap(tblarea);
    tblarea = $jj("#fixedHeaderTable");

    //Add the column header, row header, and footer areas
    var colhead = "<div id=\"fixedHeaderColumns\">&nbsp;</div>";
    var rowhead = "<div id=\"fixedHeaderRows\" style=\"float: left;\">&nbsp;</div>";
    var foothead = "<div id=\"fixedHeaderFooter\">&nbsp;</div>";
    $jj(tblarea).before(colhead);
    $jj(tblarea).after(foothead);
    
    if (opts.rowHeaders) {
      $jj(tblarea).before(rowhead);
    }

    colhead = $jj("#fixedHeaderColumns");
    rowhead = $jj("#fixedHeaderRows");
    foothead = $jj("#fixedHeaderFooter");

    buildColumnHeaders(this, opts);
    buildRowHeaders(this, opts);
    sizeHeaders(opts);


    //apply scrolling event handlers
    $jj(tblarea).unbind("scroll").scroll( function () {
      if (tblarea[0].scrollTimer) {
        clearTimeout(tblarea[0].scrollTimer);
      }
      tblarea[0].scrollTimer = setTimeout(scrollHeaders, options.scrollDelay || 10);
    });

    //make sure we readjust when the screen is resized
    $jj(window).resize( function () {
      sizeHeaders(opts);
    });

    //Debugging purposes
    $jj(workarea).css({ border : "1px solid red" });
    $jj(tblarea).css({ border : "1px solid blue" });
    $jj(colhead).css({ "background-color": "#ddd" });
    $jj(rowhead).css({ "background-color": "#ddd" });
    $jj(foothead).css({"background-color": "#ddd" });
  };

  /* **************************************************/
  //    Private Functions
  /* **************************************************/
  
  //Move the thead section into our column header area
  function buildColumnHeaders(src, options) {
    var srcHead = $jj(src).children("thead");

    var data = "";
    $jj(srcHead).children("tr").each( function () {
      data += "<tr>";
      $jj(this).children("th").each( function () {
        data += "<td><div class=\"headerContent\">" + $jj(this).html() + "</div></td>";
      });
      data += "</tr>";
    });
    
    var heads = "<table><tbody>" + data + "</tbody></table>";
    $jj("#fixedHeaderColumns").html(heads);
    srcHead.hide();

    var foots = "<table><tbody>";
    if (options.forceFooter) {
      foots += data + "</tbody></table>";
      $jj("#fixedHeaderFooter").html(foots);
      $jj(src).children("tfoot").hide();
    }
    else {
      var srcFoot = $jj(src).children("tfoot");
      if (srcFoot.length) {
        $jj(srcFoot).children("tr").each( function () {
          foots += "<tr>";
          $jj(this).children("th").each( function () {
            foots += "<td><div class=\"headerContent\">" + $jj(this).html() + "</div></td>";
          });
          foots += "</tr>";
        });
        foots += "</tbody></table>";
        $jj("#fixedHeaderFooter").html(foots);
        srcFoot.hide();
      }
    }
  }
  
  //move the desired rows to the row header area
  function buildRowHeaders(src, options) {
    if (!options) { return; }
    if (options.rowHeaders && options.rowHeaders > 0) {
      //We are going to grab the first X columns (specified by options.rowHeaders) and use those
      //as our row header values.

      var rows = "<table><tbody>";
      $jj(src).children("tbody").children("tr").each(function () {
        //For each row, we need to get the contents of the first X cells
        rows += "<tr>";
        $jj(this).children("td:lt(" + options.rowHeaders + ")").each( function () {
          rows += "<td><div class=\"headerContent\">" + $jj(this).html() + "</div></td>";
          $jj(this).hide();
        });
        rows += "</tr>";
      });
      rows += "</tbody></table>";

      $jj("#fixedHeaderRows").html(rows);


      //We need to also remove the first X number of columns on the header and header rows
      $jj("#fixedHeaderColumns td:lt(" + options.rowHeaders + ")").hide();
      $jj("#fixedHeaderFooter td:lt(" + options.rowHeaders + ")").hide();
    }
  }

  //Adjust the size of the DIVs
  function sizeHeaders(opts) {
    var tblarea = $jj("#fixedHeaderTable");
    var colhead = $jj("#fixedHeaderColumns");
    var rowHead = $jj("#fixedHeaderRows");
    var foothead = $jj("#fixedHeaderFooter");
    
    //Make sure the header/footer cells match the width of the table cells
    var row = $jj(tblarea).children("table").children("tbody").children("tr:first");
    $jj(row).children("td").each( function(pos) {
      var headtarget = $jj(colhead).find("td:eq(" + pos + ") div.headerContent");
      var foottarget = $jj(foothead).find("td:eq(" + pos + ") div.headerContent");
      headtarget.css({
        width : $jj(this).css("width"),
        "padding" : $jj(this).css("padding"),
        "font-size" : $jj(this).css("font-size"),
        "font-weight" : "bold",
        "border" : $jj(this).css("border"),
        "text-align" : "center"
      });
      foottarget.css({
        width : $jj(this).css("width"),
        "padding" : $j(this).css("padding"),
        "font-size" : $j(this).css("font-size"),
        "font-weight" : "bold",
        "border" : $j(this).css("border"),
        "text-align" : "center"
      });
    });

    //Make sure the row headers match the height of the rows
    if (opts.rowHeaders) {

      var tblrows = $j(tblarea).children("table").children("tbody").children("tr");
      tblrows.each(function (pos) {
        var cell = $j(this).children("td:eq(1)");
        var rowtarget = $j(rowHead).find("tr:eq(" + pos + ") div.headerContent");
        rowtarget.css({
          height : cell.css("height"),
          "font-size" : cell.css("font-size"),
          "fong-weight" : cell.css("font-weight")
        });
      });
    }
    
    //These widths and positions need to be set AFTER the headers have been sized
    //Set the width of the table area
    $j(tblarea).css({
      width : opts.width || "auto",
      height: opts.height|| "98%",
      overflow : "auto"
    });

    var horizontalOffset = 0;
    if ($j(rowHead).length) { horizontalOffset = $j(rowHead).width(); }

    $j(colhead).css({
      position: "relative",
      left: horizontalOffset,
      width: $j(tblarea).width() - 16,
      overflow: "hidden"
    });

    if (opts.rowHeaders) {
      $j(rowHead).css({
        height: $j(tblarea).height() - 16,
        overflow: "hidden"
      });
    }
    
    $j(foothead).css({
      position: "relative",
      left: horizontalOffset,
      width: $j(tblarea).width() - 16,
      overflow: "hidden"
    });
  }

  //Adjust the position of the headers based on a scroll
  function scrollHeaders() {
    //Find the scrolling offsets
    var tblarea = $j("#fixedHeaderTable");
    var x = tblarea[0].scrollLeft;
    var y = tblarea[0].scrollTop;

    $j("#fixedHeaderColumns")[0].scrollLeft = x;
    $j("#fixedHeaderFooter")[0].scrollLeft = x;
    if($j("#fixedHeaderRows").length) {
      $j("#fixedHeaderRows")[0].scrollTop = y;
    }
  }
  
})(jQuery);


