cornflourblue / angu-fixed-header-table

AngularJS fixed header scrollable table directive
http://pointblankdevelopment.com.au/blog/angularjs-fixed-header-scrollable-table-directive
MIT License
67 stars 43 forks source link

3 issues and 2 enhancements #12

Closed Dorian-Fusco closed 9 years ago

Dorian-Fusco commented 9 years ago

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';
                        }
                    }
                });
            }
        }
    }
})();

Hope this helps :)

Dorian-Fusco commented 9 years ago

Found how to make a pull request... See : https://github.com/cornflourblue/angu-fixed-header-table/pull/13