Skip to content

Commit 93ec723

Browse files
committed
feat(directive): support nested tables
Closes #14
1 parent ce2054c commit 93ec723

File tree

4 files changed

+169
-19
lines changed

4 files changed

+169
-19
lines changed

examples/index.html

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
<html ng-app="app">
33
<head>
44
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
56
<script src="../bower_components/angular/angular.js"></script>
67
<script src="../release/angular-responsive-tables.js"></script>
78
<script src="controller.js"></script>
@@ -229,5 +230,54 @@ <h3>Simple table with no thead, tbody</h3>
229230
<td>{{item.stars}}</td>
230231
</tr>
231232
</table>
233+
234+
<h2>Nested tables</h2>
235+
236+
<h3>Non-responsive nested table</h3>
237+
<table wt-responsive-table>
238+
<thead>
239+
<tr>
240+
<th>First title</th>
241+
<th>Second title</th>
242+
</tr>
243+
</thead>
244+
<tbody>
245+
<tr>
246+
<td>First column</td>
247+
<td>
248+
<table border=1>
249+
<tr>
250+
<th>nested title</th>
251+
<td>nested table</td>
252+
</tr>
253+
</table>
254+
</td>
255+
</tr>
256+
</tbody>
257+
</table>
258+
259+
<h3>Responsive nested table</h3>
260+
<table wt-responsive-table>
261+
<thead>
262+
<tr>
263+
<th>First title</th>
264+
<th>Second title</th>
265+
</tr>
266+
</thead>
267+
<tbody>
268+
<tr>
269+
<td>First column</td>
270+
<td>
271+
<table wt-responsive-table>
272+
<tr>
273+
<th>nested title</th>
274+
<td>nested table</td>
275+
</tr>
276+
</table>
277+
</td>
278+
</tr>
279+
</tbody>
280+
</table>
281+
232282
</body>
233283
</html>

src/directive.js

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,22 @@
11
'use strict';
22

3+
function getFirstHeaderInRow(tr) {
4+
var th = tr.firstChild;
5+
while (th) {
6+
if (th.tagName === 'TH') break;
7+
if (th.tagName === 'TD') {
8+
th = null;
9+
break;
10+
}
11+
th = th.nextSibling;
12+
}
13+
return th;
14+
}
15+
316
function getHeaders(element) {
4-
return element.querySelectorAll('tr > th');
17+
return [].filter.call(element.children().children().children(), function (it) {
18+
return it.tagName === 'TH';
19+
});
520
}
621

