Files
smart-table/lib/core/core.js

491 lines
15 KiB
JavaScript
Raw Normal View History

2020-02-02 15:20:49 +08:00
import sort from './vdom';
import {
2020-02-06 16:43:18 +08:00
setAttribute,
removeAttribute,
hasAttribute,
2020-02-06 18:51:14 +08:00
getAttribute,
appendChild,
appendChildren,
removeChild,
createElement,
querySelector,
querySelectorAll
} from './node-ops';
2020-02-02 15:20:49 +08:00
import createFixed from './fixed';
2020-01-13 16:25:20 +08:00
import {
throttle,
2020-01-16 14:25:41 +08:00
debounce,
2020-02-06 18:51:14 +08:00
getAttrNumber,
refactorCell,
replaceColGroup,
2020-02-05 11:12:40 +08:00
createTableWrapper,
getEmptyIndexInArray
2020-01-13 16:25:20 +08:00
} from './utils';
import scrollBarWidth from './scrollbar-width';
2020-01-13 16:25:20 +08:00
2020-01-16 17:17:57 +08:00
export default function initMixin(Table) {
2020-01-28 22:13:48 +08:00
Table.prototype._init = function(options = {}) {
2020-01-13 16:25:20 +08:00
if (!options.selector) {
2020-02-05 11:12:40 +08:00
return console.error("Smart Table init need a selector")
2020-01-13 16:25:20 +08:00
}
2020-02-05 11:12:40 +08:00
const selector = String(options.selector).trim()
const root = querySelector(document, selector)
if (!root) return console.error("Smart Table " + selector + " not found")
const table = querySelector(root, "table")
if (!table) return console.error("Smart Table init need a table")
const thead = querySelector(table, "thead")
2020-02-05 11:12:40 +08:00
if (!thead) return console.error("Smart Table init need a thead")
const tbody = querySelector(table, "tbody")
2020-02-05 11:12:40 +08:00
if (!tbody) return console.error("Smart Table init need a tbody")
2020-02-05 11:12:40 +08:00
root.classList.add("smart-table")
options.size && root.classList.add("stb-cust-" + options.size)
options.align && root.classList.add("stb-cust-" + options.align)
2020-02-06 16:43:18 +08:00
hasAttribute(table, "stripe") && tbody.classList.add("stripe")
2020-02-06 18:51:14 +08:00
options.expand ? (querySelectorAll(thead, 'th[sort]').forEach(column => {
2020-02-06 16:43:18 +08:00
removeAttribute(column, "sort")
}), querySelectorAll(thead, 'th[fixed]').forEach(column => {
removeAttribute(column, "fixed")
2020-02-06 18:51:14 +08:00
})) : (querySelectorAll(tbody, 'tr[expand]').forEach(column => {
removeAttribute(column, "expand")
}), querySelectorAll(thead, 'tr[expand-parent]').forEach(column => {
removeAttribute(column, "expand-parent")
2020-02-06 16:43:18 +08:00
}))
2020-01-13 16:25:20 +08:00
2020-02-05 11:12:40 +08:00
const vm = this;
vm.$root = root
2020-02-05 11:12:40 +08:00
vm.$thead = thead
vm.$tbody = tbody
vm.options = options
vm.gutterWidth = scrollBarWidth()
vm.style = {
hoverBgColor: options.hoverBgColor || '#EFF8FF'
2020-01-13 16:25:20 +08:00
}
vm.size = {}
2020-01-20 11:28:19 +08:00
2020-01-13 16:25:20 +08:00
//初始化thead 并获取props
initProps(vm)
2020-02-05 11:12:40 +08:00
layout(vm, table)
2020-01-13 16:25:20 +08:00
2020-02-06 16:43:18 +08:00
if (!options.expand) {
//初始化fixed元素及宽度
initFixed(vm)
createFixed(vm, thead, tbody)
}
2020-01-13 16:25:20 +08:00
//获取tbody的data数据
initData(vm, tbody)
2020-02-06 16:43:18 +08:00
options.expand && initExpand(vm, tbody)
bindEvents(vm)
2020-01-13 16:25:20 +08:00
const th = createElement("th");
th.style.display = 'none'
2020-02-06 16:43:18 +08:00
setAttribute(th, "width", vm.gutterWidth);
setAttribute(th, "rowspan", vm.props.shapes.length);
appendChild(querySelector(vm.$thead, "tr"), th);
vm.$scrollTH = th;
if (vm.scrollY) {
vm.$scrollTH.style.display = 'table-cell'
resize(vm)
2020-01-13 16:25:20 +08:00
}
}
}
2020-02-05 11:12:40 +08:00
function layout(vm, table) {
const { $root, $thead, $tbody, options } = vm;
const { height } = options;
querySelectorAll($thead, "th").forEach(cell => refactorCell(cell))
querySelectorAll($tbody, "td").forEach(cell => refactorCell(cell))
//初始化colgroup数据数组
initColgroupData(vm);
//初始化table 拆分table的thead和tbody
vm.$theadWrapper = createTableWrapper("stb_header-wrapper", vm, "header", $thead);
vm.$tbodyWrapper = createTableWrapper("stb_body-wrapper", vm, "body", $tbody);
appendChildren($root, [vm.$theadWrapper, vm.$tbodyWrapper]);
const theadHeight = $thead.offsetHeight;
const offsetHeight = (typeof height === 'function' ? height.call() : height) || $root.offsetHeight;
const tbodyWrapperHeight = offsetHeight > theadHeight ? (offsetHeight - theadHeight - 1) : (theadHeight + 150)
vm.$tbodyWrapper.style.height = tbodyWrapperHeight + "px";
vm.size.tbodyWrapperHeight = tbodyWrapperHeight;
//垂直方向是否有滚动条
vm.scrollY = vm.$tbody.offsetHeight > tbodyWrapperHeight;
2020-02-05 11:12:40 +08:00
//删除空余的table节点
removeChild(table.parentNode, table);
2020-01-13 16:25:20 +08:00
}
//根据表格中的tbody第一行 查出每列的宽度并记录
2020-02-05 11:12:40 +08:00
function initColgroupData(vm) {
const { $root, props } = vm;
const offsetWidth = $root.offsetWidth - 1;
const clientWidth = offsetWidth - (vm.scrollY ? vm.gutterWidth : 0);
2020-01-13 16:25:20 +08:00
let arr = [];
2020-02-05 11:12:40 +08:00
let totalWidth = 0;
props.shapes.forEach(shape => {
shape.forEach((column, cIndex) => {
if (column) {
2020-02-06 18:51:14 +08:00
let colspan = getAttrNumber(column, 'colspan', 1)
2020-02-05 11:12:40 +08:00
if (colspan === 1) {
2020-02-06 18:51:14 +08:00
arr[cIndex] = getAttrNumber(column, 'width', 0)
2020-02-05 11:12:40 +08:00
}
2020-01-17 15:02:45 +08:00
}
})
2020-02-05 11:12:40 +08:00
})
let zeroLen = 0;
arr.forEach(item => {
totalWidth += item
item === 0 && zeroLen++
})
if (zeroLen) {
const per = Math.floor((clientWidth - totalWidth) / zeroLen)
const min = per > 80 ? per : 80;
2020-02-05 11:12:40 +08:00
let lastZeroIndex = 0;
totalWidth = 0;
arr = arr.map((item, index) => {
if (item === 0) {
item = min;
lastZeroIndex = index;
2020-01-17 15:02:45 +08:00
}
2020-02-05 11:12:40 +08:00
totalWidth += item
return item
2020-01-17 15:02:45 +08:00
})
2020-02-05 11:12:40 +08:00
if (clientWidth > totalWidth) {
arr[lastZeroIndex] = arr[lastZeroIndex] + clientWidth - totalWidth;
totalWidth = clientWidth
}
} else {
totalWidth = arr.reduce((item, sum) => sum + item, 0)
}
vm.colgroup = arr;
vm.size.rootWidth = offsetWidth
vm.size.tableWidth = totalWidth
vm.scrollX = vm.size.tableWidth > vm.size.rootWidth;
2020-01-13 16:25:20 +08:00
}
2020-02-05 11:12:40 +08:00
function bindResizeEvents(vm) {
window.addEventListener("resize", () => resize(vm), {
passive: true
})
2020-02-05 11:12:40 +08:00
}
2020-01-16 17:17:57 +08:00
function bindEvents(vm) {
2020-02-06 16:43:18 +08:00
bindScrollEvents(vm);
bindHoverEvents(vm);
vm.options.expand ? bindExpandEvents(vm) : bindSortEvents(vm);
bindResizeEvents(vm);
2020-01-16 14:25:41 +08:00
}
function resize(vm) {
debounce(500, () => {
const { fixedLeft, fixedRight } = vm.props;
initColgroupData(vm)
replaceColGroup(vm)
vm.scrollY = vm.$tbody.offsetHeight > vm.size.tbodyWrapperHeight;
vm.$scrollTH.style.display = vm.scrollY ? 'table-cell' : 'none';
let height = (vm.$root.offsetHeight - (vm.scrollX ? vm.gutterWidth : 2));
const tableHeight = vm.$thead.offsetHeight + vm.$tbody.offsetHeight;
height = tableHeight > height ? height : tableHeight
const bodyWrapperHeight = vm.size.tbodyWrapperHeight - (vm.scrollX ? vm.gutterWidth : 0);
const bodyWrapperTop = vm.$thead.offsetHeight;
let fixedLeftWidth = 0;
let fixedRightWidth = 0;
if (vm.$fixedLeft && fixedLeft.thead.length) {
fixedLeft.thead.forEach((item, index) => {
fixedLeftWidth += vm.colgroup[index]
})
vm.$fixedLeftBody.style.height = bodyWrapperHeight + "px";
vm.$fixedLeftBody.style.top = bodyWrapperTop + "px";
vm.$fixedLeft.style.width = fixedLeftWidth + "px";
vm.$fixedLeft.style.height = height + "px";
}
if (vm.$fixedRight && fixedRight.thead.length) {
fixedRight.thead.forEach((item, index) => {
fixedRightWidth += vm.colgroup[vm.colgroup.length - index - 1]
})
vm.$fixedRightBody.style.height = bodyWrapperHeight + "px";
vm.$fixedRightBody.style.top = bodyWrapperTop + "px";
vm.$fixedRight.style.width = fixedRightWidth + "px"
vm.$fixedRight.style.height = height + "px";
vm.$fixedRight.style.right = (vm.scrollY ? vm.gutterWidth : 0) + "px";
vm.$rightPatch.style.display = vm.scrollY ? 'block' : 'none';
2020-02-06 22:45:09 +08:00
vm.$rightPatch.style.height = vm.$thead.offsetHeight + "px";
}
})()
2020-01-13 16:25:20 +08:00
}
2020-01-16 17:17:57 +08:00
function syncPostion(vm) {
2020-01-13 16:25:20 +08:00
throttle(20, () => {
vm.$theadWrapper.scrollLeft = vm.$tbodyWrapper.scrollLeft;
if (vm.$fixedLeftBody) {
vm.$fixedLeftBody.scrollTop = vm.$tbodyWrapper.scrollTop;
2020-01-13 16:25:20 +08:00
}
if (vm.$fixedRightBody) {
vm.$fixedRightBody.scrollTop = vm.$tbodyWrapper.scrollTop;
2020-01-13 16:25:20 +08:00
}
})()
}
2020-02-06 16:43:18 +08:00
function bindExpandEvents(vm) {
function seek(arr) {
arr.forEach(item => {
if (item.expand) {
2020-02-06 18:51:14 +08:00
querySelector(item.$el, "td").addEventListener('click', () => {
expanding(item, !hasAttribute(item.$el, "expanded"))
2020-02-06 16:43:18 +08:00
})
2020-02-06 18:51:14 +08:00
item.children && seek(item.children)
2020-02-06 16:43:18 +08:00
}
})
2020-02-06 18:51:14 +08:00
function expanding(item, expanded) {
expanded ? setAttribute(item.$el, "expanded") : removeAttribute(item.$el, "expanded");
item.children && (item.children.forEach(child => {
child.$el.style.display = expanded ? '' : 'none';
expanding(child, expanded)
}))
}
}
2020-02-06 16:43:18 +08:00
seek(vm.expandData)
}
2020-02-02 15:20:49 +08:00
function bindScrollEvents(vm) {
vm.$tbodyWrapper.addEventListener("scroll", () => syncPostion(vm), {
passive: true
})
}
function bindHoverEvents(vm) {
let data = [].concat(vm.data, vm.unsortData)
data.forEach(row => {
addHoverEventByEl(vm, row.$el, [row.$fixedLeftEl, row.$fixedRightEl])
row.$fixedLeftEl && addHoverEventByEl(vm, row.$fixedLeftEl, [row.$el, row.$fixedRightEl])
row.$fixedRightEl && addHoverEventByEl(vm, row.$fixedRightEl, [row.$el, row.$fixedLeftEl])
})
}
function addHoverEventByEl(vm, trigger, relates) {
if (!trigger) return;
trigger.addEventListener('mouseenter', () => {
trigger.style.background = vm.style.hoverBgColor
relates.forEach(el => {
el && (el.style.background = vm.style.hoverBgColor)
2020-02-02 15:20:49 +08:00
})
})
trigger.addEventListener('mouseleave', () => {
trigger.style.background = ''
relates.forEach(el => {
el && (el.style.background = '')
2020-02-02 15:20:49 +08:00
})
})
}
function bindSortEvents(vm) {
let els = Array.from(querySelectorAll(vm.$root, "th[sort]"));
2020-01-13 16:25:20 +08:00
if (els.length === 0) return;
els.forEach(el => {
el.addEventListener("click", $event => {
$event.stopPropagation();
let sortType = "ASC";
2020-02-06 18:51:14 +08:00
let sortOrder = getAttribute(el, "sort") || "string";
2020-01-13 16:25:20 +08:00
if (el.classList.contains("asc")) {
el.classList.remove("asc");
el.classList.add("desc");
sortType = "DESC"
} else {
el.classList.remove("desc");
el.classList.add("asc");
}
els = els.map(item => {
if (el != item) {
item.classList.remove("asc", "desc")
}
return item
})
2020-02-06 18:51:14 +08:00
sort(vm, getAttribute(el, "sortkey"), sortType, sortOrder)
2020-01-13 16:25:20 +08:00
})
})
}
function initProps(vm) {
2020-01-13 16:25:20 +08:00
let props = {};
//创建表头单元格二维数组
let shapes = [];
let rows = querySelectorAll(vm.$thead, "tr");
2020-01-13 16:25:20 +08:00
rows.forEach((row, index) => {
let shape = shapes[index] || [];
let columns = querySelectorAll(row, "th");
2020-01-13 16:25:20 +08:00
columns.forEach((column) => {
2020-02-06 18:51:14 +08:00
let rowspan = getAttrNumber(column, "rowspan", 1);
let colspan = getAttrNumber(column, "colspan", 1);
2020-01-13 16:25:20 +08:00
let insertIndex = getEmptyIndexInArray(shape) || shape.length;
shape[insertIndex] = column;
2020-02-06 16:43:18 +08:00
if (hasAttribute(column, "sort")) {
setAttribute(column, "sortkey", "field-" + insertIndex);
2020-01-13 16:25:20 +08:00
}
if (colspan > 1) {
for (let i = 1; i < colspan; i++) {
shape[insertIndex + i] = 0;
}
}
if (rowspan > 1) {
for (let i = 1; i < rowspan; i++) {
let next = shapes[index + i] || [];
for (let j = 0; j < colspan; j++) {
next[insertIndex + j] = 0;
}
shapes[index + i] = next;
}
}
shapes[index] = shape;
})
})
props.shapes = shapes;
vm.props = props;
2020-01-13 16:25:20 +08:00
}
function initFixed(vm) {
2020-01-13 16:25:20 +08:00
let {
colgroup,
props
} = vm;
2020-02-02 15:20:49 +08:00
const colgroupLen = colgroup.length;
2020-01-13 16:25:20 +08:00
let fixedLeft = {
thead: [],
tbody: [],
width: 0
};
let fixedRight = {
thead: [],
tbody: [],
width: 0
};
const columns = querySelectorAll(vm.$thead, "tr:first-child>th");
2020-01-13 16:25:20 +08:00
const len = columns.length;
let lastLeftIndex = 0;
if (len !== 0) {
2020-02-06 16:43:18 +08:00
if (hasAttribute(columns[0], "fixed")) {
2020-01-13 16:25:20 +08:00
//判断左边固定项 不判断最后一项做为左固定项目
for (let i = 0; i < len - 1; i++) {
2020-02-06 16:43:18 +08:00
if (hasAttribute(columns[i], "fixed")) {
2020-01-13 16:25:20 +08:00
lastLeftIndex = i;
fixedLeft.thead.push("field-" + i);
2020-02-06 18:51:14 +08:00
let colspan = getAttrNumber(columns[i], "colspan", 1);
2020-01-13 16:25:20 +08:00
for (let j = 0; j < colspan; j++) {
fixedLeft.tbody.push("field-" + (i + j));
fixedLeft.width += colgroup[i + j];
2020-01-13 16:25:20 +08:00
}
} else {
break;
2020-01-13 16:25:20 +08:00
}
}
}
2020-02-06 16:43:18 +08:00
if (hasAttribute(columns[len - 1], "fixed")) {
2020-01-13 16:25:20 +08:00
//判断右边边固定项 不判断第一项做为右边固定项目
let rightCnt = 0;
for (let i = len - 1; i > 0; i--) {
2020-02-06 16:43:18 +08:00
if (hasAttribute(columns[i], "fixed")) {
2020-01-13 16:25:20 +08:00
//左右固定项目重叠时 跳出
if (i === lastLeftIndex) {
break;
}
fixedRight.thead.push("field-" + i);
2020-02-06 18:51:14 +08:00
let colspan = getAttrNumber(columns[i], "colspan", 1);
2020-01-13 16:25:20 +08:00
for (let j = 0; j < colspan; j++) {
rightCnt++;
2020-02-02 15:20:49 +08:00
fixedRight.tbody.push("field-" + (colgroupLen - rightCnt))
fixedRight.width += colgroup[colgroupLen - rightCnt];
2020-01-13 16:25:20 +08:00
}
} else {
break;
2020-01-13 16:25:20 +08:00
}
}
}
}
props.fixedLeft = fixedLeft;
props.fixedRight = fixedRight;
}
2020-02-06 16:43:18 +08:00
function initExpand(vm, tbody) {
2020-02-06 18:51:14 +08:00
const expandAll = vm.options.defaultExpandAll;
2020-02-06 16:43:18 +08:00
let data = [];
let parents = [];
querySelectorAll(tbody, "tr").forEach(row => {
2020-02-06 18:51:14 +08:00
let paddingLength = parents.length;
2020-02-06 16:43:18 +08:00
let expand = hasAttribute(row, "expand");
2020-02-06 18:51:14 +08:00
let hasParent = hasAttribute(row, "expand-parent");
let node = { $el: row, id: getAttribute(row, "expand"), expand: expand };
2020-02-06 16:43:18 +08:00
if (expand) {
2020-02-06 18:51:14 +08:00
if (expandAll) {
2020-02-06 16:43:18 +08:00
setAttribute(row, 'expanded')
} else {
removeAttribute(row, 'expanded')
}
}
2020-02-06 18:51:14 +08:00
if (expand && !hasParent) {
2020-02-06 16:43:18 +08:00
node.children = [];
parents = [node];
data.push(node);
2020-02-06 18:51:14 +08:00
} else if (hasParent) {
let parentId = getAttribute(row, "expand-parent");
let arr = [];
for (let i = 0; i < parents.length; i++) {
arr.push(parents[i])
if (parents[i].id === parentId) {
break
}
2020-02-06 16:43:18 +08:00
}
2020-02-06 18:51:14 +08:00
parents = arr;
paddingLength = parents.length;
let parent = parents[parents.length - 1];
parent && parent.children.push(node);
if (expand) {
parents.push(node)
node.children = [];
2020-02-06 16:43:18 +08:00
}
} else {
data.push(node)
}
2020-02-06 18:51:14 +08:00
if (hasParent) {
querySelector(row, "td").style.paddingLeft = 20 * paddingLength + "px";
row.style.display = expandAll ? '' : 'none'
}
2020-02-06 16:43:18 +08:00
})
vm.expandData = data;
vm.$tbodyWrapper.style.height = ''
}
2020-01-16 17:17:57 +08:00
function initData(vm, tbody) {
let fixedLeftRows = vm.$fixedLeftBody && querySelectorAll(vm.$fixedLeftBody, "tbody tr");
let fixedRightRows = vm.$fixedRightBody && querySelectorAll(vm.$fixedRightBody, "tbody tr");
2020-01-13 16:25:20 +08:00
let data = [];
let unsortData = [];
querySelectorAll(tbody, "tr").forEach((row, index) => {
let rowData = {
$el: row,
$fixedLeftEl: fixedLeftRows && fixedLeftRows[index],
$fixedRightEl: fixedRightRows && fixedRightRows[index],
$key: '$$rowkey' + index
};
querySelectorAll(row, "td .stb_cell").forEach((cell, index) => {
rowData["field-" + index] = cell.innerHTML;
})
2020-02-06 16:43:18 +08:00
if (!hasAttribute(row, "unsort")) {
2020-01-13 16:25:20 +08:00
data.push(rowData)
} else {
unsortData.push(rowData)
2020-01-13 16:25:20 +08:00
}
})
vm.data = data;
vm.unsortData = unsortData;
2020-01-28 22:13:48 +08:00
}