d169f120ae893c9124e4cf9b50182f4372a3f0d3 kate Tue Oct 25 17:27:16 2011 -0700 Checking in jquery floating table header plugin, and ENCODE data matrix implementations that now use it diff --git src/hg/js/jquery.floatheader.js src/hg/js/jquery.floatheader.js new file mode 100644 index 0000000..665d41e --- /dev/null +++ src/hg/js/jquery.floatheader.js @@ -0,0 +1,282 @@ +/* + jQuery floating header plugin v1.4.0 + Licenced under the MIT License + Copyright (c) 2009, 2010, 2011 + Erik Bystrom <erik.bystrom@gmail.com> + + Contributors: + Elias Bergqvist <elias@basilisk.se> + Diego Arbelaez <diegoarbelaez@gmail.com> + Glen Gilbert + Vasilianskiy Sergey + Stephen J. Fuhry + Jason Axley +*/ +(function($){ + /** + * Clone the table header floating and binds its to the browser scrolling + * so that it will be displayed when the original table header is out of sight. + * + * The plugin defines two function on the table element. + * fhRecalculate Recalculates with column widths of the floater. + * fhInit Recreates the floater from the source table header. + * + * @param config + * An optional dictionary with configuration for the plugin. + * + * fadeOut The length of the fade out animation in ms. Default: 200 + * fadeIn The length of the face in animation in ms. Default: 200 + * forceClass Forces the plugin to use the markerClass instead of thead. Default: false + * markerClass The classname to use when marking which table rows that should be floating. Default: floating + * floatClass The class of the div that contains the floating header. The style should + * contain an appropriate z-index value. Default: 'floatHeader' + * cbFadeOut A callback that is called when the floating header should be faded out. + * The method is called with the wrapped header as argument. + * cbFadeIn A callback that is called when the floating header should be faded in. + * The method is called with the wrapped header as argument. + * recalculate Recalculate the column width on every scroll event + * + * @version 1.4.0 + * @see http://blog.slackers.se/2009/07/jquery-floating-table-header-plugin.html + */ + $.fn.floatHeader = function(config) { + config = $.extend({ + fadeOut: 200, + fadeIn: 200, + forceClass: false, + markerClass: 'floating', + floatClass: 'floatHeader', + recalculate: false, + IE6Fix_DetectScrollOnBody: true + }, config); + + return this.each(function () { + var self = $(this); + + var tableClone = self[0].cloneNode(false); // only perform a shallow copy + var table = $(tableClone); + var cloneId = table.attr("id") + "FloatHeaderClone"; + table.attr("id", cloneId); // change the ID to avoid conflicts + table.parent().remove(); // remove any existing float box divs for this same grid. we may be reinitializing and don't want to keep adding these to the DOM + + self.floatBox = $('<div class="'+config.floatClass+'"style="display:none"></div>'); + self.floatBox.append(table); + + // Fix for the IE resize handling + self.IEWindowWidth = document.documentElement.clientWidth; + self.IEWindowHeight = document.documentElement.clientHeight; + + // DO NOT create the floater yet. + // Lazy-load and create it only when neccessary to improve page load time + + /* + * This is very specific to IE6 only if using position:fixed fixes. + * This requires the window overflow to be set to hidden and the + * containing 'body' tag to have overflow:auto. + */ + if (!$.browser.msie) { + config.IE6Fix_DetectScrollOnBody = false; + } else { + if ($.browser.version > 7) { + config.IE6Fix_DetectScrollOnBody = false; + } + } + var scrollElement = config.IE6Fix_DetectScrollOnBody ? $('body') : $(window); + + // bind to the scroll event + scrollElement.scroll(function() { + if (self.floatBoxVisible) { + if (!showHeader(self, self.floatBox)) { + // kill the floatbox + var offset = self.offset(); + self.floatBox.css('position', 'absolute'); + self.floatBox.css('top', offset.top); + self.floatBox.css('left', offset.left); + + self.floatBoxVisible = false; + if (config.cbFadeOut) { + config.cbFadeOut(self.floatBox); + } else { + self.floatBox.stop(true, true); + self.floatBox.fadeOut(config.fadeOut); + } + } + } else if (showHeader(self, self.floatBox)) { + // populate the floating header now in case it is needed (lazy load) + // and only if we haven't yet filled in the header details + if (table.children().length === 0) { + createFloater(table, self, config); + } + + self.floatBoxVisible = true; + // show the floatbox + if ($.browser.msie && $.browser.version < 7) { + // IE6 can't handle fixed positioning; has to use absolute and additional calculation to position correctly + // strictly speaking, this isn't necessary as it is position:absolute + self.floatBox.css('position', 'absolute'); + } else { + self.floatBox.css('position', 'fixed'); + } + + if (config.cbFadeIn) { + config.cbFadeIn(self.floatBox); + } else { + self.floatBox.stop(true, true); + self.floatBox.fadeIn(config.fadeIn); + } + } + + // if the box is visible update the position + if (self.floatBoxVisible) { + // ie6 fix + if ($.browser.msie && $.browser.version <= 7) { + self.floatBox.css('top', $(window).scrollTop()); + } else { + self.floatBox.css('top', 0); + } + self.floatBox.css('left', self.offset().left-$(window).scrollLeft()); + if (config.recalculate) { + recalculateColumnWidth(table, self, config); + } + } + }); + + /* + * Unfortunately IE gets rather stroppy with the non-IE version, + * constantly resizing, thus cooking your CPU with 100% usage whilest + * the browser crashes. So, test for IE and add additional code. + */ + if ($.browser.msie && $.browser.version <= 7) { + $(window).resize(function() { + // Check if the window size has changed () + if ((self.IEWindowWidth != document.documentElement.clientWidth) || (self.IEWindowHeight != document.documentElement.clientHeight)) { + // Update the client width and height with the Microsoft version. + self.IEWindowWidth = document.documentElement.clientWidth; + self.IEWindowHeight = document.documentElement.clientHeight; + + if (table.children().length > 0) { + table.fastempty(); + createFloater(table, self, config); + } + } + }); + } else { + // bind to the resize event + $(window).resize(function() { + // Only redo the header cells if we have created them already + if (table.children().length > 0) { + table.fastempty(); + createFloater(table, self, config); + } + }); + }; + + // append the floatBox to the dom + $(self).after(self.floatBox); + + // connect some convenience callbacks + this.fhRecalculate = function() { + recalculateColumnWidth(table, self, config); + }; + + this.fhInit = function() { + // Only redo the header cells if we have created them already + if (table.children().length > 0) { + table.fastempty(); + createFloater(table, self, config); + } + }; + + /// Creating an alternative to the jquery empty() API that is optimized for cases where you know that there are not any event handlers left on the nodes in the container you are emptying + /// Otherwise, you could experience memory leaks. empty() is very slow because it has to visit every DOM element and delete it individually. + /// This function will clear out all child elements using DOM APIs. Note: you CANNOT use innerHTML = '' as a general solution because in IE innerHTML is read-only for many, many container nodes. + $.fn.fastempty = function() { + if (this[0]) { + while (this[0].hasChildNodes()) { + this[0].removeChild(this[0].lastChild); + } + } + + return this; + }; + }); + }; + + /** + * Copies the template and inserts each element into the target. + */ + function createFloater(target, template, config) { + target.width(template.width()); + + var items; + if (!config.forceClass && template.children('thead').length > 0) { + // set the template to the children of thead + items = template.children('thead').eq(0).children(); + var thead = jQuery("<thead/>"); + target.append(thead); + target = thead; + } else { + // set the template to the class marking + items = template.find('.'+config.markerClass); + } + + // iterate though each row that should be floating + items.each(function() { + var row = $(this); + // avoid deep clone, then removal the nodes you just cloned + var rowClone = row[0].cloneNode(false); + var floatRow = $(rowClone); + + // adjust the column width for each header cell + row.children().each(function() { + var cell = $(this); + var floatCell = cell.clone(); + + floatCell.width(cell.width()); + floatRow.append(floatCell); + }); + + // append the row to the table + target.append(floatRow); + }); + } + + /** + * Recalculates the column widths of the floater. + */ + function recalculateColumnWidth(target, template, config) { + target.width(template.width()); + var src; + var dst; + if (!config.forceClass && template.children('thead').length > 0) { + src = template.children('thead').eq(0).children().eq(0); + dst = target.children('thead').eq(0).children().eq(0); + } else { + src = template.find('.'+config.markerClass).eq(0); + dst = target.children().eq(0); + } + + dst = dst.children().eq(0); + src.children().each(function(index, element) { + dst.width($(element).width()); + dst = dst.next(); + }); + } + + /** + * Determines if the element is visible + */ + function showHeader(element, floater) { + var elem = $(element); + var top = $(window).scrollTop(); + var y0 = elem.offset().top; + + var height = elem.height()-floater.height(); + var foot = elem.children('tfoot'); + if (foot.length > 0) { + height -= foot.height(); + } + + return y0 <= top && top <= y0 + height; + } +})(jQuery);