本文转自:http://pointblankdevelopment.com.au/blog/angularjs-fixed-header-scrollable-table-directive
This post contains a custom AngularJS directive you can use to give your html table a fixed header and footer with a scrollable body, we developed it for a law firm marketing website recently, it uses a pure CSS approach and doesn't touch any of the html tags, leaving the html table completely intact and happily semantic :)
Creating a fixed header scrollable table using purely CSS turns out to be a surprisingly tricky thing to do, in an ideal world I thought it would just be a matter of setting the height of the table body and "overflow:hidden", but there turns out to be a bit more to it than that, especially if you have a table that contains dynamic content because the width of each column in the thead and tbody need to be set in order for it to continue looking like a table and not just a big mess.
In a nutshell the CSS changes that need to happen are:
- Set the width of each column in the thead and tbody, making sure they match up so the columns aren't wonky
- Set the thead and tbody to "display:block;"
- Set the tbody height and "overflow:auto;" to add the scrollbar
- When there's a scrollbar (when the tbody content overflows it's height), reduce the width of the final column in the tbody by the width of the scrollbar
Here's the solution I came up with:
- Install using Bower:
bower install angu-fixed-header-table
- See on Plunker at http://plnkr.co/edit/YIohJ7?p=preview)
- Available on GitHub at https://github.com/cornflourblue/angu-fixed-header-table
The fixedHeader AngularJS directive
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
|
/**
* AngularJS fixed header scrollable table directive
* @author Jason Watmore <jason@pointblankdevelopment.com.au> (http://jasonwatmore.com)
* @version 1.2.0
*/
(
function
() {
angular
.module(
'anguFixedHeaderTable'
, [])
.directive(
'fixedHeader'
, fixedHeader);
fixedHeader.$inject = [
'$timeout'
];
function
fixedHeader($timeout) {
return
{
restrict:
'A'
,
link: link
};
function
link($scope, $elem, $attrs, $ctrl) {
var
elem = $elem[0];
// wait for data to load and then transform the table
$scope.$watch(tableDataLoaded,
function
(isTableDataLoaded) {
if
(isTableDataLoaded) {
transformTable();
}
});
function
tableDataLoaded() {
// first cell in the tbody exists when data is loaded but doesn't have a width
// until after the table is transformed
var
firstCell = elem.querySelector('tbody tr:first-child td:first-child
');
return firstCell && !firstCell.style.width;
}
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 () {
// set widths of columns
angular.forEach(elem.querySelectorAll('
tr:first-child th
'), function (thElem, i) {
var tdElems = elem.querySelector('
tbody tr:first-child td:nth-child(
' + (i + 1) + '
)
');
var tfElems = elem.querySelector('
tfoot tr:first-child td:nth-child(
' + (i + 1) + '
)
');
var columnWidth = tdElems ? tdElems.offsetWidth : thElem.offsetWidth;
if (tdElems) {
tdElems.style.width = columnWidth + '
px
';
}
if (thElem) {
thElem.style.width = columnWidth + '
px
';
}
if (tfElems) {
tfElems.style.width = columnWidth + '
px
';
}
});
// set css styles on thead and tbody
angular.element(elem.querySelectorAll('
thead, tfoot
')).css('
display
', '
block
');
angular.element(elem.querySelectorAll('
tbody
')).css({
'
display
': '
block
',
'
height
': $attrs.tableHeight || '
inherit
',
'
overflow
': '
auto
'
});
// reduce width of last column by width of scrollbar
var tbody = elem.querySelector('
tbody
');
var scrollBarWidth = tbody.offsetWidth - tbody.clientWidth;
if (scrollBarWidth > 0) {
// for some reason trimming the width by 2px lines everything up better
scrollBarWidth -= 2;
var lastColumn = elem.querySelector('
tbody tr:first-child td:last-child
');
lastColumn.style.width = (lastColumn.offsetWidth - scrollBarWidth) + '
px';
}
});
}
}
}
})();
|
Sample HTML that uses the fixed-header directive
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
<
table
class
=
"table table-bordered"
fixed-header>
<
thead
>
<
tr
>
<
th
>Header 1</
th
>
<
th
>Header 2</
th
>
<
th
>Header 3</
th
>
<
th
>Header 4</
th
>
</
tr
>
</
thead
>
<
tbody
>
<
tr
ng-repeat
=
"item in [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]"
>
<
td
>Row {{item}} Col 1</
td
>
<
td
>Row {{item}} Col 2</
td
>
<
td
>Row {{item}} Col 3</
td
>
<
td
>Row {{item}} Col 4</
td
>
</
tr
>
</
tbody
>
<
tfoot
>
<
tr
>
<
td
>Footer 1</
td
>
<
td
>Footer 2</
td
>
<
td
>Footer 3</
td
>
<
td
>Footer 4</
td
>
</
tr
>
</
tfoot
>
</
table
>
|
The default height of the table body is 400px, to change this add a table-height attribute to the table element eg: table-height="500px".
UPDATE 08/10/2014: Added support for fixed footer (tfoot) element.