722
function updateTitle(td, th) {
@@ -22,36 +37,54 @@ function wtResponsiveTable() {
2237
restrict: 'A',
2338
controller: ['$element', function ($element) {
2439
return {
40+
contains: function (td) {
41+
var tableEl = $element[0];
42+
var el = td;
43+
do {
44+
if (el === tableEl) return true;
45+
if (el.tagName === 'TABLE') return false;
46+
47+
el = el.parentElement;
48+
} while (el);
49+
throw new Error('Table element not found for ' + td);
50+
},
51+
2552
getHeader: function (td) {
26-
var firstHeader = td.parentElement.querySelector('th');
53+
var firstHeader = getFirstHeaderInRow(td.parentElement);
2754
if (firstHeader) return firstHeader;
2855

29-
var headers = getHeaders($element[0]);
56+
var headers = getHeaders($element);
3057
if (headers.length) {
3158
var row = td.parentElement;
3259
var headerIndex = 0;
33-
var found = Array.prototype.some.call(row.querySelectorAll('td'), function (value, index) {
60+
var found = Array.prototype.some.call(row.children, function (value, index) {
61+
if (value.tagName !== 'TD') return false;
3462
if (value === td) {
3563
return true;
3664
}
3765

3866
headerIndex += colspan(value);
3967
});
4068

41-
return found ? headers.item(headerIndex) : null;
69+
return found ? headers[headerIndex] : null;
4270
}
4371
},
4472
}
4573
}],
4674
compile: function (element, attrs) {
4775
attrs.$addClass('responsive');
48-
var headers = getHeaders(element[0]);
76+
var headers = getHeaders(element);
4977
if (headers.length) {
50-
var rows = element[0].querySelectorAll('tbody > tr');
78+
var rows = [].filter.call(element.children(), function (it) {
79+
return it.tagName === 'TBODY';
80+
})[0].children;
5181
Array.prototype.forEach.call(rows, function(row) {
5282
var headerIndex = 0;
53-
Array.prototype.forEach.call(row.querySelectorAll('td'), function (value, index) {
54-
var th = value.parentElement.querySelector('th') || headers.item(headerIndex);
83+
[].forEach.call(row.children, function (value, index) {
84+
if (value.tagName !== 'TD') return;
85+
86+
var th = getFirstHeaderInRow(value.parentElement);
87+
th = th || headers[headerIndex];
5588
updateTitle(value, th);
5689

5790
headerIndex += colspan(value);
@@ -68,9 +101,12 @@ function wtResponsiveDynamic() {
68101
require: '?^^wtResponsiveTable',
69102
link: function (scope, element, attrs, tableCtrl) {
70103
if (!tableCtrl) return;
104+
if (!tableCtrl.contains(element[0])) return;
71105

72106
setTimeout(function () {
73-
Array.prototype.forEach.call(element[0].parentElement.querySelectorAll("td"), function (td) {
107+
[].forEach.call(element[0].parentElement.children, function (td) {
108+
if (td.tagName !== 'TD') return;
109+
74110
var th = tableCtrl.getHeader(td);
75111
updateTitle(td, th);
76112
});

src/style.css

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,25 @@
99

1010
/* Force table to not be like tables anymore */
1111
/* table.responsive,*/
12-
.responsive thead,
13-
.responsive tbody,
14-
.responsive tr,
15-
.responsive th {
12+
.responsive > thead,
13+
.responsive > tbody,
14+
.responsive > tbody > tr,
15+
.responsive > thead > th {
1616
display: block;
1717
}
1818

1919
/* Hide table headers (but not display: none;, for accessibility) */
20-
.responsive thead tr, .responsive th {
20+
.responsive > thead > tr, .responsive > thead > tr > th, .responsive > tbody > tr > th {
2121
position: absolute;
2222
top: -9999px;
2323
left: -9999px;
2424
}
2525

26-
.responsive tr {
26+
.responsive > tbody > tr {
2727
border: 1px solid #ccc;
2828
}
2929

30-
.responsive td:nth-child(odd), .responsive td:nth-child(even) {
30+
.responsive > tbody > tr > td {
3131
/* Behave like a "row" */
3232
border: none;
3333
border-bottom: 1px solid #eee;
@@ -43,7 +43,7 @@
4343
min-height: 1em;
4444
}
4545

46-
.responsive td:nth-child(odd)::before, .responsive td:nth-child(even)::before {
46+
.responsive > tbody > tr > td::before {
4747
/* Now like a table header */
4848
position: absolute;
4949
/* Top/left values mimic padding */

tests/directive.spec.js

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ describe('directive', function () {
4343
$compile(element);
4444
$rootScope.$digest();
4545

46-
var headerRow = element.find('tr th');
46+
var headerRow = element.find('th');
4747
expect(headerRow.is(':visible')).toBe(true);
4848
expect(headerRow.is(':offscreen')).toBe(true);
4949
});
@@ -357,6 +357,70 @@ describe('directive', function () {
357357
$rootScope.$digest();
358358

359359
expect(styles.paddingLeft).toBe('50%');
360+
element.remove();
361+
});
362+
363+
describe('nested tables', function () {
364+
365+
var element;
366+
beforeEach(function () {
367+
var markup = [
368+
'<table wt-responsive-table>',
369+
' <thead>',
370+
' <tr>',
371+
' <th>First title</th>',
372+
' <th>Second title</th>',
373+
' </tr>',
374+
' </thead>',
375+
' <tbody>',
376+
' <tr>',
377+
' <td>First column</td>',
378+
' <td>',
379+
' <table>',
380+
' <tr><th>nested title</th></tr>',
381+
' <tr><td>nested table</td></tr>',
382+
' </table>',
383+
' </td>',
384+
' </tr>',
385+
' </tbody>',
386+
'</table>'
387+
].join('');
388+
element = angular.element(markup);
389+
390+
var scope = $rootScope.$new();
391+
$compile(element)(scope);
392+
scope.$digest();
393+
});
394+
395+
it('nested tables are ignored', function (done) {
396+
var tds = element.find('table td');
397+
398+
setTimeout(function () {
399+
var content = Array.prototype.map.call(tds, function (item) {
400+
return item.textContent;
401+
});
402+
expect(content).toEqual(['nested table']);
403+
var titles = Array.prototype.map.call(tds, function (item) {
404+
return item.getAttribute('data-title');
405+
});
406+
expect(titles).toEqual([null]);
407+
done();
408+
}, 0);
409+
});
410+
411+
it('nested tables does not match responsive CSS', function (done) {
412+
setTimeout(function () {
413+
angular.element("body").append(element);
414+
var thStyles = getComputedStyle(element.find('table th')[0]);
415+
expect(thStyles.position).toBe('static');
416+
var tdStyles = getComputedStyle(element.find('table td')[0]);
417+
expect(tdStyles.display).toBe('table-cell');
418+
var pseudoElStyles = getComputedStyle(element.find('table td')[0], '::before');
419+
expect(pseudoElStyles.position).toBe('static');
420+
element.remove();
421+
done();
422+
}, 100);
423+
});
360424
});
361425

362426
describe('responsive-dynamic', function () {

0 commit comments

Comments
 (0)