/** * v0.5 THFloat plugin for jQuery * http://rommelsantor.com/jquery/thfloat * * Author(s): Rommel Santor * http://rommelsantor.com * * This plugin allows you to float a table's or keeping it * in view when it would normally be scrolled out of view. * * Copyright (c) 2011 by Rommel Santor * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. @*/ /** * >> Description << * The THFloat plugin for jQuery allows you to automatically float (or affix) * either a given table's , , or both at the top or bottom of a * scrolling container parent/ancestor so that they stay in view even though * the table's contents have been scrolled out of the visible area. * * This was developed and is very handy for long data tables with many columns, * as it allows users to always know which column contains which value without * the column header row having to be included repeatedly throughout the table * (this is often the typical solution). * * >> Requirements << * jQuery v1.4 or better * * >> Version History << * Ver 0.5 - 2012-08-10 - Rommel Santor * Added 'noprint' option to hide floating block when page is printed. * Thanks again to Daniel Gomez for spotting the need for that. * Ver 0.4 - 2012-08-09 - Rommel Santor * Daniel Gomez pointed out that when applying thfloat to multiple * tables on a page and multiple don't have an ID attribute, the * plugin fails. This bug is now fixed. * Ver 0.2 - 2011-04-14 - Rommel Santor * Added 'resize' method and made initial resize automatic; also * cleaned up how the floater is sized when scrolling to stay in * sync with the real table; removed widthOffset as it made the * floater look off most of the time * Ver 0.1.1 - 2011-03-07 - Rommel Santor * Fixed "return;" bug in init() * Ver 0.1 - 2011-03-01 - Rommel Santor * Initial Release * * >> Tested << * Mozilla (Firefox 3+) * Webkit (Chrome 9+, Safari for Windows 5+) * MSIE 7, 8, 9 * Opera 11+ * * >> Known issues << * - Safari for Mac apparently has some issues with table resizing; thanks to * Adi Fairbank for reporting this issue; unfixable by me for now to due lack of * development access on a Mac * - MSIE 9 ignores the cells' (inner) borders for some reason * - if you have any others, let me know */ /** * Methods: * .thfloat([options]) * .thfloat('init', [options]) - initialize THFloat on a new jQuery object * options : see "Options" below * * .thfloat('resize', [side]) - force the floating block to resize itself * and each cell contained within to match the parent table; useful utility * if you modify the original table contents and want to sync it with the floater * side : "head" or "foot"; defaults to both * * .thfloat('refresh', [side]) - force the floating block to refresh itself, as if * the container has scrolled; useful for tables in blocks that toggle visibility * side : "head" or "foot"; defaults to both * * .thfloat('destroy') - remove THFloat instance from jQuery object */ /** * Options: * side - the block of the table that is to be floated * default: "head" * "head" for * "foot" for * * attachment - the scrolling container to which the floated block is attached * default: window * string selector, DOM object, or jQuery object; * * noprint - do not display the floating block when the page is printed * default: true * boolean * * sticky - force the floating block visible even when source block is in view * (but not if the table is out of view entirely, of course) * default: false * boolean * * onShow - see "Overridable Events" below * * onHide - see "Overridable Events" below */ /** * Overridable Events: * onShow(table, block) - triggered just after a floating block is created * table : the floating holding the block and its content * block : the temporary or containing the content being floated * * onHide(table, block) - triggered as the floating block is about to be destroyed * table : the floating
holding the block and its content * block : the temporary or containing the content being floated */ /** * CSS Styles: * .thfloat-table * class is added to the cloned, floating
holding the cloned / * * .thfloat * class is added to each / while it is floated * * #thfloat[head|foot]-[table_id] (default; used if source table has an id) * #thfloathead - if side "head" and table has no id * #thfloatfoot - if side "foot" and table has no id * id is assigned to the floating table holding the fixed or $*/ /** * Example: * html * ---- * * * * *
*
* ...a row of header cells... * ...a bunch of content rows... * ...a row of footer cells... *
* * * css * --- * #thfloathead-floater { border-bottom: 2px solid black; } * #thfloatfoot-floater { border-top: 2px solid black; } * * javascript * ---------- * // make both the and float * $("#floater") * .thfloat({ * side : "head", * attachment : "#scroller" * }) * .thfloat({ * side : "foot", * attachment : "#scroller" * }); * * // ... do some stuff ... * * // destroy just the floater * $("#floater").thfloat('destroy', 'foot'); */ (function($){ // essential jQuery css() override by Keith Bentrup for cloning full styles between elements // http://stackoverflow.com/questions/1004475/jquery-css-plugin-that-returns-computed-style-of-element-to-pseudo-clone-that-ele // (there needs to be a better way to use this portably than overriding jQuery here) jQuery.fn.css2 = jQuery.fn.css; jQuery.fn.css = function() { if (arguments.length) return jQuery.fn.css2.apply(this, arguments); var attr = ['font-family','font-size','font-weight','font-style','color', 'text-transform','text-decoration','letter-spacing','word-spacing', 'line-height','text-align','vertical-align','direction','background-color', 'background-image','background-repeat','background-position', 'background-attachment','opacity','width','height','top','right','bottom', 'left','margin-top','margin-right','margin-bottom','margin-left', 'padding-top','padding-right','padding-bottom','padding-left', 'border-top-width','border-right-width','border-bottom-width', 'border-left-width','border-top-color','border-right-color', 'border-bottom-color','border-left-color','border-top-style', 'border-right-style','border-bottom-style','border-left-style','position', 'display','visibility','z-index','overflow-x','overflow-y','white-space', 'clip','float','clear','cursor','list-style-image','list-style-position', 'list-style-type','marker-offset']; var len = attr.length, obj = {}; for (var i = 0; i < len; i++) obj[attr[i]] = jQuery.fn.css2.call(this, attr[i]); return obj; } var methods = { init : function(options) { var settings = { side : 'head', // "head" for or "foot" for attachment : window, // selector, dom object, or jquery object - scrolling parent to attach to sticky : false, // stay floating always, not just when the source block is out of view noprint : true, // do not display when the page is printed onShow : function(table, block) {}, // called after the floating block ( or ) is created onHide : function(table, block) {} // called as the block is about to be hidden (actually deleted) }; if (typeof options === 'object') $.extend(settings, options); var $this = this, side = settings.side == 'foot' ? 'foot' : 'head', other = side == 'foot' ? 'head' : 'foot', ns = 'thfloat'+side, data = $this.data(ns) ; while (!data) { var $src = $("t"+side, this), $opp = $("t"+other, this) ; if (!$src.length) break; var id = ns+'-'+($this.attr('id')?$this.attr('id'):Math.floor(Math.random()*1000000)); data = { settings : settings, active : false, clonetbl : $(this).clone(true).attr({id:id}).addClass('thfloat-table').css({zIndex:'1000',display:'none',position:'absolute'}).appendTo('body'), srcblock : $src, oppblock : $opp.length ? $opp : null, thwidths : [], cloneblk : null }; if (settings.noprint) $('head').append(''); data.clonetbl.children().remove(); $this.data(ns, data); $(window) .bind('resize.'+ns, function(){ $.each(['thfloathead','thfloatfoot'], function(i, ns){ var data = $this.data(ns); if (!data) return; var thw = []; $("tr", data.srcblock).children().each(function(){ thw.push($(this).width()); }); data.thwidths = thw; $this.data(ns, data); $this.thfloat('_scroll', ns, $(data.settings.attachment)); }); }) .resize() ; var a = $(settings.attachment).bind('scroll.'+ns, function(){ $this.thfloat('_scroll', ns, this); }); $this.thfloat('_scroll', ns, a); break; } return $this; }, refresh : function(side) { var $this = $(this); $.each(['head', 'foot'], function(i, s){ if (side && side != s) return; var ns = 'thfloat'+s, data = $this.data(ns); if (data) $this.thfloat('_scroll', ns, data.settings.attachment); }); }, resize : function(side) { var $this = $(this); $.each(['head', 'foot'], function(i, s){ if (side && side != s) return; var ns = 'thfloat'+s, data = $this.data(ns); if (!data || !data.active) return; var thw = []; $("tr", data.srcblock).children().each(function(){ thw.push($(this).width()); }); var $el = $(data.settings.attachment), heightOffset = $.browser.mozilla || $.browser.opera || $.browser.msie ? -(($this.attr('cellspacing')||0)*2) : 0, edgeheight = s == 'foot' ? ((!$el.offset() ? $el.height() : $el.innerHeight()) - data.srcblock.outerHeight() + heightOffset) : 0, edge = !$el.offset() ? ($el.scrollTop() + edgeheight) : ($el.offset().top + edgeheight); data.clonetbl.css({top:edge+'px',left:$this.offset().left+"px",width:$this.width()+'px'}); $('tr', data.cloneblk).children().each(function(i){ $(this).css({width:thw[i]+'px',maxWidth:thw[i]+'px'}); }); data.thwidths = thw; $this.data(ns, data); }); }, destroy : function(side) { var $this = this; $.each(['thfloathead','thfloatfoot'], function(i, ns){ var data = $this.data(ns); if (!data || (side && ('thfloat'+side) != ns)) return; $(data.settings.attachment).unbind('.'+ns); $(window).unbind('.'+ns); data.clonetbl.remove(); data.cloneblk && data.cloneblk.remove(); $this.removeData(ns); }); return $this; }, _scroll : function(ns, element) { var $this = this, $el = $(element); var data = $this.data(ns); if (!data) return; var heightOffset = $.browser.mozilla || $.browser.opera || $.browser.msie ? -(($this.attr('cellspacing')||0)*2) : 0, edgeheight = data.settings.side == 'foot' ? ((!$el.offset() ? $el.height() : $el.innerHeight()) - data.srcblock.outerHeight() + heightOffset) : 0, edge = !$el.offset() ? ($el.scrollTop() + edgeheight) : ($el.offset().top + edgeheight), beyond = data.settings.side == 'foot' ? ((!data.settings.sticky && data.srcblock.offset().top < edge) || (!data.oppblock ? false : (data.oppblock.offset().top + data.oppblock.outerHeight() >= edge))) : ((!data.settings.sticky && data.srcblock.offset().top > edge) || (!data.oppblock ? false : (data.oppblock.offset().top <= edge + data.srcblock.outerHeight()))) ; if (!data.active) { if (!beyond) { data.active = true; data.clonetbl.css({display:($this.is(':visible')?'table':'none'),top:edge+'px',left:$this.offset().left+"px",marginTop:'0',marginBottom:'0',width:$this.width()+'px'}); data.cloneblk = data.srcblock.clone(true); data.cloneblk.addClass('thfloat'); // we need to not only clone the block and rows and cells, but // the CSS of each of those data.cloneblk.css(data.srcblock.css()); data.cloneblk.children().remove(); // copy each row and its styles $("tr", data.srcblock).each(function(){ // for each row, copy each cell and its styles var tr = $(this).clone(true).css($(this).css()); tr.children().remove(); $("td,th", this).each(function(){ tr.append($(this).clone(true).css($(this).css())); }); // finally add the new cloned row and cloned columns to our thead/tfoot block data.cloneblk.append(tr); }); data.cloneblk.appendTo(data.clonetbl); $this.thfloat('resize',data.settings.side); data.settings.onShow && data.settings.onShow.apply(this, [data.clonetbl, data.cloneblk]); } } else { if (data.clonetbl.is(':visible') != $this.is(':visible')) data.clonetbl.css({display:($this.is(':visible')?'table':'none')}); if (beyond) { data.settings.onHide && data.settings.onHide.apply(this, [data.clonetbl, data.cloneblk]); data.active = false; data.cloneblk.remove(); data.cloneblk = null; data.clonetbl.css({display:'none'}); } else $this.thfloat('resize',data.settings.side); } $this.data(ns, data); } }; $.fn.thfloat = function(method) { if (methods[method]) return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); else if (typeof method === 'object' || !method) return methods.init.apply(this, arguments); else $.error('Method ' + method + ' does not exist on jQuery.thfloat'); }; })(jQuery);