c11f08ddfedc4f4c91c38b514b772976882f04e48d1d14884e05f34b3a2d6e7f52f6d657cb831224cb2e75bcb7915404fda098c2678a8edaa5b62692011559 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  1. import {addClass, outerHeight, outerWidth} from './../helpers/dom/element';
  2. import {arrayEach} from './../helpers/array';
  3. /**
  4. * @class GhostTable
  5. * @util
  6. */
  7. class GhostTable {
  8. constructor(hotInstance) {
  9. /**
  10. * Handsontable instance.
  11. *
  12. * @type {Core}
  13. */
  14. this.hot = hotInstance;
  15. /**
  16. * Container element where every table will be injected.
  17. *
  18. * @type {HTMLElement|null}
  19. */
  20. this.container = null;
  21. /**
  22. * Flag which determine is table was injected to DOM.
  23. *
  24. * @type {Boolean}
  25. */
  26. this.injected = false;
  27. /**
  28. * Added rows collection.
  29. *
  30. * @type {Array}
  31. */
  32. this.rows = [];
  33. /**
  34. * Added columns collection.
  35. *
  36. * @type {Array}
  37. */
  38. this.columns = [];
  39. /**
  40. * Samples prepared for calculations.
  41. *
  42. * @type {Map}
  43. * @default {null}
  44. */
  45. this.samples = null;
  46. /**
  47. * Ghost table settings.
  48. *
  49. * @type {Object}
  50. * @default {Object}
  51. */
  52. this.settings = {
  53. useHeaders: true
  54. };
  55. }
  56. /**
  57. * Add row.
  58. *
  59. * @param {Number} row Row index.
  60. * @param {Map} samples Samples Map object.
  61. */
  62. addRow(row, samples) {
  63. if (this.columns.length) {
  64. throw new Error('Doesn\'t support multi-dimensional table');
  65. }
  66. if (!this.rows.length) {
  67. this.container = this.createContainer(this.hot.rootElement.className);
  68. }
  69. const rowObject = {row};
  70. this.rows.push(rowObject);
  71. this.samples = samples;
  72. this.table = this.createTable(this.hot.table.className);
  73. this.table.colGroup.appendChild(this.createColGroupsCol());
  74. this.table.tr.appendChild(this.createRow(row));
  75. this.container.container.appendChild(this.table.fragment);
  76. rowObject.table = this.table.table;
  77. }
  78. /**
  79. * Add a row consisting of the column headers.
  80. */
  81. addColumnHeadersRow(samples) {
  82. if (this.hot.getColHeader(0) != null) {
  83. const rowObject = {row: -1};
  84. this.rows.push(rowObject);
  85. this.container = this.createContainer(this.hot.rootElement.className);
  86. this.samples = samples;
  87. this.table = this.createTable(this.hot.table.className);
  88. this.table.colGroup.appendChild(this.createColGroupsCol());
  89. this.table.tHead.appendChild(this.createColumnHeadersRow());
  90. this.container.container.appendChild(this.table.fragment);
  91. rowObject.table = this.table.table;
  92. }
  93. }
  94. /**
  95. * Add column.
  96. *
  97. * @param {Number} column Column index.
  98. * @param {Map} samples Samples Map object.
  99. */
  100. addColumn(column, samples) {
  101. if (this.rows.length) {
  102. throw new Error('Doesn\'t support multi-dimensional table');
  103. }
  104. if (!this.columns.length) {
  105. this.container = this.createContainer(this.hot.rootElement.className);
  106. }
  107. const columnObject = {col: column};
  108. this.columns.push(columnObject);
  109. this.samples = samples;
  110. this.table = this.createTable(this.hot.table.className);
  111. if (this.getSetting('useHeaders') && this.hot.getColHeader(column) !== null) {
  112. this.hot.view.appendColHeader(column, this.table.th);
  113. }
  114. this.table.tBody.appendChild(this.createCol(column));
  115. this.container.container.appendChild(this.table.fragment);
  116. columnObject.table = this.table.table;
  117. }
  118. /**
  119. * Get calculated heights.
  120. *
  121. * @param {Function} callback Callback which will be fired for each calculated row.
  122. */
  123. getHeights(callback) {
  124. if (!this.injected) {
  125. this.injectTable();
  126. }
  127. arrayEach(this.rows, (row) => {
  128. // -1 <- reduce border-top from table
  129. callback(row.row, outerHeight(row.table) - 1);
  130. });
  131. }
  132. /**
  133. * Get calculated widths.
  134. *
  135. * @param {Function} callback Callback which will be fired for each calculated column.
  136. */
  137. getWidths(callback) {
  138. if (!this.injected) {
  139. this.injectTable();
  140. }
  141. arrayEach(this.columns, (column) => {
  142. callback(column.col, outerWidth(column.table));
  143. });
  144. }
  145. /**
  146. * Set the Ghost Table settings to the provided object.
  147. *
  148. * @param {Object} settings New Ghost Table Settings
  149. */
  150. setSettings(settings) {
  151. this.settings = settings;
  152. }
  153. /**
  154. * Set a single setting of the Ghost Table.
  155. *
  156. * @param {String} name Setting name.
  157. * @param {*} value Setting value.
  158. */
  159. setSetting(name, value) {
  160. if (!this.settings) {
  161. this.settings = {};
  162. }
  163. this.settings[name] = value;
  164. }
  165. /**
  166. * Get the Ghost Table settings.
  167. *
  168. * @returns {Object|null}
  169. */
  170. getSettings() {
  171. return this.settings;
  172. }
  173. /**
  174. * Get a single Ghost Table setting.
  175. *
  176. * @param {String} name
  177. * @returns {Boolean|null}
  178. */
  179. getSetting(name) {
  180. if (this.settings) {
  181. return this.settings[name];
  182. }
  183. return null;
  184. }
  185. /**
  186. * Create colgroup col elements.
  187. *
  188. * @returns {DocumentFragment}
  189. */
  190. createColGroupsCol() {
  191. const d = document;
  192. const fragment = d.createDocumentFragment();
  193. if (this.hot.hasRowHeaders()) {
  194. fragment.appendChild(this.createColElement(-1));
  195. }
  196. this.samples.forEach((sample) => {
  197. arrayEach(sample.strings, (string) => {
  198. fragment.appendChild(this.createColElement(string.col));
  199. });
  200. });
  201. return fragment;
  202. }
  203. /**
  204. * Create table row element.
  205. *
  206. * @param {Number} row Row index.
  207. * @returns {DocumentFragment} Returns created table row elements.
  208. */
  209. createRow(row) {
  210. const d = document;
  211. const fragment = d.createDocumentFragment();
  212. const th = d.createElement('th');
  213. if (this.hot.hasRowHeaders()) {
  214. this.hot.view.appendRowHeader(row, th);
  215. fragment.appendChild(th);
  216. }
  217. this.samples.forEach((sample) => {
  218. arrayEach(sample.strings, (string) => {
  219. let column = string.col;
  220. let cellProperties = this.hot.getCellMeta(row, column);
  221. cellProperties.col = column;
  222. cellProperties.row = row;
  223. let renderer = this.hot.getCellRenderer(cellProperties);
  224. const td = d.createElement('td');
  225. renderer(this.hot, td, row, column, this.hot.colToProp(column), string.value, cellProperties);
  226. fragment.appendChild(td);
  227. });
  228. });
  229. return fragment;
  230. }
  231. createColumnHeadersRow() {
  232. const d = document;
  233. const fragment = d.createDocumentFragment();
  234. if (this.hot.hasRowHeaders()) {
  235. const th = d.createElement('th');
  236. this.hot.view.appendColHeader(-1, th);
  237. fragment.appendChild(th);
  238. }
  239. this.samples.forEach((sample) => {
  240. arrayEach(sample.strings, (string) => {
  241. let column = string.col;
  242. const th = d.createElement('th');
  243. this.hot.view.appendColHeader(column, th);
  244. fragment.appendChild(th);
  245. });
  246. });
  247. return fragment;
  248. }
  249. /**
  250. * Create table column elements.
  251. *
  252. * @param {Number} column Column index.
  253. * @returns {DocumentFragment} Returns created column table column elements.
  254. */
  255. createCol(column) {
  256. const d = document;
  257. const fragment = d.createDocumentFragment();
  258. this.samples.forEach((sample) => {
  259. arrayEach(sample.strings, (string) => {
  260. let row = string.row;
  261. let cellProperties = this.hot.getCellMeta(row, column);
  262. cellProperties.col = column;
  263. cellProperties.row = row;
  264. let renderer = this.hot.getCellRenderer(cellProperties);
  265. const td = d.createElement('td');
  266. const tr = d.createElement('tr');
  267. renderer(this.hot, td, row, column, this.hot.colToProp(column), string.value, cellProperties);
  268. tr.appendChild(td);
  269. fragment.appendChild(tr);
  270. });
  271. });
  272. return fragment;
  273. }
  274. /**
  275. * Remove table from document and reset internal state.
  276. */
  277. clean() {
  278. this.rows.length = 0;
  279. this.rows[-1] = void 0;
  280. this.columns.length = 0;
  281. if (this.samples) {
  282. this.samples.clear();
  283. }
  284. this.samples = null;
  285. this.removeTable();
  286. }
  287. /**
  288. * Inject generated table into document.
  289. *
  290. * @param {HTMLElement} [parent=null]
  291. */
  292. injectTable(parent = null) {
  293. if (!this.injected) {
  294. (parent || this.hot.rootElement).appendChild(this.container.fragment);
  295. this.injected = true;
  296. }
  297. }
  298. /**
  299. * Remove table from document.
  300. */
  301. removeTable() {
  302. if (this.injected && this.container.container.parentNode) {
  303. this.container.container.parentNode.removeChild(this.container.container);
  304. this.container = null;
  305. this.injected = false;
  306. }
  307. }
  308. /**
  309. * Create col element.
  310. *
  311. * @param {Number} column Column index.
  312. * @returns {HTMLElement}
  313. */
  314. createColElement(column) {
  315. const d = document;
  316. const col = d.createElement('col');
  317. col.style.width = `${this.hot.view.wt.wtTable.getStretchedColumnWidth(column)}px`;
  318. return col;
  319. }
  320. /**
  321. * Create table element.
  322. *
  323. * @param {String} className
  324. * @returns {Object}
  325. */
  326. createTable(className = '') {
  327. const d = document;
  328. const fragment = d.createDocumentFragment();
  329. const table = d.createElement('table');
  330. const tHead = d.createElement('thead');
  331. const tBody = d.createElement('tbody');
  332. const colGroup = d.createElement('colgroup');
  333. const tr = d.createElement('tr');
  334. const th = d.createElement('th');
  335. if (this.isVertical()) {
  336. table.appendChild(colGroup);
  337. }
  338. if (this.isHorizontal()) {
  339. tr.appendChild(th);
  340. tHead.appendChild(tr);
  341. table.style.tableLayout = 'auto';
  342. table.style.width = 'auto';
  343. }
  344. table.appendChild(tHead);
  345. if (this.isVertical()) {
  346. tBody.appendChild(tr);
  347. }
  348. table.appendChild(tBody);
  349. addClass(table, className);
  350. fragment.appendChild(table);
  351. return {fragment, table, tHead, tBody, colGroup, tr, th};
  352. }
  353. /**
  354. * Create container for tables.
  355. *
  356. * @param {String} className
  357. * @returns {Object}
  358. */
  359. createContainer(className = '') {
  360. const d = document;
  361. const fragment = d.createDocumentFragment();
  362. const container = d.createElement('div');
  363. className = `htGhostTable htAutoSize ${className.trim()}`;
  364. addClass(container, className);
  365. fragment.appendChild(container);
  366. return {fragment, container};
  367. }
  368. /**
  369. * Checks if table is raised vertically (checking rows).
  370. *
  371. * @returns {Boolean}
  372. */
  373. isVertical() {
  374. return !!(this.rows.length && !this.columns.length);
  375. }
  376. /**
  377. * Checks if table is raised horizontally (checking columns).
  378. *
  379. * @returns {Boolean}
  380. */
  381. isHorizontal() {
  382. return !!(this.columns.length && !this.rows.length);
  383. }
  384. }
  385. export default GhostTable;