text: /*! Copyright (c) Jonas Mosbech - https://github.com/jmosbech/StickyTableHeaders
  	MIT license info: https://github.com/jmosbech/StickyTableHeaders/blob/master/license.txt */
  
  ;(function ($, window, undefined) {
  	'use strict';
  
  	var name = 'stickyTableHeaders',
  		id = 0,
  		defaults = {
  			fixedOffset: 0,
  			leftOffset: 0,
  			marginTop: 0,
  			objDocument: document,
  			objHead: 'head',
  			objWindow: window,
  			scrollableArea: window,
  			setStylesOnce: false,
  			cacheHeights:  true,
  			flushHeightAfter: 0,
  			flushHeightEvery: 5000,
  			cacheHeaderHeight: false,
  			cacheHeaderWidth: false,
  			cacheClippingContainerWidth: false,
  			zIndex: 3,
  			clippingContainerId: null,
  			dynamicTopOffset: function() { return 0; }
  		};
  
  	function Plugin (el, options) {
  		// To avoid scope issues, use 'base' instead of 'this'
  		// to reference this class from internal events and functions.
  		var base = this;
  
  		// Access to jQuery and DOM versions of element
  		base.$el  = $(el);
  		base.$els = [];
  		base.el   = el;
  		base.id   = id++;
  		base.heights = [];
  
  		// Listen for destroyed, call teardown
  		base.$el.bind('destroyed',
  			$.proxy(base.teardown, base));
  
  		// Cache DOM refs for performance reasons
  		base.$clonedHeader = null;
  		base.$originalHeader = null;
  
  		// Cache header height, width and clippingContainer width for performance reasons
  		base.cachedHeaderHeight = null;
  		base.cachedHeaderWidth = null;
  		base.cachedClippingContainerWidth = null;
  
  		// Keep track of state
  		base.isSticky = false;
  		base.hasBeenSticky = false;
  		base.leftOffset = null;
  		base.topOffset = null;
  
  		base.resetHeights = function() {
  			base.$el.each(function (idx, elem) {
  				base.heights[idx] = {table : null, cHeader : null};
  			});
  		}
  		base.delayedReset = $.noop;
  
  		base.init = function () {
  			base.setOptions(options);
  
  			base.$el.each(function (idx, elem) {
  				var $this = $(this);
  				base.$els[idx] = $this;
  
  				// remove padding on <table> to fix issue #7
  				$this.css('padding', 0);
  
  				base.$originalHeader = $('thead:first', this);
  				base.$clonedHeader = base.$originalHeader.clone();
  				$this.trigger('clonedHeader.' + name, [base.$clonedHeader]);
  
  				base.$clonedHeader.addClass('tableFloatingHeader');
  				base.$clonedHeader.css({display: 'none', opacity: 0.0});
  
  				base.$originalHeader.addClass('tableFloatingHeaderOriginal');
  
  				base.changeClonedHeaderIds(base.$clonedHeader);
  				base.$originalHeader.after(base.$clonedHeader);
  
  				base.$printStyle = $('<style type="text/css" media="print">' +
  					'.tableFloatingHeader{display:none !important;}' +
  					'.tableFloatingHeaderOriginal{position:static !important;}' +
  					'</style>');
  				base.$head.append(base.$printStyle);
  			});
  
  			base.$clonedHeader.find("input, select").attr("disabled", true);
  
  			base.toggleHeaders({type:'init'});
  			base.updateWidth();
  			
  			var updatingWidth = false;
  
  			angular.element( base.$scrollableArea ).on('scroll', function() {
  				if (updatingWidth === false) {
  					updatingWidth = true;
  					setTimeout(function(){
  						updatingWidth = false;
  						base.updateWidth();
  					},650);
  				}
  			});
  
  			angular.element( window ).resize(function() {
  				if (updatingWidth === false) {
  					updatingWidth = true;
  					setTimeout(function(){
  						updatingWidth = false;
  						base.updateWidth();
  					},650);
  				}
  	    		});
  			
  			base.updateHeaderCssPropertyClip();
  			base.bind();
  			if (base.options.cacheHeights && base.options.flushHeightAfter)
  				setTimeout(base.resetHeights, base.options.flushHeightAfter);
  			if (base.options.cacheHeights && base.options.flushHeightEvery)
  				base.delayedReset = (function(func, every) {
  					var timeout = 0;
  					return function() {
  							if (timeout)
  								return false;
   							var res = func();
  							timeout = setTimeout(function(){ timeout = 0; }, every);
  							return res;
  						}
  				})(base.resetHeights, base.options.flushHeightEvery);
  		};
  
  		base.changeClonedHeaderIds = function($clonedHeader) {
  			$clonedHeader.find('*').addBack().filter('[id]').each(function() {
  				var $component = $(this);
  				$component.attr('id', $component.attr('id') + '-' + name + '-cloned');
  			});
  		};
  
  		base.destroy = function (){
  			base.$el.unbind('destroyed', base.teardown);
  			base.teardown();
  		};
  
  		base.teardown = function(){
  			if (base.isSticky) {
  				base.$originalHeader.css('position', 'static');
  			}
  			$.removeData(base.el, 'plugin_' + name);
  			base.unbind();
  
  			base.$clonedHeader.remove();
  			base.$originalHeader.removeClass('tableFloatingHeaderOriginal');
  			base.$originalHeader.css('visibility', 'visible');
  			base.$printStyle.remove();
  
  			base.el = null;
  			base.$el = null;
  		};
  
  		base.bind = function(){
  			base.$scrollableArea.on('scroll.' + name, base.debouncedToggleHeaders);
  			if (!base.isWindowScrolling) {
  				base.$window.on('scroll.' + name + base.id, base.setPositionValues);
  				base.$window.on('scroll.' + name + base.id, base.debouncedToggleHeaders);
  				base.$window.on('resize.' + name + base.id, base.debouncedToggleHeaders);
  			}
  			base.$scrollableArea.on('resize.' + name, base.debouncedToggleHeaders);
  			base.$scrollableArea.on('resize.' + name, base.debouncedUpdateWidth);
  		};
  
  		base.unbind = function(){
  			// unbind window events by specifying handle so we don't remove too much
  			base.$scrollableArea.off('.' + name, base.debouncedToggleHeaders);
  			if (!base.isWindowScrolling) {
  				base.$window.off('.' + name + base.id, base.setPositionValues);
  				base.$window.off('.' + name + base.id, base.debouncedToggleHeaders);
  			}
  			base.$scrollableArea.off('.' + name, base.debouncedUpdateWidth);
  		};
  
  		// We debounce the functions bound to the scroll and resize events
  		base.debounce = function (fn, delay) {
  			return function () {
  				var context = this, args = arguments;
  				fn.apply(context, args);
  			};
  		};
  
  		base.getCached = function($elem, what, idx, key)
  		{
  			if (base[what + 's'][idx][key])
  				return base[what + 's'][idx][key];
  			else
  				return base[what + 's'][idx][key] = $elem[what]();
  		};
  
  		base.toggleHeaders = function (ev) {
  			if ('scroll' == ev.type)
  				base.delayedReset();
  			else
  				base.resetHeights();
  			if (base.$el) {
  				base.$el.each(function (idx, elem) {
  					var $this = base.$els[idx],
  						dynamicTopOffset = base.options.dynamicTopOffset(),
  						newTopOffset = dynamicTopOffset + (base.isWindowScrolling ? (
  									isNaN(base.options.fixedOffset) ?
  									base.options.fixedOffset.outerHeight() :
  									base.options.fixedOffset
  								) :
  								base.$scrollableArea.offset().top + (!isNaN(base.options.fixedOffset) ? base.options.fixedOffset : 0)),
  						offset = $this.offset(),
  
  						scrollTop = base.$scrollableArea.scrollTop() + newTopOffset,
  						scrollLeft = base.$window.scrollLeft(),
  
  						headerHeight,
  
  						scrolledPastTop = base.isWindowScrolling ?
  								scrollTop > offset.top :
  								newTopOffset > offset.top,
  						notScrolledPastBottom;
  
  					if (scrolledPastTop) {
  						headerHeight = base.getHeaderHeight();
  						notScrolledPastBottom = (base.isWindowScrolling ? scrollTop : 0) <
  						(
  								offset.top +
  								(
  									base.options.cacheHeights && ev /* Filling cache only when scrolling */ ?
  
  											base.getCached($this, 'height', idx, 'table') :
  											$this.height()
  								) -
  								(
  									base.options.cacheHeights && ev ?
  											base.getCached(base.$clonedHeader, 'height', idx, 'cHeader') :
  											base.$clonedHeader.height()
  								) -
  								(base.isWindowScrolling ? 0 : newTopOffset)
  							);
  					}
  
  					if (scrolledPastTop && notScrolledPastBottom) {
  						base.leftOffset = offset.left - scrollLeft + base.options.leftOffset;
  						base.topOffset = newTopOffset;
  						var doSet = !base.options.setStylesOnce || !base.isSticky || base.leftOffset != base.options.leftOffset;
  						if (doSet) {
  
  							base.$originalHeader.css({
  								'position': 'fixed',
  								'margin-top': base.options.marginTop,
  								'top': base.topOffset - (base.isWindowScrolling ? 0 : base.$window.scrollTop()),
  								'left': base.leftOffset,
  								'z-index': base.options.zIndex
  							});
  							base.$clonedHeader.css('display', '');
  							base.updateHeaderCssPropertyClip();
  						}
  						if (!base.isSticky) {
  							base.isSticky = true;
  							// make sure the width is correct: the user might have resized the browser while in static mode
  							base.updateWidth();
  							$this.trigger('enabledStickiness.' + name);
  						}
  						if (doSet) {
  							base.setPositionValues();
  						}
  					} else if (base.isSticky) {
  						base.$originalHeader.css('position', 'static');
  						base.$clonedHeader.css('display', 'none');
  						base.isSticky = false;
  						base.resetWidth($('td,th', base.$clonedHeader), $('td,th', base.$originalHeader));
  						$this.trigger('disabledStickiness.' + name);
  					}
  				});
  			}
  		};
  
  		base.debouncedToggleHeaders = base.debounce(base.toggleHeaders, 0);
  
  		base.setPositionValues = base.debounce(function () {
  			var winScrollTop = base.$window.scrollTop(),
  				winScrollLeft = base.$window.scrollLeft();
  			if (!base.isSticky ||
  					winScrollTop < 0 || winScrollTop + base.$window.height() > base.$document.height() ||
  					winScrollLeft < 0 || winScrollLeft + base.$window.width() > base.$document.width()) {
  				return;
  			}
  			base.$originalHeader.css({
  				'top': base.topOffset - (base.isWindowScrolling ? 0 : winScrollTop),
  				'left': base.leftOffset
  			});
  		}, 0);
  
  		base.updateWidth = function () {
  			if (!base.isSticky) {
  				return;
  			}
  			// Copy cell widths from clone
  			if (!base.$originalHeaderCells) {
  				base.$originalHeaderCells = $('th,td', base.$originalHeader);
  			}
  			if (!base.$clonedHeaderCells) {
  				base.$clonedHeaderCells = $('th,td', base.$clonedHeader);
  			}
  			var cellWidths = base.getWidth(base.$clonedHeaderCells);
  			base.setWidth(cellWidths, base.$clonedHeaderCells, base.$originalHeaderCells);
  
  			// Copy row width from whole table
  			base.$originalHeader.css('width', base.$clonedHeader.width());
  
  			// If we're caching the height or width, we need to update the cached values when the width or height changes.
  			if (base.options.cacheHeaderHeight) {
  				base.cachedHeaderHeight = base.$clonedHeader.height();
  			}
  			if (base.options.cacheHeaderWidth) {
  				base.cachedHeaderWidth = base.$clonedHeader.width();
  			}
  		};
  
  		base.debouncedUpdateWidth = base.debounce(base.updateWidth, 0);
  
  		base.updateHeaderCssPropertyClip = function() {
  			if (base.$clippingContainer.length === 0) {
  				return;
  			}
  			var container = base.$clippingContainer,
  				containerOffset = container.offset(),
  				headerOffset = base.$originalHeader.offset(),
  				fromHeaderToContainer = containerOffset.left - headerOffset.left,
  				left = Math.max(0, fromHeaderToContainer),
  				right = Math.max(0, Math.min(base.getHeaderWidth(), fromHeaderToContainer + base.clippingContainerWidth())),
  				top = 0,
  				bottom = base.getHeaderHeight();
  			var clipString = 'rect(' + top + 'px,' + right + 'px,' + bottom + 'px,' + left + 'px)';
  			base.$originalHeader.css('clip', clipString);
  		};
  
  		base.clippingContainerWidth = function() {
  			if (!base.cachedClippingContainerWidth || !base.options.cacheClippingContainerWidth) {
  				base.cachedClippingContainerWidth = base.$clippingContainer.width();
  			}
  			return base.cachedClippingContainerWidth;
  		}
  
  		base.getWidth = function ($clonedHeaders) {
  			var widths = [];
  			$clonedHeaders.each(function (index) {
  				var width, $this = $(this);
  
  				if ($this.css('box-sizing') === 'border-box') {
  					var boundingClientRect = $this[0].getBoundingClientRect();
  					if(boundingClientRect.width) {
  						width = boundingClientRect.width; // #39: border-box bug
  					} else {
  						width = boundingClientRect.right - boundingClientRect.left; // ie8 bug: getBoundingClientRect() does not have a width property
  					}
  				} else {
  					var $origTh = $('th', base.$originalHeader);
  					if ($origTh.css('border-collapse') === 'collapse') {
  						if (window.getComputedStyle) {
  							width = parseFloat(window.getComputedStyle(this, null).width);
  						} else {
  							// ie8 only
  							var leftPadding = parseFloat($this.css('padding-left'));
  							var rightPadding = parseFloat($this.css('padding-right'));
  							// Needs more investigation - this is assuming constant border around this cell and it's neighbours.
  							var border = parseFloat($this.css('border-width'));
  							width = $this.outerWidth() - leftPadding - rightPadding - border;
  						}
  					} else {
  						width = $this.width();
  					}
  				}
  
  				widths[index] = width;
  			});
  			return widths;
  		};
  
  		base.setWidth = function (widths, $clonedHeaders, $origHeaders) {
  			$clonedHeaders.each(function (index) {
  				var width = widths[index];
  				$origHeaders.eq(index).css({
  					'min-width': width,
  					'max-width': width
  				});
  			});
  		};
  
  		base.resetWidth = function ($clonedHeaders, $origHeaders) {
  			$clonedHeaders.each(function (index) {
  				var $this = $(this);
  				$origHeaders.eq(index).css({
  					'min-width': $this.css('min-width'),
  					'max-width': $this.css('max-width')
  				});
  			});
  		};
  
  		base.getHeaderHeight = function() {
  			 return base.options.cacheHeaderHeight ? base.cachedHeaderHeight : base.$clonedHeader.height() + 2; // +2 for border
  		}
  
  		base.getHeaderWidth = function() {
  			return base.options.cacheHeaderWidth ? base.cachedHeaderWidth : base.$clonedHeader.width();
  		}
  
  		base.setOptions = function (options) {
  			base.options = $.extend({}, defaults, options);
  			base.$window = $(base.options.objWindow);
  			base.$head = $(base.options.objHead);
  			base.$document = $(base.options.objDocument);
  			base.$scrollableArea = $(base.options.scrollableArea);
  			base.isWindowScrolling = base.$scrollableArea[0] === base.$window[0];
  			base.$clippingContainer = $('#' + base.options.clippingContainerId);
  		};
  
  		base.updateOptions = function (options) {
  			base.setOptions(options);
  			// scrollableArea might have changed
  			base.unbind();
  			base.bind();
  			base.updateWidth();
  			base.toggleHeaders({type:'update'});
  		};
  
  		// Run initializer
  		base.init();
  	}
  
  	// A plugin wrapper around the constructor,
  	// preventing against multiple instantiations
  	$.fn[name] = function ( options ) {
  		return this.each(function () {
  			var instance = $.data(this, 'plugin_' + name);
  			if (instance) {
  				if (typeof options === 'string') {
  					instance[options].apply(instance);
  				} else {
  					instance.updateOptions(options);
  				}
  			} else if(options !== 'destroy') {
  				$.data(this, 'plugin_' + name, new Plugin( this, options ));
  			}
  		});
  	};
  
  })(jQuery, window);
  