Skip to content

Instantly share code, notes, and snippets.

@bbrown
Last active August 26, 2016 14:53
Show Gist options
  • Select an option

  • Save bbrown/6bd7811ec95ee6993b98 to your computer and use it in GitHub Desktop.

Select an option

Save bbrown/6bd7811ec95ee6993b98 to your computer and use it in GitHub Desktop.
Angular directive that makes a table's thead fixed at top of screen on scrolling
angular.module("app").directive("fixedHeader", function($timeout, $window)
{
return {
restrict: "A",
scope: false,
link: function(scope, element, attrs)
{
var waitTime = attrs.fixedHeader || 0;
var collectionToWatch = attrs.fhCollection;
var tableTop, topPos, tableLeft;
var widths = [];
function setup()
{
var IS_MOBILE = ("ontouchstart" in document.documentElement) || document.documentElement.clientWidth < 1024;
tableTop = element[0].offsetTop;
// Handles the mobile nav header's fixed position
topPos = (!IS_MOBILE) ? 0 : 50;
tableLeft = element[0].offsetLeft;
// Save the current widths of the thead th's
for (var i = 0, l = element[0].tHead.firstElementChild.cells.length; i < l; i++)
{
widths.push(element[0].tHead.firstElementChild.cells[i].offsetWidth);
}
}
// Wait for data binding to complete before getting coordinates
if (collectionToWatch)
{
var cw = scope.$watch(collectionToWatch, function(newValue, oldValue)
{
if (newValue && newValue.length > 0)
{
// Run the setup on next tick since we know that the collection is set
// and the table rendered (though not shown yet)
$timeout(setup, 0);
cw();
}
});
}
else
{
// If no collection is associated, just use either the provided timeout period
// or 0 for next tick
$timeout(setup, waitTime);
}
// Use the thead's tr to clone since it has a height after changing to fixed
var thead = element[0].tHead.firstElementChild;
// The thead clone is created and removed on scrolling to keep the offsetHeight's constant
var theadClone = angular.element(thead).clone().css({ position:"static", top:"0px" });
// This variable reduces the DOM manipulation to just once during the positioning change
var madeTheadFixed = false;
function handleScroll()
{
// Handle vertical scrolling
if (!madeTheadFixed && $window.pageYOffset > tableTop)
{
madeTheadFixed = true;
// Explicitly set the widths of the th's to prevent shrinkage
widths.forEach(function(width, index)
{
thead.cells[index].style.width = width + "px";
// console.log("was", thead.cells[index].offsetWidth, "is", width);
});
thead.style.position = "fixed";
thead.style.top = topPos + "px";
thead.style.backgroundColor = "#000";
thead.style.zIndex = "10";
theadClone.css("height", thead.offsetHeight + "px");
element.addClass("fh-scrolling");
angular.element(thead).parent().append(theadClone);
}
else if (madeTheadFixed && $window.pageYOffset < tableTop)
{
madeTheadFixed = false;
thead.style.position = "static";
thead.style.top = "0px";
theadClone.remove();
element.removeClass("fh-scrolling");
}
// Handle horizontal scrolling
if ($window.pageXOffset > 0)
{
thead.style.left = (tableLeft - $window.pageXOffset) + "px";
}
else
{
thead.style.left = tableLeft + "px";
}
}
angular.element($window).on("scroll", handleScroll);
element.addClass("fh");
// Clean up after self
scope.$on("$destroy", function()
{
angular.element($window).off("scroll", handleScroll);
tableTop = undefined;
tableLeft = undefined;
thead = undefined;
});
}
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment