/**
* v0.6 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.6 - 2013-04-11 - Rommel Santor
* Reimplementing jQuery.browser if it's not available (it was removed
* in jQuery v1.9)
* 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
* ----
*
*
*
*
*
*
* 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;
}
/**
* jQuery.browser for 1.9+
* We all love feature detection but that's sometimes not enough.
* @author Tero Piirainen
*/
if (!$.browser) {
var b = $.browser = {},
ua = navigator.userAgent.toLowerCase(),
match = /(chrome)[ \/]([\w.]+)/.exec(ua) ||
/(webkit)[ \/]([\w.]+)/.exec(ua) ||
/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) ||
/(msie) ([\w.]+)/.exec(ua) ||
ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || [];
if (match[1]) {
b[match[1]] = true;
b.version = match[2] || "0";
}
}
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);