65d8ed9250ff29a81c6036ef5de83419ddab2570e2d19f380fd5333da0e8549b23a51b07f4a9ca02cfcb61c118aad8cdb3b6d677c0bce67fbc80e848cc4135 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661
  1. import {
  2. addClass,
  3. empty,
  4. getScrollbarWidth,
  5. hasClass,
  6. innerHeight,
  7. outerWidth
  8. } from './../../../helpers/dom/element';
  9. import Overlay from './overlay/_base';
  10. let performanceWarningAppeared = false;
  11. /**
  12. * @class TableRenderer
  13. */
  14. class TableRenderer {
  15. /**
  16. * @param {WalkontableTable} wtTable
  17. */
  18. constructor(wtTable) {
  19. this.wtTable = wtTable;
  20. this.wot = wtTable.instance;
  21. // legacy support
  22. this.instance = wtTable.instance;
  23. this.rowFilter = wtTable.rowFilter;
  24. this.columnFilter = wtTable.columnFilter;
  25. this.TABLE = wtTable.TABLE;
  26. this.THEAD = wtTable.THEAD;
  27. this.TBODY = wtTable.TBODY;
  28. this.COLGROUP = wtTable.COLGROUP;
  29. this.rowHeaders = [];
  30. this.rowHeaderCount = 0;
  31. this.columnHeaders = [];
  32. this.columnHeaderCount = 0;
  33. this.fixedRowsTop = 0;
  34. this.fixedRowsBottom = 0;
  35. }
  36. /**
  37. *
  38. */
  39. render() {
  40. if (!this.wtTable.isWorkingOnClone()) {
  41. const skipRender = {};
  42. this.wot.getSetting('beforeDraw', true, skipRender);
  43. if (skipRender.skipRender === true) {
  44. return;
  45. }
  46. }
  47. this.rowHeaders = this.wot.getSetting('rowHeaders');
  48. this.rowHeaderCount = this.rowHeaders.length;
  49. this.fixedRowsTop = this.wot.getSetting('fixedRowsTop');
  50. this.fixedRowsBottom = this.wot.getSetting('fixedRowsBottom');
  51. this.columnHeaders = this.wot.getSetting('columnHeaders');
  52. this.columnHeaderCount = this.columnHeaders.length;
  53. let columnsToRender = this.wtTable.getRenderedColumnsCount();
  54. let rowsToRender = this.wtTable.getRenderedRowsCount();
  55. let totalColumns = this.wot.getSetting('totalColumns');
  56. let totalRows = this.wot.getSetting('totalRows');
  57. let workspaceWidth;
  58. let adjusted = false;
  59. if (Overlay.isOverlayTypeOf(this.wot.cloneOverlay, Overlay.CLONE_BOTTOM) ||
  60. Overlay.isOverlayTypeOf(this.wot.cloneOverlay, Overlay.CLONE_BOTTOM_LEFT_CORNER)) {
  61. // do NOT render headers on the bottom or bottom-left corner overlay
  62. this.columnHeaders = [];
  63. this.columnHeaderCount = 0;
  64. }
  65. if (totalColumns >= 0) {
  66. // prepare COL and TH elements for rendering
  67. this.adjustAvailableNodes();
  68. adjusted = true;
  69. // adjust column widths according to user widths settings
  70. this.renderColumnHeaders();
  71. // Render table rows
  72. this.renderRows(totalRows, rowsToRender, columnsToRender);
  73. if (!this.wtTable.isWorkingOnClone()) {
  74. workspaceWidth = this.wot.wtViewport.getWorkspaceWidth();
  75. this.wot.wtViewport.containerWidth = null;
  76. }
  77. this.adjustColumnWidths(columnsToRender);
  78. this.markOversizedColumnHeaders();
  79. this.adjustColumnHeaderHeights();
  80. }
  81. if (!adjusted) {
  82. this.adjustAvailableNodes();
  83. }
  84. this.removeRedundantRows(rowsToRender);
  85. if (!this.wtTable.isWorkingOnClone() || this.wot.isOverlayName(Overlay.CLONE_BOTTOM)) {
  86. this.markOversizedRows();
  87. }
  88. if (!this.wtTable.isWorkingOnClone()) {
  89. this.wot.wtViewport.createVisibleCalculators();
  90. this.wot.wtOverlays.refresh(false);
  91. this.wot.wtOverlays.applyToDOM();
  92. let hiderWidth = outerWidth(this.wtTable.hider);
  93. let tableWidth = outerWidth(this.wtTable.TABLE);
  94. if (hiderWidth !== 0 && (tableWidth !== hiderWidth)) {
  95. // Recalculate the column widths, if width changes made in the overlays removed the scrollbar, thus changing the viewport width.
  96. this.adjustColumnWidths(columnsToRender);
  97. }
  98. if (workspaceWidth !== this.wot.wtViewport.getWorkspaceWidth()) {
  99. // workspace width changed though to shown/hidden vertical scrollbar. Let's reapply stretching
  100. this.wot.wtViewport.containerWidth = null;
  101. let firstRendered = this.wtTable.getFirstRenderedColumn();
  102. let lastRendered = this.wtTable.getLastRenderedColumn();
  103. let defaultColumnWidth = this.wot.getSetting('defaultColumnWidth');
  104. let rowHeaderWidthSetting = this.wot.getSetting('rowHeaderWidth');
  105. rowHeaderWidthSetting = this.instance.getSetting('onModifyRowHeaderWidth', rowHeaderWidthSetting);
  106. if (rowHeaderWidthSetting != null) {
  107. for (let i = 0; i < this.rowHeaderCount; i++) {
  108. let width = Array.isArray(rowHeaderWidthSetting) ? rowHeaderWidthSetting[i] : rowHeaderWidthSetting;
  109. width = width == null ? defaultColumnWidth : width;
  110. this.COLGROUP.childNodes[i].style.width = `${width}px`;
  111. }
  112. }
  113. for (let i = firstRendered; i < lastRendered; i++) {
  114. let width = this.wtTable.getStretchedColumnWidth(i);
  115. let renderedIndex = this.columnFilter.sourceToRendered(i);
  116. this.COLGROUP.childNodes[renderedIndex + this.rowHeaderCount].style.width = `${width}px`;
  117. }
  118. }
  119. this.wot.getSetting('onDraw', true);
  120. } else if (this.wot.isOverlayName(Overlay.CLONE_BOTTOM)) {
  121. this.wot.cloneSource.wtOverlays.adjustElementsSize();
  122. }
  123. }
  124. /**
  125. * @param {Number} renderedRowsCount
  126. */
  127. removeRedundantRows(renderedRowsCount) {
  128. while (this.wtTable.tbodyChildrenLength > renderedRowsCount) {
  129. this.TBODY.removeChild(this.TBODY.lastChild);
  130. this.wtTable.tbodyChildrenLength--;
  131. }
  132. }
  133. /**
  134. * @param {Number} totalRows
  135. * @param {Number} rowsToRender
  136. * @param {Number} columnsToRender
  137. */
  138. renderRows(totalRows, rowsToRender, columnsToRender) {
  139. let lastTD,
  140. TR;
  141. let visibleRowIndex = 0;
  142. let sourceRowIndex = this.rowFilter.renderedToSource(visibleRowIndex);
  143. let isWorkingOnClone = this.wtTable.isWorkingOnClone();
  144. while (sourceRowIndex < totalRows && sourceRowIndex >= 0) {
  145. if (!performanceWarningAppeared && visibleRowIndex > 1000) {
  146. performanceWarningAppeared = true;
  147. console.warn('Performance tip: Handsontable rendered more than 1000 visible rows. Consider limiting the number ' +
  148. 'of rendered rows by specifying the table height and/or turning off the "renderAllRows" option.');
  149. }
  150. if (rowsToRender !== void 0 && visibleRowIndex === rowsToRender) {
  151. // We have as much rows as needed for this clone
  152. break;
  153. }
  154. TR = this.getOrCreateTrForRow(visibleRowIndex, TR);
  155. // Render row headers
  156. this.renderRowHeaders(sourceRowIndex, TR);
  157. // Add and/or remove TDs to TR to match the desired number
  158. this.adjustColumns(TR, columnsToRender + this.rowHeaderCount);
  159. lastTD = this.renderCells(sourceRowIndex, TR, columnsToRender);
  160. if (!isWorkingOnClone ||
  161. // Necessary to refresh oversized row heights after editing cell in overlays
  162. this.wot.isOverlayName(Overlay.CLONE_BOTTOM)) {
  163. // Reset the oversized row cache for this row
  164. this.resetOversizedRow(sourceRowIndex);
  165. }
  166. if (TR.firstChild) {
  167. // if I have 2 fixed columns with one-line content and the 3rd column has a multiline content, this is
  168. // the way to make sure that the overlay will has same row height
  169. let height = this.wot.wtTable.getRowHeight(sourceRowIndex);
  170. if (height) {
  171. // Decrease height. 1 pixel will be "replaced" by 1px border top
  172. height--;
  173. TR.firstChild.style.height = `${height}px`;
  174. } else {
  175. TR.firstChild.style.height = '';
  176. }
  177. }
  178. visibleRowIndex++;
  179. sourceRowIndex = this.rowFilter.renderedToSource(visibleRowIndex);
  180. }
  181. }
  182. /**
  183. * Reset the oversized row cache for the provided index
  184. *
  185. * @param {Number} sourceRow Row index
  186. */
  187. resetOversizedRow(sourceRow) {
  188. if (this.wot.getSetting('externalRowCalculator')) {
  189. return;
  190. }
  191. if (this.wot.wtViewport.oversizedRows && this.wot.wtViewport.oversizedRows[sourceRow]) {
  192. this.wot.wtViewport.oversizedRows[sourceRow] = void 0;
  193. }
  194. }
  195. /**
  196. * Check if any of the rendered rows is higher than expected, and if so, cache them
  197. */
  198. markOversizedRows() {
  199. if (this.wot.getSetting('externalRowCalculator')) {
  200. return;
  201. }
  202. let rowCount = this.instance.wtTable.TBODY.childNodes.length;
  203. let expectedTableHeight = rowCount * this.instance.wtSettings.settings.defaultRowHeight;
  204. let actualTableHeight = innerHeight(this.instance.wtTable.TBODY) - 1;
  205. let previousRowHeight;
  206. let rowInnerHeight;
  207. let sourceRowIndex;
  208. let currentTr;
  209. let rowHeader;
  210. let totalRows = this.instance.getSetting('totalRows');
  211. if (expectedTableHeight === actualTableHeight && !this.instance.getSetting('fixedRowsBottom')) {
  212. // If the actual table height equals rowCount * default single row height, no row is oversized -> no need to iterate over them
  213. return;
  214. }
  215. while (rowCount) {
  216. rowCount--;
  217. sourceRowIndex = this.instance.wtTable.rowFilter.renderedToSource(rowCount);
  218. previousRowHeight = this.instance.wtTable.getRowHeight(sourceRowIndex);
  219. currentTr = this.instance.wtTable.getTrForRow(sourceRowIndex);
  220. rowHeader = currentTr.querySelector('th');
  221. if (rowHeader) {
  222. rowInnerHeight = innerHeight(rowHeader);
  223. } else {
  224. rowInnerHeight = innerHeight(currentTr) - 1;
  225. }
  226. if ((!previousRowHeight && this.instance.wtSettings.settings.defaultRowHeight < rowInnerHeight ||
  227. previousRowHeight < rowInnerHeight)) {
  228. this.instance.wtViewport.oversizedRows[sourceRowIndex] = ++rowInnerHeight;
  229. }
  230. }
  231. }
  232. /**
  233. * Check if any of the rendered columns is higher than expected, and if so, cache them.
  234. */
  235. markOversizedColumnHeaders() {
  236. let overlayName = this.wot.getOverlayName();
  237. if (!this.columnHeaderCount || this.wot.wtViewport.hasOversizedColumnHeadersMarked[overlayName] || this.wtTable.isWorkingOnClone()) {
  238. return;
  239. }
  240. let columnCount = this.wtTable.getRenderedColumnsCount();
  241. for (let i = 0; i < this.columnHeaderCount; i++) {
  242. for (let renderedColumnIndex = (-1) * this.rowHeaderCount; renderedColumnIndex < columnCount; renderedColumnIndex++) {
  243. this.markIfOversizedColumnHeader(renderedColumnIndex);
  244. }
  245. }
  246. this.wot.wtViewport.hasOversizedColumnHeadersMarked[overlayName] = true;
  247. }
  248. /**
  249. *
  250. */
  251. adjustColumnHeaderHeights() {
  252. let columnHeaders = this.wot.getSetting('columnHeaders');
  253. let children = this.wot.wtTable.THEAD.childNodes;
  254. let oversizedColumnHeaders = this.wot.wtViewport.oversizedColumnHeaders;
  255. for (let i = 0, len = columnHeaders.length; i < len; i++) {
  256. if (oversizedColumnHeaders[i]) {
  257. if (!children[i] || children[i].childNodes.length === 0) {
  258. return;
  259. }
  260. children[i].childNodes[0].style.height = `${oversizedColumnHeaders[i]}px`;
  261. }
  262. }
  263. }
  264. /**
  265. * Check if column header for the specified column is higher than expected, and if so, cache it
  266. *
  267. * @param {Number} col Index of column
  268. */
  269. markIfOversizedColumnHeader(col) {
  270. let sourceColIndex = this.wot.wtTable.columnFilter.renderedToSource(col);
  271. let level = this.columnHeaderCount;
  272. let defaultRowHeight = this.wot.wtSettings.settings.defaultRowHeight;
  273. let previousColHeaderHeight;
  274. let currentHeader;
  275. let currentHeaderHeight;
  276. let columnHeaderHeightSetting = this.wot.getSetting('columnHeaderHeight') || [];
  277. while (level) {
  278. level--;
  279. previousColHeaderHeight = this.wot.wtTable.getColumnHeaderHeight(level);
  280. currentHeader = this.wot.wtTable.getColumnHeader(sourceColIndex, level);
  281. if (!currentHeader) {
  282. /* eslint-disable no-continue */
  283. continue;
  284. }
  285. currentHeaderHeight = innerHeight(currentHeader);
  286. if (!previousColHeaderHeight && defaultRowHeight < currentHeaderHeight || previousColHeaderHeight < currentHeaderHeight) {
  287. this.wot.wtViewport.oversizedColumnHeaders[level] = currentHeaderHeight;
  288. }
  289. if (Array.isArray(columnHeaderHeightSetting)) {
  290. if (columnHeaderHeightSetting[level] != null) {
  291. this.wot.wtViewport.oversizedColumnHeaders[level] = columnHeaderHeightSetting[level];
  292. }
  293. } else if (!isNaN(columnHeaderHeightSetting)) {
  294. this.wot.wtViewport.oversizedColumnHeaders[level] = columnHeaderHeightSetting;
  295. }
  296. if (this.wot.wtViewport.oversizedColumnHeaders[level] < (columnHeaderHeightSetting[level] || columnHeaderHeightSetting)) {
  297. this.wot.wtViewport.oversizedColumnHeaders[level] = (columnHeaderHeightSetting[level] || columnHeaderHeightSetting);
  298. }
  299. }
  300. }
  301. /**
  302. * @param {Number} sourceRowIndex
  303. * @param {HTMLTableRowElement} TR
  304. * @param {Number} columnsToRender
  305. * @returns {HTMLTableCellElement}
  306. */
  307. renderCells(sourceRowIndex, TR, columnsToRender) {
  308. let TD;
  309. let sourceColIndex;
  310. for (let visibleColIndex = 0; visibleColIndex < columnsToRender; visibleColIndex++) {
  311. sourceColIndex = this.columnFilter.renderedToSource(visibleColIndex);
  312. if (visibleColIndex === 0) {
  313. TD = TR.childNodes[this.columnFilter.sourceColumnToVisibleRowHeadedColumn(sourceColIndex)];
  314. } else {
  315. TD = TD.nextSibling; // http://jsperf.com/nextsibling-vs-indexed-childnodes
  316. }
  317. // If the number of headers has been reduced, we need to replace excess TH with TD
  318. if (TD.nodeName == 'TH') {
  319. TD = replaceThWithTd(TD, TR);
  320. }
  321. if (!hasClass(TD, 'hide')) {
  322. TD.className = '';
  323. }
  324. TD.removeAttribute('style');
  325. this.wot.wtSettings.settings.cellRenderer(sourceRowIndex, sourceColIndex, TD);
  326. }
  327. return TD;
  328. }
  329. /**
  330. * @param {Number} columnsToRender Number of columns to render.
  331. */
  332. adjustColumnWidths(columnsToRender) {
  333. let scrollbarCompensation = 0;
  334. let sourceInstance = this.wot.cloneSource ? this.wot.cloneSource : this.wot;
  335. let mainHolder = sourceInstance.wtTable.holder;
  336. let defaultColumnWidth = this.wot.getSetting('defaultColumnWidth');
  337. let rowHeaderWidthSetting = this.wot.getSetting('rowHeaderWidth');
  338. if (mainHolder.offsetHeight < mainHolder.scrollHeight) {
  339. scrollbarCompensation = getScrollbarWidth();
  340. }
  341. this.wot.wtViewport.columnsRenderCalculator.refreshStretching(this.wot.wtViewport.getViewportWidth() - scrollbarCompensation);
  342. rowHeaderWidthSetting = this.instance.getSetting('onModifyRowHeaderWidth', rowHeaderWidthSetting);
  343. if (rowHeaderWidthSetting != null) {
  344. for (let i = 0; i < this.rowHeaderCount; i++) {
  345. let width = Array.isArray(rowHeaderWidthSetting) ? rowHeaderWidthSetting[i] : rowHeaderWidthSetting;
  346. width = width == null ? defaultColumnWidth : width;
  347. this.COLGROUP.childNodes[i].style.width = `${width}px`;
  348. }
  349. }
  350. for (let renderedColIndex = 0; renderedColIndex < columnsToRender; renderedColIndex++) {
  351. let width = this.wtTable.getStretchedColumnWidth(this.columnFilter.renderedToSource(renderedColIndex));
  352. this.COLGROUP.childNodes[renderedColIndex + this.rowHeaderCount].style.width = `${width}px`;
  353. }
  354. }
  355. /**
  356. * @param {HTMLTableCellElement} TR
  357. */
  358. appendToTbody(TR) {
  359. this.TBODY.appendChild(TR);
  360. this.wtTable.tbodyChildrenLength++;
  361. }
  362. /**
  363. * @param {Number} rowIndex
  364. * @param {HTMLTableRowElement} currentTr
  365. * @returns {HTMLTableCellElement}
  366. */
  367. getOrCreateTrForRow(rowIndex, currentTr) {
  368. let TR;
  369. if (rowIndex >= this.wtTable.tbodyChildrenLength) {
  370. TR = this.createRow();
  371. this.appendToTbody(TR);
  372. } else if (rowIndex === 0) {
  373. TR = this.TBODY.firstChild;
  374. } else {
  375. // http://jsperf.com/nextsibling-vs-indexed-childnodes
  376. TR = currentTr.nextSibling;
  377. }
  378. if (TR.className) {
  379. TR.removeAttribute('class');
  380. }
  381. return TR;
  382. }
  383. /**
  384. * @returns {HTMLTableCellElement}
  385. */
  386. createRow() {
  387. let TR = document.createElement('TR');
  388. for (let visibleColIndex = 0; visibleColIndex < this.rowHeaderCount; visibleColIndex++) {
  389. TR.appendChild(document.createElement('TH'));
  390. }
  391. return TR;
  392. }
  393. /**
  394. * @param {Number} row
  395. * @param {Number} col
  396. * @param {HTMLTableCellElement} TH
  397. */
  398. renderRowHeader(row, col, TH) {
  399. TH.className = '';
  400. TH.removeAttribute('style');
  401. this.rowHeaders[col](row, TH, col);
  402. }
  403. /**
  404. * @param {Number} row
  405. * @param {HTMLTableCellElement} TR
  406. */
  407. renderRowHeaders(row, TR) {
  408. for (let TH = TR.firstChild, visibleColIndex = 0; visibleColIndex < this.rowHeaderCount; visibleColIndex++) {
  409. // If the number of row headers increased we need to create TH or replace an existing TD node with TH
  410. if (!TH) {
  411. TH = document.createElement('TH');
  412. TR.appendChild(TH);
  413. } else if (TH.nodeName == 'TD') {
  414. TH = replaceTdWithTh(TH, TR);
  415. }
  416. this.renderRowHeader(row, visibleColIndex, TH);
  417. // http://jsperf.com/nextsibling-vs-indexed-childnodes
  418. TH = TH.nextSibling;
  419. }
  420. }
  421. /**
  422. * Adjust the number of COL and TH elements to match the number of columns and headers that need to be rendered
  423. */
  424. adjustAvailableNodes() {
  425. this.adjustColGroups();
  426. this.adjustThead();
  427. }
  428. /**
  429. * Renders the column headers
  430. */
  431. renderColumnHeaders() {
  432. if (!this.columnHeaderCount) {
  433. return;
  434. }
  435. let columnCount = this.wtTable.getRenderedColumnsCount();
  436. for (let i = 0; i < this.columnHeaderCount; i++) {
  437. let TR = this.getTrForColumnHeaders(i);
  438. for (let renderedColumnIndex = (-1) * this.rowHeaderCount; renderedColumnIndex < columnCount; renderedColumnIndex++) {
  439. let sourceCol = this.columnFilter.renderedToSource(renderedColumnIndex);
  440. this.renderColumnHeader(i, sourceCol, TR.childNodes[renderedColumnIndex + this.rowHeaderCount]);
  441. }
  442. }
  443. }
  444. /**
  445. * Adjusts the number of COL elements to match the number of columns that need to be rendered
  446. */
  447. adjustColGroups() {
  448. let columnCount = this.wtTable.getRenderedColumnsCount();
  449. while (this.wtTable.colgroupChildrenLength < columnCount + this.rowHeaderCount) {
  450. this.COLGROUP.appendChild(document.createElement('COL'));
  451. this.wtTable.colgroupChildrenLength++;
  452. }
  453. while (this.wtTable.colgroupChildrenLength > columnCount + this.rowHeaderCount) {
  454. this.COLGROUP.removeChild(this.COLGROUP.lastChild);
  455. this.wtTable.colgroupChildrenLength--;
  456. }
  457. if (this.rowHeaderCount) {
  458. addClass(this.COLGROUP.childNodes[0], 'rowHeader');
  459. }
  460. }
  461. /**
  462. * Adjusts the number of TH elements in THEAD to match the number of headers and columns that need to be rendered
  463. */
  464. adjustThead() {
  465. let columnCount = this.wtTable.getRenderedColumnsCount();
  466. let TR = this.THEAD.firstChild;
  467. if (this.columnHeaders.length) {
  468. for (let i = 0, len = this.columnHeaders.length; i < len; i++) {
  469. TR = this.THEAD.childNodes[i];
  470. if (!TR) {
  471. TR = document.createElement('TR');
  472. this.THEAD.appendChild(TR);
  473. }
  474. this.theadChildrenLength = TR.childNodes.length;
  475. while (this.theadChildrenLength < columnCount + this.rowHeaderCount) {
  476. TR.appendChild(document.createElement('TH'));
  477. this.theadChildrenLength++;
  478. }
  479. while (this.theadChildrenLength > columnCount + this.rowHeaderCount) {
  480. TR.removeChild(TR.lastChild);
  481. this.theadChildrenLength--;
  482. }
  483. }
  484. let theadChildrenLength = this.THEAD.childNodes.length;
  485. if (theadChildrenLength > this.columnHeaders.length) {
  486. for (let i = this.columnHeaders.length; i < theadChildrenLength; i++) {
  487. this.THEAD.removeChild(this.THEAD.lastChild);
  488. }
  489. }
  490. } else if (TR) {
  491. empty(TR);
  492. }
  493. }
  494. /**
  495. * @param {Number} index
  496. * @returns {HTMLTableCellElement}
  497. */
  498. getTrForColumnHeaders(index) {
  499. return this.THEAD.childNodes[index];
  500. }
  501. /**
  502. * @param {Number} row
  503. * @param {Number} col
  504. * @param {HTMLTableCellElement} TH
  505. * @returns {*}
  506. */
  507. renderColumnHeader(row, col, TH) {
  508. TH.className = '';
  509. TH.removeAttribute('style');
  510. return this.columnHeaders[row](col, TH, row);
  511. }
  512. /**
  513. * Add and/or remove the TDs to match the desired number
  514. *
  515. * @param {HTMLTableCellElement} TR Table row in question
  516. * @param {Number} desiredCount The desired number of TDs in the TR
  517. */
  518. adjustColumns(TR, desiredCount) {
  519. let count = TR.childNodes.length;
  520. while (count < desiredCount) {
  521. let TD = document.createElement('TD');
  522. TR.appendChild(TD);
  523. count++;
  524. }
  525. while (count > desiredCount) {
  526. TR.removeChild(TR.lastChild);
  527. count--;
  528. }
  529. }
  530. /**
  531. * @param {Number} columnsToRender
  532. */
  533. removeRedundantColumns(columnsToRender) {
  534. while (this.wtTable.tbodyChildrenLength > columnsToRender) {
  535. this.TBODY.removeChild(this.TBODY.lastChild);
  536. this.wtTable.tbodyChildrenLength--;
  537. }
  538. }
  539. }
  540. function replaceTdWithTh(TD, TR) {
  541. let TH = document.createElement('TH');
  542. TR.insertBefore(TH, TD);
  543. TR.removeChild(TD);
  544. return TH;
  545. }
  546. function replaceThWithTd(TH, TR) {
  547. let TD = document.createElement('TD');
  548. TR.insertBefore(TD, TH);
  549. TR.removeChild(TH);
  550. return TD;
  551. }
  552. export default TableRenderer;