I encountered and fixed 3 issues and made an enhancement (but didn't do a pull request because I'm actually not really used to github)
First issue : I had bootstrap datepickers in my thead. These directive create tables (to display a calendar). Angu-fixed-header-table was "fixing" this calendar's td widths instead of my table using angu-fixed-header-table.
Second issue : the scrollbar problem - I tweaked it more than fixed it : instead of reducing the size of the last td of tbody, I widened the last td of thead and tfoot. It worked better and didn't need any adjustement. The behaviour changes juste a bit, but I do believe it's better this way.
Third issue : the table's header css were refreshed only when the first cell of tbody had no width set. As a result, if I was setting it through another directive, it failed to refresh. To fix it in the most reliable way, I had to add a scope. So now, I MUST set a "table-data" attribute wich is an Array object containing all the datas displayed in my table. As a result, anytime my table-data changes, the tHead's css is recalculated.
First enhancement : before I changed it, setting the tableHeight attribute was just like fixing the table height. Now it's like setting table max-height (since max-height would be ignored otherwise). However, you now have to set the attribute like this :
<table fixed-header table-height=300>
Second enhancement : you used to change the thead / tbody / tfoot display to '' and back to 'block' every time transformTable was called. The thing is that I wanted to use your directive along with an ngInfiniteScroll directive to scroll as much a I wanted on my table and still have my headers there. Since I loaded more data in my array, my table headers had to adapt to the new column's width, thus applying the transformTable function wich changes the display -even for a split second- and made the scrollbar disappeared. The display-swap had an effect similar to forcing the user to drop the scrollbar, he had to click it again to scroll some more.
Instead of changing the display, I create a clone of elem and operate everything that was requiring the display to be back to '' on that clone. Once done, I delete the clone.
Although human eye shouldn't be able to see the clone (since I had to append the clone to the table's parent to get it's actual width, but I remove it as soon as I can), I made sure it wouldn't mess the page, even for that split second by putting the clone in a wrapper div with height set to 0 px and overflow hidden + visibility set to invisible (wich is a bit of an overkill I guess)
Anyway, here is the new code for the transformTable function in your directive :
(function () {
angular
.module('anguFixedHeaderTable', [])
.directive('fixedHeader', fixedHeader);
fixedHeader.$inject = ['$timeout'];
function fixedHeader($timeout) {
return {
restrict: 'A',
scope: {tableData : '='},
link: link
};
function link($scope, $elem, $attrs, $ctrl)
{
var elem = $elem[0];
$scope.$watch('tableData', function(newValue)
{
if (Array.isArray(newValue))
{
transformTable();
}
}, true);
function transformTable()
{
// reset display styles so column widths are correct when measured below
//angular.element(elem.querySelectorAll('thead, tbody, tfoot')).css('display', '');
// wrap in $timeout to give table a chance to finish rendering
$timeout(function ()
{
//instead of doing any calculs on elem, we will do them on a clone of him !
//This way we won't change the display of the table itself, thus not enabling / disabling scrolling (wich makes the cursor 'drop' the scrollbar)
var clone = elem.cloneNode(true);
var wrapper= document.createElement('div');
wrapper.style.height = '0px';
wrapper.style.overflow = 'hidden';
wrapper.style.visibility = 'invisible';
wrapper.appendChild(clone);
angular.element(clone.querySelectorAll('thead, tbody, tfoot')).css('display', '');
$elem.parent()[0].appendChild(wrapper);
// set widths of columns
angular.forEach(clone.querySelectorAll('tr:first-child th:not(:last-child)'), function (clonedThElem, i)
{
var clonedTdElems = clone.querySelector('table[fixed-header]>tbody tr:first-child th:nth-child(' + (i + 1) + '), table[fixed-header]>tbody tr:first-child td:nth-child(' + (i + 1) + ')');
var columnWidth = clonedTdElems ? clonedTdElems.offsetWidth : clonedThElem.offsetWidth;
var tdElems = elem.querySelector('table[fixed-header]>tbody tr:first-child th:nth-child(' + (i + 1) + '), table[fixed-header]>tbody tr:first-child td:nth-child(' + (i + 1) + ')');
var thElems = elem.querySelector('table[fixed-header]>thead tr:first-child th:nth-child(' + (i + 1) + '), table[fixed-header]>thead tr:first-child td:nth-child(' + (i + 1) + ')');
var tfElems = elem.querySelector('table[fixed-header]>tfoot tr:first-child th:nth-child(' + (i + 1) + '), table[fixed-header]>tfoot tr:first-child td:nth-child(' + (i + 1) + ')');
if (tdElems)
{
tdElems.style.width = columnWidth + 'px';
}
if (thElems)
{
thElems.style.width = columnWidth + 'px';
}
if (tfElems)
{
tfElems.style.width = columnWidth + 'px';
}
});
//Done with the math ! We can just get rid of the clone
wrapper.remove();
// set css styles on thead and tbody
angular.element(elem.querySelectorAll('table[fixed-header]>thead, table[fixed-header]>tfoot')).css('display', 'block');
angular.element(elem.querySelector('table[fixed-header]>tbody')).css({
'display': 'block',
'height': 'inherit',
'overflow': 'auto'
});
// reduce width of last column by width of scrollbar
var tbody = elem.querySelector('table[fixed-header]>tbody');
if($attrs.tableHeight !== undefined && tbody.clientHeight > $attrs.tableHeight)
{
tbody.style.height = $attrs.tableHeight + 'px';
}
else
{
tbody.style.height = 'inherit';
}
var scrollBarWidth = tbody.offsetWidth - tbody.clientWidth;
var tBodyLastColumn = elem.querySelector('table[fixed-header]>tbody tr:first-child th:last-child, table[fixed-header]>tbody tr:first-child td:last-child');
var tHeadLastColumn = elem.querySelector('table[fixed-header]>thead tr:first-child th:last-child, table[fixed-header]>thead tr:first-child td:last-child');
var tFootLastColumn = elem.querySelector('table[fixed-header]>tfoot tr:first-child th:last-child, table[fixed-header]>tfoot tr:first-child td:last-child');
if (scrollBarWidth > 0)
{
if(tHeadLastColumn && tBodyLastColumn)
{
tHeadLastColumn.style.width = (tBodyLastColumn.offsetWidth + scrollBarWidth) + 'px';
}
if(tFootLastColumn && tBodyLastColumn)
{
tFootLastColumn.style.width = (tBodyLastColumn.offsetWidth + scrollBarWidth) + 'px';
}
if(tHeadLastColumn && tBodyLastColumn)
{
tBodyLastColumn.style.width = tHeadLastColumn.offsetWidth - scrollBarWidth + 'px';
}
}
else
{
if(tHeadLastColumn && tBodyLastColumn)
{
tBodyLastColumn.style.width = tHeadLastColumn.offsetWidth + 'px';
}
}
});
}
}
}
})();
I encountered and fixed 3 issues and made an enhancement (but didn't do a pull request because I'm actually not really used to github)
First issue : I had bootstrap datepickers in my thead. These directive create tables (to display a calendar). Angu-fixed-header-table was "fixing" this calendar's td widths instead of my table using angu-fixed-header-table.
Second issue : the scrollbar problem - I tweaked it more than fixed it : instead of reducing the size of the last td of tbody, I widened the last td of thead and tfoot. It worked better and didn't need any adjustement. The behaviour changes juste a bit, but I do believe it's better this way.
Third issue : the table's header css were refreshed only when the first cell of tbody had no width set. As a result, if I was setting it through another directive, it failed to refresh. To fix it in the most reliable way, I had to add a scope. So now, I MUST set a "table-data" attribute wich is an Array object containing all the datas displayed in my table. As a result, anytime my table-data changes, the tHead's css is recalculated.
First enhancement : before I changed it, setting the tableHeight attribute was just like fixing the table height. Now it's like setting table max-height (since max-height would be ignored otherwise). However, you now have to set the attribute like this :
<table fixed-header table-height=300>
Second enhancement : you used to change the thead / tbody / tfoot display to '' and back to 'block' every time transformTable was called. The thing is that I wanted to use your directive along with an ngInfiniteScroll directive to scroll as much a I wanted on my table and still have my headers there. Since I loaded more data in my array, my table headers had to adapt to the new column's width, thus applying the transformTable function wich changes the display -even for a split second- and made the scrollbar disappeared. The display-swap had an effect similar to forcing the user to drop the scrollbar, he had to click it again to scroll some more. Instead of changing the display, I create a clone of elem and operate everything that was requiring the display to be back to '' on that clone. Once done, I delete the clone. Although human eye shouldn't be able to see the clone (since I had to append the clone to the table's parent to get it's actual width, but I remove it as soon as I can), I made sure it wouldn't mess the page, even for that split second by putting the clone in a wrapper div with height set to 0 px and overflow hidden + visibility set to invisible (wich is a bit of an overkill I guess)
Anyway, here is the new code for the transformTable function in your directive :
Hope this helps :)