bd1b55fd0d96abc4f0569fcd76d0b87522417114ae51c4ee9434a36564afaf7c5c17a7451e2a1325349587ae7547e5adfaa8844db01eaf4e953a57cc3d9b71 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  1. import {
  2. getScrollbarWidth,
  3. getScrollTop,
  4. getStyle,
  5. offset,
  6. outerHeight,
  7. outerWidth,
  8. } from './../../../helpers/dom/element';
  9. import {objectEach} from './../../../helpers/object';
  10. import EventManager from './../../../eventManager';
  11. import ViewportColumnsCalculator from './calculator/viewportColumns';
  12. import ViewportRowsCalculator from './calculator/viewportRows';
  13. /**
  14. * @class Viewport
  15. */
  16. class Viewport {
  17. /**
  18. * @param wotInstance
  19. */
  20. constructor(wotInstance) {
  21. this.wot = wotInstance;
  22. // legacy support
  23. this.instance = this.wot;
  24. this.oversizedRows = [];
  25. this.oversizedColumnHeaders = [];
  26. this.hasOversizedColumnHeadersMarked = {};
  27. this.clientHeight = 0;
  28. this.containerWidth = NaN;
  29. this.rowHeaderWidth = NaN;
  30. this.rowsVisibleCalculator = null;
  31. this.columnsVisibleCalculator = null;
  32. this.eventManager = new EventManager(this.wot);
  33. this.eventManager.addEventListener(window, 'resize', () => {
  34. this.clientHeight = this.getWorkspaceHeight();
  35. });
  36. }
  37. /**
  38. * @returns {number}
  39. */
  40. getWorkspaceHeight() {
  41. let trimmingContainer = this.instance.wtOverlays.topOverlay.trimmingContainer;
  42. let elemHeight;
  43. let height = 0;
  44. if (trimmingContainer === window) {
  45. height = document.documentElement.clientHeight;
  46. } else {
  47. elemHeight = outerHeight(trimmingContainer);
  48. // returns height without DIV scrollbar
  49. height = (elemHeight > 0 && trimmingContainer.clientHeight > 0) ? trimmingContainer.clientHeight : Infinity;
  50. }
  51. return height;
  52. }
  53. getWorkspaceWidth() {
  54. let width;
  55. let totalColumns = this.wot.getSetting('totalColumns');
  56. let trimmingContainer = this.instance.wtOverlays.leftOverlay.trimmingContainer;
  57. let overflow;
  58. let stretchSetting = this.wot.getSetting('stretchH');
  59. let docOffsetWidth = document.documentElement.offsetWidth;
  60. let preventOverflow = this.wot.getSetting('preventOverflow');
  61. if (preventOverflow) {
  62. return outerWidth(this.instance.wtTable.wtRootElement);
  63. }
  64. if (this.wot.getSetting('freezeOverlays')) {
  65. width = Math.min(docOffsetWidth - this.getWorkspaceOffset().left, docOffsetWidth);
  66. } else {
  67. width = Math.min(this.getContainerFillWidth(), docOffsetWidth - this.getWorkspaceOffset().left, docOffsetWidth);
  68. }
  69. if (trimmingContainer === window && totalColumns > 0 && this.sumColumnWidths(0, totalColumns - 1) > width) {
  70. // in case sum of column widths is higher than available stylesheet width, let's assume using the whole window
  71. // otherwise continue below, which will allow stretching
  72. // this is used in `scroll_window.html`
  73. // TODO test me
  74. return document.documentElement.clientWidth;
  75. }
  76. if (trimmingContainer !== window) {
  77. overflow = getStyle(this.instance.wtOverlays.leftOverlay.trimmingContainer, 'overflow');
  78. if (overflow == 'scroll' || overflow == 'hidden' || overflow == 'auto') {
  79. // this is used in `scroll.html`
  80. // TODO test me
  81. return Math.max(width, trimmingContainer.clientWidth);
  82. }
  83. }
  84. if (stretchSetting === 'none' || !stretchSetting) {
  85. // if no stretching is used, return the maximum used workspace width
  86. return Math.max(width, outerWidth(this.instance.wtTable.TABLE));
  87. }
  88. // if stretching is used, return the actual container width, so the columns can fit inside it
  89. return width;
  90. }
  91. /**
  92. * Checks if viewport has vertical scroll
  93. *
  94. * @returns {Boolean}
  95. */
  96. hasVerticalScroll() {
  97. return this.getWorkspaceActualHeight() > this.getWorkspaceHeight();
  98. }
  99. /**
  100. * Checks if viewport has horizontal scroll
  101. *
  102. * @returns {Boolean}
  103. */
  104. hasHorizontalScroll() {
  105. return this.getWorkspaceActualWidth() > this.getWorkspaceWidth();
  106. }
  107. /**
  108. * @param from
  109. * @param length
  110. * @returns {Number}
  111. */
  112. sumColumnWidths(from, length) {
  113. let sum = 0;
  114. while (from < length) {
  115. sum += this.wot.wtTable.getColumnWidth(from);
  116. from++;
  117. }
  118. return sum;
  119. }
  120. /**
  121. * @returns {Number}
  122. */
  123. getContainerFillWidth() {
  124. if (this.containerWidth) {
  125. return this.containerWidth;
  126. }
  127. let mainContainer = this.instance.wtTable.holder;
  128. let fillWidth;
  129. let dummyElement;
  130. dummyElement = document.createElement('div');
  131. dummyElement.style.width = '100%';
  132. dummyElement.style.height = '1px';
  133. mainContainer.appendChild(dummyElement);
  134. fillWidth = dummyElement.offsetWidth;
  135. this.containerWidth = fillWidth;
  136. mainContainer.removeChild(dummyElement);
  137. return fillWidth;
  138. }
  139. /**
  140. * @returns {Number}
  141. */
  142. getWorkspaceOffset() {
  143. return offset(this.wot.wtTable.TABLE);
  144. }
  145. /**
  146. * @returns {Number}
  147. */
  148. getWorkspaceActualHeight() {
  149. return outerHeight(this.wot.wtTable.TABLE);
  150. }
  151. /**
  152. * @returns {Number}
  153. */
  154. getWorkspaceActualWidth() {
  155. return outerWidth(this.wot.wtTable.TABLE) ||
  156. outerWidth(this.wot.wtTable.TBODY) ||
  157. outerWidth(this.wot.wtTable.THEAD); // IE8 reports 0 as <table> offsetWidth;
  158. }
  159. /**
  160. * @returns {Number}
  161. */
  162. getColumnHeaderHeight() {
  163. if (isNaN(this.columnHeaderHeight)) {
  164. this.columnHeaderHeight = outerHeight(this.wot.wtTable.THEAD);
  165. }
  166. return this.columnHeaderHeight;
  167. }
  168. /**
  169. * @returns {Number}
  170. */
  171. getViewportHeight() {
  172. let containerHeight = this.getWorkspaceHeight();
  173. let columnHeaderHeight;
  174. if (containerHeight === Infinity) {
  175. return containerHeight;
  176. }
  177. columnHeaderHeight = this.getColumnHeaderHeight();
  178. if (columnHeaderHeight > 0) {
  179. containerHeight -= columnHeaderHeight;
  180. }
  181. return containerHeight;
  182. }
  183. /**
  184. * @returns {Number}
  185. */
  186. getRowHeaderWidth() {
  187. let rowHeadersHeightSetting = this.instance.getSetting('rowHeaderWidth');
  188. let rowHeaders = this.instance.getSetting('rowHeaders');
  189. if (rowHeadersHeightSetting) {
  190. this.rowHeaderWidth = 0;
  191. for (let i = 0, len = rowHeaders.length; i < len; i++) {
  192. this.rowHeaderWidth += rowHeadersHeightSetting[i] || rowHeadersHeightSetting;
  193. }
  194. }
  195. if (this.wot.cloneSource) {
  196. return this.wot.cloneSource.wtViewport.getRowHeaderWidth();
  197. }
  198. if (isNaN(this.rowHeaderWidth)) {
  199. if (rowHeaders.length) {
  200. let TH = this.instance.wtTable.TABLE.querySelector('TH');
  201. this.rowHeaderWidth = 0;
  202. for (let i = 0, len = rowHeaders.length; i < len; i++) {
  203. if (TH) {
  204. this.rowHeaderWidth += outerWidth(TH);
  205. TH = TH.nextSibling;
  206. } else {
  207. // yes this is a cheat but it worked like that before, just taking assumption from CSS instead of measuring.
  208. // TODO: proper fix
  209. this.rowHeaderWidth += 50;
  210. }
  211. }
  212. } else {
  213. this.rowHeaderWidth = 0;
  214. }
  215. }
  216. this.rowHeaderWidth = this.instance.getSetting('onModifyRowHeaderWidth', this.rowHeaderWidth) || this.rowHeaderWidth;
  217. return this.rowHeaderWidth;
  218. }
  219. /**
  220. * @returns {Number}
  221. */
  222. getViewportWidth() {
  223. let containerWidth = this.getWorkspaceWidth();
  224. let rowHeaderWidth;
  225. if (containerWidth === Infinity) {
  226. return containerWidth;
  227. }
  228. rowHeaderWidth = this.getRowHeaderWidth();
  229. if (rowHeaderWidth > 0) {
  230. return containerWidth - rowHeaderWidth;
  231. }
  232. return containerWidth;
  233. }
  234. /**
  235. * Creates:
  236. * - rowsRenderCalculator (before draw, to qualify rows for rendering)
  237. * - rowsVisibleCalculator (after draw, to measure which rows are actually visible)
  238. *
  239. * @returns {ViewportRowsCalculator}
  240. */
  241. createRowsCalculator(visible = false) {
  242. let height;
  243. let pos;
  244. let fixedRowsTop;
  245. let scrollbarHeight;
  246. let fixedRowsBottom;
  247. let fixedRowsHeight;
  248. let totalRows;
  249. this.rowHeaderWidth = NaN;
  250. if (this.wot.wtSettings.settings.renderAllRows) {
  251. height = Infinity;
  252. } else {
  253. height = this.getViewportHeight();
  254. }
  255. pos = this.wot.wtOverlays.topOverlay.getScrollPosition() - this.wot.wtOverlays.topOverlay.getTableParentOffset();
  256. if (pos < 0) {
  257. pos = 0;
  258. }
  259. fixedRowsTop = this.wot.getSetting('fixedRowsTop');
  260. fixedRowsBottom = this.wot.getSetting('fixedRowsBottom');
  261. totalRows = this.wot.getSetting('totalRows');
  262. if (fixedRowsTop) {
  263. fixedRowsHeight = this.wot.wtOverlays.topOverlay.sumCellSizes(0, fixedRowsTop);
  264. pos += fixedRowsHeight;
  265. height -= fixedRowsHeight;
  266. }
  267. if (fixedRowsBottom && this.wot.wtOverlays.bottomOverlay.clone) {
  268. fixedRowsHeight = this.wot.wtOverlays.bottomOverlay.sumCellSizes(totalRows - fixedRowsBottom, totalRows);
  269. height -= fixedRowsHeight;
  270. }
  271. if (this.wot.wtTable.holder.clientHeight === this.wot.wtTable.holder.offsetHeight) {
  272. scrollbarHeight = 0;
  273. } else {
  274. scrollbarHeight = getScrollbarWidth();
  275. }
  276. return new ViewportRowsCalculator(
  277. height,
  278. pos,
  279. this.wot.getSetting('totalRows'),
  280. (sourceRow) => this.wot.wtTable.getRowHeight(sourceRow),
  281. visible ? null : this.wot.wtSettings.settings.viewportRowCalculatorOverride,
  282. visible,
  283. scrollbarHeight
  284. );
  285. }
  286. /**
  287. * Creates:
  288. * - columnsRenderCalculator (before draw, to qualify columns for rendering)
  289. * - columnsVisibleCalculator (after draw, to measure which columns are actually visible)
  290. *
  291. * @returns {ViewportRowsCalculator}
  292. */
  293. createColumnsCalculator(visible = false) {
  294. let width = this.getViewportWidth();
  295. let pos;
  296. let fixedColumnsLeft;
  297. this.columnHeaderHeight = NaN;
  298. pos = this.wot.wtOverlays.leftOverlay.getScrollPosition() - this.wot.wtOverlays.leftOverlay.getTableParentOffset();
  299. if (pos < 0) {
  300. pos = 0;
  301. }
  302. fixedColumnsLeft = this.wot.getSetting('fixedColumnsLeft');
  303. if (fixedColumnsLeft) {
  304. let fixedColumnsWidth = this.wot.wtOverlays.leftOverlay.sumCellSizes(0, fixedColumnsLeft);
  305. pos += fixedColumnsWidth;
  306. width -= fixedColumnsWidth;
  307. }
  308. if (this.wot.wtTable.holder.clientWidth !== this.wot.wtTable.holder.offsetWidth) {
  309. width -= getScrollbarWidth();
  310. }
  311. return new ViewportColumnsCalculator(
  312. width,
  313. pos,
  314. this.wot.getSetting('totalColumns'),
  315. (sourceCol) => this.wot.wtTable.getColumnWidth(sourceCol),
  316. visible ? null : this.wot.wtSettings.settings.viewportColumnCalculatorOverride,
  317. visible,
  318. this.wot.getSetting('stretchH'),
  319. (stretchedWidth, column) => this.wot.getSetting('onBeforeStretchingColumnWidth', stretchedWidth, column)
  320. );
  321. }
  322. /**
  323. * Creates rowsRenderCalculator and columnsRenderCalculator (before draw, to determine what rows and
  324. * cols should be rendered)
  325. *
  326. * @param fastDraw {Boolean} If `true`, will try to avoid full redraw and only update the border positions.
  327. * If `false` or `undefined`, will perform a full redraw
  328. * @returns fastDraw {Boolean} The fastDraw value, possibly modified
  329. */
  330. createRenderCalculators(fastDraw = false) {
  331. if (fastDraw) {
  332. let proposedRowsVisibleCalculator = this.createRowsCalculator(true);
  333. let proposedColumnsVisibleCalculator = this.createColumnsCalculator(true);
  334. if (!(this.areAllProposedVisibleRowsAlreadyRendered(proposedRowsVisibleCalculator) &&
  335. this.areAllProposedVisibleColumnsAlreadyRendered(proposedColumnsVisibleCalculator))) {
  336. fastDraw = false;
  337. }
  338. }
  339. if (!fastDraw) {
  340. this.rowsRenderCalculator = this.createRowsCalculator();
  341. this.columnsRenderCalculator = this.createColumnsCalculator();
  342. }
  343. // delete temporarily to make sure that renderers always use rowsRenderCalculator, not rowsVisibleCalculator
  344. this.rowsVisibleCalculator = null;
  345. this.columnsVisibleCalculator = null;
  346. return fastDraw;
  347. }
  348. /**
  349. * Creates rowsVisibleCalculator and columnsVisibleCalculator (after draw, to determine what are
  350. * the actually visible rows and columns)
  351. */
  352. createVisibleCalculators() {
  353. this.rowsVisibleCalculator = this.createRowsCalculator(true);
  354. this.columnsVisibleCalculator = this.createColumnsCalculator(true);
  355. }
  356. /**
  357. * Returns information whether proposedRowsVisibleCalculator viewport
  358. * is contained inside rows rendered in previous draw (cached in rowsRenderCalculator)
  359. *
  360. * @param {Object} proposedRowsVisibleCalculator
  361. * @returns {Boolean} Returns `true` if all proposed visible rows are already rendered (meaning: redraw is not needed).
  362. * Returns `false` if at least one proposed visible row is not already rendered (meaning: redraw is needed)
  363. */
  364. areAllProposedVisibleRowsAlreadyRendered(proposedRowsVisibleCalculator) {
  365. if (this.rowsVisibleCalculator) {
  366. if (proposedRowsVisibleCalculator.startRow < this.rowsRenderCalculator.startRow ||
  367. (proposedRowsVisibleCalculator.startRow === this.rowsRenderCalculator.startRow &&
  368. proposedRowsVisibleCalculator.startRow > 0)) {
  369. return false;
  370. } else if (proposedRowsVisibleCalculator.endRow > this.rowsRenderCalculator.endRow ||
  371. (proposedRowsVisibleCalculator.endRow === this.rowsRenderCalculator.endRow &&
  372. proposedRowsVisibleCalculator.endRow < this.wot.getSetting('totalRows') - 1)) {
  373. return false;
  374. }
  375. return true;
  376. }
  377. return false;
  378. }
  379. /**
  380. * Returns information whether proposedColumnsVisibleCalculator viewport
  381. * is contained inside column rendered in previous draw (cached in columnsRenderCalculator)
  382. *
  383. * @param {Object} proposedColumnsVisibleCalculator
  384. * @returns {Boolean} Returns `true` if all proposed visible columns are already rendered (meaning: redraw is not needed).
  385. * Returns `false` if at least one proposed visible column is not already rendered (meaning: redraw is needed)
  386. */
  387. areAllProposedVisibleColumnsAlreadyRendered(proposedColumnsVisibleCalculator) {
  388. if (this.columnsVisibleCalculator) {
  389. if (proposedColumnsVisibleCalculator.startColumn < this.columnsRenderCalculator.startColumn ||
  390. (proposedColumnsVisibleCalculator.startColumn === this.columnsRenderCalculator.startColumn &&
  391. proposedColumnsVisibleCalculator.startColumn > 0)) {
  392. return false;
  393. } else if (proposedColumnsVisibleCalculator.endColumn > this.columnsRenderCalculator.endColumn ||
  394. (proposedColumnsVisibleCalculator.endColumn === this.columnsRenderCalculator.endColumn &&
  395. proposedColumnsVisibleCalculator.endColumn < this.wot.getSetting('totalColumns') - 1)) {
  396. return false;
  397. }
  398. return true;
  399. }
  400. return false;
  401. }
  402. /**
  403. * Resets values in keys of the hasOversizedColumnHeadersMarked object after updateSettings.
  404. */
  405. resetHasOversizedColumnHeadersMarked() {
  406. objectEach(this.hasOversizedColumnHeadersMarked, (value, key, object) => {
  407. object[key] = void 0;
  408. });
  409. }
  410. }
  411. export default Viewport;