table-layout.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. import Vue from 'vue';
  2. import scrollbarWidth from 'element-ui/src/utils/scrollbar-width';
  3. import { parseHeight } from './util';
  4. class TableLayout {
  5. constructor(options) {
  6. this.observers = [];
  7. this.table = null;
  8. this.store = null;
  9. this.columns = null;
  10. this.fit = true;
  11. this.showHeader = true;
  12. this.height = null;
  13. this.scrollX = false;
  14. this.scrollY = false;
  15. this.bodyWidth = null;
  16. this.fixedWidth = null;
  17. this.rightFixedWidth = null;
  18. this.tableHeight = null;
  19. this.headerHeight = 44; // Table Header Height
  20. this.appendHeight = 0; // Append Slot Height
  21. this.footerHeight = 44; // Table Footer Height
  22. this.viewportHeight = null; // Table Height - Scroll Bar Height
  23. this.bodyHeight = null; // Table Height - Table Header Height
  24. this.fixedBodyHeight = null; // Table Height - Table Header Height - Scroll Bar Height
  25. this.gutterWidth = scrollbarWidth();
  26. for (let name in options) {
  27. if (options.hasOwnProperty(name)) {
  28. this[name] = options[name];
  29. }
  30. }
  31. if (!this.table) {
  32. throw new Error('table is required for Table Layout');
  33. }
  34. if (!this.store) {
  35. throw new Error('store is required for Table Layout');
  36. }
  37. }
  38. updateScrollY() {
  39. const height = this.height;
  40. if (height === null) return false;
  41. const bodyWrapper = this.table.bodyWrapper;
  42. if (this.table.$el && bodyWrapper) {
  43. const body = bodyWrapper.querySelector('.el-table__body');
  44. const prevScrollY = this.scrollY;
  45. const scrollY = body.offsetHeight > this.bodyHeight;
  46. this.scrollY = scrollY;
  47. return prevScrollY !== scrollY;
  48. }
  49. return false;
  50. }
  51. setHeight(value, prop = 'height') {
  52. if (Vue.prototype.$isServer) return;
  53. const el = this.table.$el;
  54. value = parseHeight(value);
  55. this.height = value;
  56. if (!el && (value || value === 0)) return Vue.nextTick(() => this.setHeight(value, prop));
  57. if (typeof value === 'number') {
  58. el.style[prop] = value + 'px';
  59. this.updateElsHeight();
  60. } else if (typeof value === 'string') {
  61. el.style[prop] = value;
  62. this.updateElsHeight();
  63. }
  64. }
  65. setMaxHeight(value) {
  66. this.setHeight(value, 'max-height');
  67. }
  68. getFlattenColumns() {
  69. const flattenColumns = [];
  70. const columns = this.table.columns;
  71. columns.forEach((column) => {
  72. if (column.isColumnGroup) {
  73. flattenColumns.push.apply(flattenColumns, column.columns);
  74. } else {
  75. flattenColumns.push(column);
  76. }
  77. });
  78. return flattenColumns;
  79. }
  80. updateElsHeight() {
  81. if (!this.table.$ready) return Vue.nextTick(() => this.updateElsHeight());
  82. const { headerWrapper, appendWrapper, footerWrapper } = this.table.$refs;
  83. this.appendHeight = appendWrapper ? appendWrapper.offsetHeight : 0;
  84. if (this.showHeader && !headerWrapper) return;
  85. // fix issue (https://github.com/ElemeFE/element/pull/16956)
  86. const headerTrElm = headerWrapper ? headerWrapper.querySelector('.el-table__header tr') : null;
  87. const noneHeader = this.headerDisplayNone(headerTrElm);
  88. const headerHeight = this.headerHeight = !this.showHeader ? 0 : headerWrapper.offsetHeight;
  89. if (this.showHeader && !noneHeader && headerWrapper.offsetWidth > 0 && (this.table.columns || []).length > 0 && headerHeight < 2) {
  90. return Vue.nextTick(() => this.updateElsHeight());
  91. }
  92. const tableHeight = this.tableHeight = this.table.$el.clientHeight;
  93. const footerHeight = this.footerHeight = footerWrapper ? footerWrapper.offsetHeight : 0;
  94. if (this.height !== null) {
  95. this.bodyHeight = tableHeight - headerHeight - footerHeight + (footerWrapper ? 1 : 0);
  96. }
  97. this.fixedBodyHeight = this.scrollX ? (this.bodyHeight - this.gutterWidth) : this.bodyHeight;
  98. const noData = !(this.store.states.data && this.store.states.data.length);
  99. this.viewportHeight = this.scrollX ? tableHeight - (noData ? 0 : this.gutterWidth) : tableHeight;
  100. this.updateScrollY();
  101. this.notifyObservers('scrollable');
  102. }
  103. headerDisplayNone(elm) {
  104. if (!elm) return true;
  105. let headerChild = elm;
  106. while (headerChild.tagName !== 'DIV') {
  107. if (getComputedStyle(headerChild).display === 'none') {
  108. return true;
  109. }
  110. headerChild = headerChild.parentElement;
  111. }
  112. return false;
  113. }
  114. updateColumnsWidth() {
  115. if (Vue.prototype.$isServer) return;
  116. const fit = this.fit;
  117. const bodyWidth = this.table.$el.clientWidth;
  118. let bodyMinWidth = 0;
  119. const flattenColumns = this.getFlattenColumns();
  120. let flexColumns = flattenColumns.filter((column) => typeof column.width !== 'number');
  121. flattenColumns.forEach((column) => { // Clean those columns whose width changed from flex to unflex
  122. if (typeof column.width === 'number' && column.realWidth) column.realWidth = null;
  123. });
  124. if (flexColumns.length > 0 && fit) {
  125. flattenColumns.forEach((column) => {
  126. bodyMinWidth += column.width || column.minWidth || 80;
  127. });
  128. const scrollYWidth = this.scrollY ? this.gutterWidth : 0;
  129. if (bodyMinWidth <= bodyWidth - scrollYWidth) { // DON'T HAVE SCROLL BAR
  130. this.scrollX = false;
  131. const totalFlexWidth = bodyWidth - scrollYWidth - bodyMinWidth;
  132. if (flexColumns.length === 1) {
  133. flexColumns[0].realWidth = (flexColumns[0].minWidth || 80) + totalFlexWidth;
  134. } else {
  135. const allColumnsWidth = flexColumns.reduce((prev, column) => prev + (column.minWidth || 80), 0);
  136. const flexWidthPerPixel = totalFlexWidth / allColumnsWidth;
  137. let noneFirstWidth = 0;
  138. flexColumns.forEach((column, index) => {
  139. if (index === 0) return;
  140. const flexWidth = Math.floor((column.minWidth || 80) * flexWidthPerPixel);
  141. noneFirstWidth += flexWidth;
  142. column.realWidth = (column.minWidth || 80) + flexWidth;
  143. });
  144. flexColumns[0].realWidth = (flexColumns[0].minWidth || 80) + totalFlexWidth - noneFirstWidth;
  145. }
  146. } else { // HAVE HORIZONTAL SCROLL BAR
  147. this.scrollX = true;
  148. flexColumns.forEach(function(column) {
  149. column.realWidth = column.minWidth;
  150. });
  151. }
  152. this.bodyWidth = Math.max(bodyMinWidth, bodyWidth);
  153. this.table.resizeState.width = this.bodyWidth;
  154. } else {
  155. flattenColumns.forEach((column) => {
  156. if (!column.width && !column.minWidth) {
  157. column.realWidth = 80;
  158. } else {
  159. column.realWidth = column.width || column.minWidth;
  160. }
  161. bodyMinWidth += column.realWidth;
  162. });
  163. this.scrollX = bodyMinWidth > bodyWidth;
  164. this.bodyWidth = bodyMinWidth;
  165. }
  166. const fixedColumns = this.store.states.fixedColumns;
  167. if (fixedColumns.length > 0) {
  168. let fixedWidth = 0;
  169. fixedColumns.forEach(function(column) {
  170. fixedWidth += column.realWidth || column.width;
  171. });
  172. this.fixedWidth = fixedWidth;
  173. }
  174. const rightFixedColumns = this.store.states.rightFixedColumns;
  175. if (rightFixedColumns.length > 0) {
  176. let rightFixedWidth = 0;
  177. rightFixedColumns.forEach(function(column) {
  178. rightFixedWidth += column.realWidth || column.width;
  179. });
  180. this.rightFixedWidth = rightFixedWidth;
  181. }
  182. this.notifyObservers('columns');
  183. }
  184. addObserver(observer) {
  185. this.observers.push(observer);
  186. }
  187. removeObserver(observer) {
  188. const index = this.observers.indexOf(observer);
  189. if (index !== -1) {
  190. this.observers.splice(index, 1);
  191. }
  192. }
  193. notifyObservers(event) {
  194. const observers = this.observers;
  195. observers.forEach((observer) => {
  196. switch (event) {
  197. case 'columns':
  198. observer.onColumnsChange(this);
  199. break;
  200. case 'scrollable':
  201. observer.onScrollableChange(this);
  202. break;
  203. default:
  204. throw new Error(`Table Layout don't have event ${event}.`);
  205. }
  206. });
  207. }
  208. }
  209. export default TableLayout;