get-headers.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. import isRowHeader from './is-row-header';
  2. import isColumnHeader from './is-column-header';
  3. import toGrid from './to-grid';
  4. import getCellPosition from './get-cell-position';
  5. import idrefs from '../dom/idrefs';
  6. import findUp from '../dom/find-up';
  7. /**
  8. * Loop through the table grid looking for headers and caching the result.
  9. * @param {String} headerType The type of header to look for ("row" or "col")
  10. * @param {Object} position The position of the cell to start looking
  11. * @param {Array} tablegrid A matrix of the table obtained using axe.commons.table.toGrid
  12. * @return {Array<HTMLTableCellElement>} Array of HTMLTableCellElements that are headers
  13. */
  14. function traverseForHeaders(headerType, position, tableGrid) {
  15. const property = headerType === 'row' ? '_rowHeaders' : '_colHeaders';
  16. const predicate = headerType === 'row' ? isRowHeader : isColumnHeader;
  17. const startCell = tableGrid[position.y][position.x];
  18. // adjust position by rowspan and colspan
  19. // subtract 1 from col/rowspan to make them 0 indexed
  20. const colspan = startCell.colSpan - 1;
  21. // ie11 returns 1 as the rowspan value even if it's set to 0
  22. const rowspanAttr = startCell.getAttribute('rowspan');
  23. const rowspanValue =
  24. parseInt(rowspanAttr) === 0 || startCell.rowspan === 0
  25. ? tableGrid.length
  26. : startCell.rowSpan;
  27. const rowspan = rowspanValue - 1;
  28. const rowStart = position.y + rowspan;
  29. const colStart = position.x + colspan;
  30. const rowEnd = headerType === 'row' ? position.y : 0;
  31. const colEnd = headerType === 'row' ? 0 : position.x;
  32. let headers;
  33. const cells = [];
  34. for (let row = rowStart; row >= rowEnd && !headers; row--) {
  35. for (let col = colStart; col >= colEnd; col--) {
  36. const cell = tableGrid[row] ? tableGrid[row][col] : undefined;
  37. if (!cell) {
  38. continue;
  39. }
  40. // stop traversing once we've found a cache
  41. const vNode = axe.utils.getNodeFromTree(cell);
  42. if (vNode[property]) {
  43. headers = vNode[property];
  44. break;
  45. }
  46. cells.push(cell);
  47. }
  48. }
  49. // need to check that the cells we've traversed are headers
  50. headers = (headers || []).concat(cells.filter(predicate));
  51. // cache results
  52. cells.forEach(tableCell => {
  53. const vNode = axe.utils.getNodeFromTree(tableCell);
  54. vNode[property] = headers;
  55. });
  56. return headers;
  57. }
  58. /**
  59. * Get any associated table headers for a `HTMLTableCellElement`
  60. * @method getHeaders
  61. * @memberof axe.commons.table
  62. * @instance
  63. * @param {HTMLTableCellElement} cell The cell of which to get headers
  64. * @param {Array} [tablegrid] A matrix of the table obtained using axe.commons.table.toGrid
  65. * @return {Array<HTMLTableCellElement>} Array of headers associated to the table cell
  66. */
  67. function getHeaders(cell, tableGrid) {
  68. if (cell.getAttribute('headers')) {
  69. const headers = idrefs(cell, 'headers');
  70. // testing has shown that if the headers attribute is incorrect the browser
  71. // will default to the table row/column headers
  72. if (headers.filter(header => header).length) {
  73. return headers;
  74. }
  75. }
  76. if (!tableGrid) {
  77. tableGrid = toGrid(findUp(cell, 'table'));
  78. }
  79. const position = getCellPosition(cell, tableGrid);
  80. // TODO: RTL text
  81. const rowHeaders = traverseForHeaders('row', position, tableGrid);
  82. const colHeaders = traverseForHeaders('col', position, tableGrid);
  83. return [].concat(rowHeaders, colHeaders).reverse();
  84. }
  85. export default getHeaders;