e-virt-table
Version:
A powerful data table based on canvas. You can use it as data grid、Microsoft Excel or Google sheets. It supports virtual scroll、cell edit etc.
358 lines • 12.9 kB
JavaScript
import { throttle } from './util';
export default class Overlayer {
constructor(ctx) {
Object.defineProperty(this, "ctx", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "observer", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
this.ctx = ctx;
this.init();
}
arerMapsEqual(m1, m2) {
if (m1.size !== m2.size)
return false;
for (let [key, value] of m1) {
if (!m2.has(key))
return false;
if (m2.get(key) !== value)
return false;
}
return true;
}
init() {
// 监听覆盖层变化,用于自动高度计算
this.observer = new MutationObserver(throttle(() => {
// 当 DOM 发生变化时执行的回调
const elements = this.ctx.overlayerElement.querySelectorAll('[data-auto-height="true"]');
const map = new Map();
elements.forEach((element) => {
const rowIndex = Number(element.getAttribute('data-row-index'));
const colIndex = Number(element.getAttribute('data-col-index'));
if (isNaN(rowIndex)) {
return;
}
if (isNaN(colIndex)) {
return;
}
if (!(element instanceof HTMLElement)) {
return;
}
if (element.offsetWidth === 0) {
return;
}
const key = `${rowIndex}\u200b_${colIndex}`;
map.set(key, Math.round(element.offsetHeight));
});
const overlayerAutoHeightMap = this.ctx.database.getOverlayerAutoHeightMap();
const isNeedUpdate = !this.arerMapsEqual(overlayerAutoHeightMap, map);
if (isNeedUpdate) {
this.ctx.database.setOverlayerAutoHeightMap(map);
if (overlayerAutoHeightMap.size === 0 && map.size === 0) {
return;
}
this.ctx.emit('draw');
}
}, 16.67));
this.observer.observe(this.ctx.overlayerElement, { childList: true, subtree: true, attributes: true });
// 自定义覆盖层时,不监听覆盖层变化
if (this.ctx.overlayerElement.getAttribute('data-overlayer') === 'default') {
this.ctx.on('overlayerChange', (container) => {
const overlayerEl = this.ctx.overlayerElement;
// 移除所有子元素
overlayerEl.replaceChildren();
Object.assign(overlayerEl.style, container.style);
container.views.forEach((typeView) => {
const typeDiv = document.createElement('div');
typeDiv.className = typeView.class;
Object.assign(typeDiv.style, typeView.style);
typeView.views.forEach((cellWrapView) => {
const cellWrap = document.createElement('div');
Object.assign(cellWrap.style, cellWrapView.style);
cellWrapView.cells.forEach((cell) => {
const cellEl = document.createElement('div');
Object.assign(cellEl.style, cell.style);
Object.keys(cell.domDataset).forEach((key) => {
cellEl.setAttribute(key, cell.domDataset[key]);
});
if (typeof cell.render === 'function') {
cell.render(cellEl, cell);
}
cellWrap.appendChild(cellEl);
});
typeDiv.appendChild(cellWrap);
});
overlayerEl.appendChild(typeDiv);
});
});
}
}
draw() {
const overlayer = this.getContainer();
this.ctx.emit('overlayerChange', overlayer);
}
destroy() {
// 清除MutationObserver
if (this.observer) {
this.observer.disconnect();
}
this.ctx.emit('overlayerChange', {
style: {},
views: [],
});
}
getContainer() {
const header = this.getHeader();
const body = this.getBody();
const footer = this.getFooter();
let views = [];
// 不是固定底部的时候,不用加入footer,因为footer是在body的下面的
const { FOOTER_FIXED, FOOTER_POSITION } = this.ctx.config;
if (!FOOTER_FIXED) {
views = [header, body];
}
else {
if (FOOTER_POSITION === 'top') {
views = [header, footer, body];
}
else {
views = [header, body, footer];
}
}
return {
views,
};
}
getHeader() {
const { fixedLeftWidth, fixedRightWidth: _fixedRightWidth, config: { SCROLLER_TRACK_SIZE, CSS_PREFIX }, } = this.ctx;
const { visibleWidth, visibleHeight, renderCellHeaders } = this.ctx.header;
let centerCells = [];
let leftCells = [];
let rightCells = [];
renderCellHeaders.forEach((cellHeader) => {
if (cellHeader.render) {
if (cellHeader.fixed === 'left') {
leftCells.push(cellHeader);
}
else if (cellHeader.fixed === 'right') {
rightCells.push(cellHeader);
}
else {
centerCells.push(cellHeader);
}
}
});
// 减去滚动条的宽度
const fixedRightWidth = _fixedRightWidth - SCROLLER_TRACK_SIZE;
const left = {
key: 'left',
style: {
position: 'absolute',
top: `${0}px`,
left: `${0}px`,
overflow: 'hidden',
width: `${fixedLeftWidth}px`,
height: `${visibleHeight}px`,
},
cells: leftCells,
};
const center = {
key: 'center',
style: {
position: 'absolute',
top: `${0}px`,
left: `${fixedLeftWidth}px`,
overflow: 'hidden',
width: `${visibleWidth - fixedLeftWidth - fixedRightWidth + 1}px`,
height: `${visibleHeight}px`,
},
cells: centerCells,
};
const right = {
key: 'right',
style: {
position: 'absolute',
top: `${0}px`,
right: `${0}px`,
overflow: 'hidden',
width: `${fixedRightWidth + 1}px`,
height: `${visibleHeight}px`,
},
cells: rightCells,
};
const header = {
type: 'header',
class: `${CSS_PREFIX}-overlayer-header`,
style: {
position: 'relative',
overflow: 'hidden',
width: `${visibleWidth}px`,
height: `${visibleHeight}px`,
},
views: [left, center, right],
};
return header;
}
getBody() {
const centerCells = [];
const leftCells = [];
const rightCells = [];
let renderRows = this.ctx.body.renderRows;
// 合计如果不是固定在底部,就加入到渲染行中
if (!this.ctx.config.FOOTER_FIXED) {
renderRows = renderRows.concat(this.ctx.footer.renderRows);
}
renderRows.forEach((row) => {
row.cells.forEach((cell) => {
if (cell.cellType === 'footer') {
cell.render = cell.renderFooter;
}
if (cell.render) {
if (cell.fixed === 'left') {
leftCells.push(cell);
}
else if (cell.fixed === 'right') {
rightCells.push(cell);
}
else {
centerCells.push(cell);
}
}
});
});
const { fixedLeftWidth, fixedRightWidth: _fixedRightWidth, config: { SCROLLER_TRACK_SIZE, CSS_PREFIX }, } = this.ctx;
const { visibleWidth, visibleHeight } = this.ctx.body;
// 减去滚动条的宽度
const fixedRightWidth = _fixedRightWidth - SCROLLER_TRACK_SIZE;
const left = {
key: 'left',
style: {
position: 'absolute',
top: `${0.5}px`,
left: `${0.5}px`,
overflow: 'hidden',
width: `${fixedLeftWidth}px`,
height: `${visibleHeight}px`,
},
cells: leftCells,
};
const center = {
key: 'center',
style: {
position: 'absolute',
top: `${0.5}px`,
left: `${fixedLeftWidth - 0.5}px`,
overflow: 'hidden',
width: `${visibleWidth - fixedLeftWidth - fixedRightWidth}px`,
height: `${visibleHeight}px`,
},
cells: centerCells,
};
const right = {
key: 'right',
style: {
position: 'absolute',
top: `${0}px`,
right: `${0}px`,
overflow: 'hidden',
width: `${fixedRightWidth}px`,
height: `${visibleHeight}px`,
},
cells: rightCells,
};
const body = {
type: 'body',
class: `${CSS_PREFIX}-overlayer-body`,
style: {
position: 'relative',
overflow: 'hidden',
width: `${visibleWidth}px`,
height: `${visibleHeight}px`,
},
views: [left, center, right],
};
return body;
}
getFooter() {
const centerCells = [];
const leftCells = [];
const rightCells = [];
this.ctx.footer.renderRows.forEach((row) => {
row.cells.forEach((cell) => {
if (cell.cellType === 'footer' && cell.renderFooter) {
// 把转renderFooter成render统一出口
cell.render = cell.renderFooter;
if (cell.fixed === 'left') {
leftCells.push(cell);
}
else if (cell.fixed === 'right') {
rightCells.push(cell);
}
else {
centerCells.push(cell);
}
}
});
});
const { fixedLeftWidth, fixedRightWidth: _fixedRightWidth, config: { SCROLLER_TRACK_SIZE, CSS_PREFIX }, } = this.ctx;
const { visibleWidth, visibleHeight } = this.ctx.footer;
// 减去滚动条的宽度
const fixedRightWidth = _fixedRightWidth - SCROLLER_TRACK_SIZE;
const left = {
key: 'left',
style: {
position: 'absolute',
top: `${0.5}px`,
left: `${0.5}px`,
overflow: 'hidden',
width: `${fixedLeftWidth}px`,
height: `${visibleHeight}px`,
},
cells: leftCells,
};
const center = {
key: 'center',
style: {
position: 'absolute',
top: `${0.5}px`,
left: `${fixedLeftWidth - 0.5}px`,
overflow: 'hidden',
width: `${visibleWidth - fixedLeftWidth - fixedRightWidth}px`,
height: `${visibleHeight}px`,
},
cells: centerCells,
};
const right = {
key: 'right',
style: {
position: 'absolute',
top: `${0.5}px`,
right: `${0.5}px`,
overflow: 'hidden',
width: `${fixedRightWidth}px`,
height: `${visibleHeight}px`,
},
cells: rightCells,
};
const footer = {
type: 'footer',
class: `${CSS_PREFIX}-overlayer-footer`,
style: {
position: 'relative',
overflow: 'hidden',
width: `${visibleWidth}px`,
height: `${visibleHeight}px`,
},
views: [left, center, right],
};
return footer;
}
}
//# sourceMappingURL=Overlayer.js.map