import '../css/grid.scss'; import { global, isPositive, isMobile, throttle, truncate } from "../../utility"; import { r as lang } from "../../utility/lgres"; import { nullOrEmpty } from "../../utility/strings"; import { createElement } from "../../functions"; import { createIcon } from "../icon"; import { createCheckbox } from "../checkbox"; import { setTooltip } from "../tooltip"; import { Popup, showAlert } from "../popup"; import { convertCssStyle } from "../extension"; import { GridColumn, GridInputColumn, GridTextColumn, GridDropdownColumn, GridCheckboxColumn, GridIconColumn, GridDateColumn } from "./column"; const ColumnChangedType = { Reorder: 'reorder', Resize: 'resize', Sort: 'sort' }; const RefreshInterval = isMobile() ? 32 : 0; const HoverInternal = 200; const RedumCount = 4; const MiniDragOffset = 10; const MiniColumnWidth = 50; const FilterPanelWidth = 200; function getClientX(e) { if (e == null) { return null; } const cx = e.touches && e.touches[0]?.clientX; return cx ?? e.clientX; } function indexOfParent(target) { // return [...target.parentElement.children].indexOf(target); return Array.prototype.indexOf.call(target.parentElement.children, target); } const ColumnTypes = { 0: GridColumn, 1: GridInputColumn, 2: GridDropdownColumn, 3: GridCheckboxColumn, 4: GridIconColumn, 5: GridTextColumn, 6: GridDateColumn }; let r = lang; export class Grid { _var = { selectedColumnIndex: -1, startIndex: 0, rowCount: -1, colTypes: {}, colAttrs: {} }; // _var.source; // _var.currentSource; // _var.parent; // _var.el; // _var.refs; // _var.rendering; // _var.selectedColumnIndex = -1; // _var.selectedIndexes; // _var.startIndex = 0; // _var.needResize; // _var.containerHeight; // _var.bodyClientWidth; // _var.rowCount = -1; // _var.scrollTop; // _var.scrollLeft; // _var.colTypes = {}; // _var.colAttrs = {}; // _var.vtable = []; columns = []; langs = {}; virtualCount = 100; rowHeight = 36; lineHeight = 24; extraRows = 0; filterRowHeight = 30; height; readonly; multiSelect = false; fullrowClick = true; tooltipDisabled = false; headerVisible = true; window = global; sortIndex = -1; sortDirection = 1; sortArray = null; willSelect; cellClicked; onSelectedRowChanged; onCellDblClicked; onRowDblClicked; onColumnChanged; onBodyScrolled; static ColumnTypes = { Common: 0, Input: 1, Dropdown: 2, Checkbox: 3, Icon: 4, Text: 5, Date: 6, isCheckbox(type) { return type === 3 } }; constructor(container, getText) { this._var.parent = typeof container === 'string' ? document.querySelector(container) : container; if (typeof getText === 'function') { r = getText; } this.langs = { all: r('allItem', '( All )'), ok: r('ok', 'OK'), reset: r('reset', 'Reset'), cancel: r('cancel', 'Cancel'), null: r('null', '( Null )'), addLevel: r('', 'Add level'), deleteLevel: r('', 'Delete level'), copyLevel: r('', 'Copy level'), asc: r('', 'Ascending'), desc: r('', 'Descending'), column: r('', 'Column'), order: r('', 'Order'), sort: r('', 'Sort'), requirePrompt: r('', 'Column required.'), duplicatePrompt: r('', 'Column duplicated: "{column}"') }; } get element() { return this._var.el } get changed() { const source = this._var.source; if (source == null) { return false; } return source.find(r => r.__changed) != null; } get allSource() { return this._var.source?.map(s => s.values) } get source() { return this._var.currentSource?.map(s => s.values) } set source(list) { if (this._var.el == null) { throw new Error('grid has not been initialized.') } if (!Array.isArray(list)) { throw new Error('source is not an Array.') } list = list.map((it, index) => { return { __index: index, values: it } }); this._var.source = list; this._refreshSource(list); } setItem(index, item) { if (this._var.currentSource == null) { throw new Error('no source'); } const it = this._var.currentSource[index]; // clear dropdown source cache delete it.source; it.values = item; this.refresh(); } addItem(item, index) { if (this._var.currentSource == null) { throw new Error('no source'); } const it = index >= 0 ? this._var.currentSource[index] : null; const newIt = { __index: null, values: item }; if (it != null) { newIt.__index = it.__index; this._var.currentSource.splice(index, 0, newIt); if (this._var.colAttrs.__filtered === true) { this._var.source.splice(it.__index, 0, newIt); } for (let i = it.__index + 1; i < this._var.source.length; ++i) { this._var.source[i].__index += 1; } } else { newIt.__index = this._var.source.length; this._var.currentSource.push(newIt); if (this._var.colAttrs.__filtered === true) { this._var.source.push(newIt); } } this.reload(); return item; } addItems(array, index) { if (this._var.currentSource == null) { throw new Error('no source'); } if (!Array.isArray(array) || array.length <= 0) { // throw new Error(`invalid items array: ${array}`); return; } const it = index >= 0 ? this._var.currentSource[index] : null; if (it != null) { const items = array.map((a, i) => ({ __index: it.__index + i, values: a })); this._var.currentSource.splice(index, 0, ...items); if (this._var.colAttrs.__filtered === true) { this._var.source.splice(it.__index, 0, ...items); } const offset = array.length; for (let i = it.__index + offset; i < this._var.source.length; ++i) { this._var.source[i].__index += offset; } } else { const length = this._var.source.length; const items = array.map((a, i) => ({ __index: length + i, values: a })); this._var.currentSource.push(...items); if (this._var.colAttrs.__filtered === true) { this._var.source.push(...items); } } this.reload(); return array; } removeItem(index) { if (this._var.currentSource == null) { throw new Error('no source'); } const it = this._var.currentSource.splice(index, 1)[0]; if (it == null) { return null; } if (this._var.colAttrs.__filtered === true) { this._var.source.splice(it.__index, 1); } for (let i = it.__index; i < this._var.source.length; ++i) { this._var.source[i].__index -= 1; } if (index < 1) { this._var.selectedIndexes = [index - 1]; } else { this._var.selectedIndexes = []; } this.reload(); return it.values; } removeItems(indexes) { if (this._var.currentSource == null) { throw new Error('no source'); } if (Array.isArray(indexes) && indexes.length > 0) { indexes = indexes.slice().sort(); } else { indexes = this._var.currentSource.map(a => a.__index); } const array = []; let first = 0; for (let i = indexes.length - 1; i >= 0; --i) { let it = this._var.currentSource.splice(indexes[i], 1)[0]; if (it == null) { continue; } let next = this._var.source[it.__index]; if (next != null && next.__offset == null) { next.__offset = i + 1; } if (this._var.colAttrs.__filtered === true) { this._var.source.splice(it.__index, 1); } array.splice(0, 0, it.values); first = it.__index; } let offset = 1; for (let i = first; i < this._var.source.length; ++i) { let it = this._var.source[i]; if (it.__offset > 0) { offset = it.__offset; delete it.__offset; } it.__index -= offset; } const index = indexes[0]; if (index < 1) { this._var.selectedIndexes = [index - 1]; } else { this._var.selectedIndexes = []; } this.reload(); return array; } _refreshSource(list) { list ??= this._var.source; if (this._var.colAttrs.__filtered === true) { this._var.currentSource = list.filter(it => { for (let col of this.columns) { if (Array.isArray(col.filterValues)) { const v = this._getItemProp(it.values, false, col); if (col.filterValues.indexOf(v) < 0) { return false; } } } return true; }); } else { this._var.currentSource = list; } this._var.selectedColumnIndex = -1; this._var.selectedIndexes = []; this._var.startIndex = 0; this._var.scrollTop = 0; this._var.scrollLeft = 0; this._var.rowCount = -1; if (this.sortIndex >= 0) { this.sortColumn(); } else if (this.sortArray?.length > 0) { this.sort(); } this.resize(); } get virtual() { return this._var.currentSource?.length > this.virtualCount } get sortKey() { if (this.columns == null) { return null; } return this.columns[this.sortIndex]?.key; } get tableRows() { // return [...this._var.refs.body.children]; return Array.prototype.slice.call(this._var.refs.body.children); } get selectedIndexes() { return this._var.selectedIndexes } set selectedIndexes(indexes) { const startIndex = this._var.startIndex; this._var.selectedIndexes.splice(0, this._var.selectedIndexes.length, ...indexes); if (this.readonly !== true) { this.refresh(); } else { this.tableRows.forEach((row, i) => { if (indexes.includes(startIndex + i)) { row.classList.add('selected'); } else if (row.classList.contains('selected')) { row.classList.remove('selected'); } }); } if (typeof this.onSelectedRowChanged === 'function') { this.onSelectedRowChanged(); } } get selectedIndex() { return (this._var.selectedIndexes && this._var.selectedIndexes[0]) ?? -1 } get loading() { return this._var.refs.loading?.style?.visibility === 'visible' } set loading(flag) { if (this._var.refs.loading == null) { return; } if (flag === false) { this._var.refs.loading.style.visibility = 'hidden'; this._var.refs.loading.style.opacity = 0; } else { this._var.refs.loading.style.visibility = 'visible'; this._var.refs.loading.style.opacity = 1; } } get scrollTop() { return this._var.el?.scrollTop; } set scrollTop(top) { if (this._var.el == null) { return; } this._var.el.scrollTop = top; this.reload(true); } init(container = this._var.parent) { this._var.el = null; this._var.refs = {}; this._var.rendering = true; if (container == null) { throw new Error('no specified parent.'); } if (!(container instanceof HTMLElement)) { const ele = container[0]; if (!(ele instanceof HTMLElement)) { throw new Error(`parent type not supported. ${JSON.stringify(Object.getPrototypeOf(container))}`); } container = ele; } this._var.parent = container; const grid = createElement('div', 'ui-grid'); grid.setAttribute('tabindex', 0); grid.addEventListener('keydown', e => { let index = this.selectedIndex; let flag = false; if (e.key === 'ArrowUp') { // up if (index > 0) { flag = true; index -= 1; } } else if (e.key === 'ArrowDown') { // down const count = this._var.currentSource?.length ?? 0; if (index < count - 1) { flag = true; index += 1; } } if (flag) { this._var.selectedIndexes = [index]; this.scrollToIndex(index); this.refresh(); if (typeof this.onSelectedRowChanged === 'function') { this.onSelectedRowChanged(index); } e.stopPropagation(); } }); container.replaceChildren(grid); const sizer = createElement('span', 'ui-grid-sizer'); grid.appendChild(sizer); this._var.refs.sizer = sizer; grid.addEventListener('scroll', e => throttle(this._onScroll, RefreshInterval, this, e), { passive: true }); // header & body const wrapper = createElement('div', 'ui-grid-wrapper'); this._var.refs.wrapper = wrapper; grid.appendChild(wrapper); const table = createElement('table', 'ui-grid-table'); this._var.refs.table = table; this._createHeader(table); const body = this._createBody(table); wrapper.appendChild(table); // tooltip if (!this.tooltipDisabled) { const holder = createElement('div', 'ui-grid-hover-holder'); holder.addEventListener('mousedown', e => { const holder = e.currentTarget; const row = Number(holder.dataset.row); const col = Number(holder.dataset.col); if (holder.classList.contains('active')) { holder.classList.remove('active'); } return this._onRowClicked(e, row, col); }); holder.addEventListener('dblclick', e => this._onRowDblClicked(e)); wrapper.appendChild(holder); body.addEventListener('mousemove', e => throttle(this._onBodyMouseMove, HoverInternal, this, e, holder), { passive: true }); } // loading const loading = createElement('div', 'ui-grid-loading', createElement('div', null, createIcon('fa-regular', 'spinner-third')) ); this._var.refs.loading = loading; grid.appendChild(loading); this._var.el = grid; this._var.rendering = false; if (this._var.source != null) { if (this.sortIndex >= 0) { this.sortColumn(); } else if (this.sortArray?.length > 0) { this.sort(); } } } setData(source) { this.source = source; } scrollToIndex(index) { const top = this._scrollToTop(index * (this.rowHeight + 1), true); this._var.el.scrollTop = top; } resize(force, keep) { if (this._var.rendering || this._var.el == null) { return; } const body = this._var.refs.body; const top = this.headerVisible === false ? 0 : this._var.refs.header.offsetHeight; let height = this.height; if (height === 0) { height = this._var.containerHeight; } else if (isNaN(height) || height < 0) { height = this._var.el.offsetHeight - top; } const count = truncate((height - 1) / (this.rowHeight + 1)) + (RedumCount * 2) + 1; if (force || count !== this._var.rowCount) { this._var.rowCount = count; this.reload(keep); } this._var.bodyClientWidth = body.clientWidth; } reload(keep) { const filtered = this.columns.some(c => c.filterValues != null); if ((filtered ^ this._var.colAttrs.__filtered) === 1) { this._var.colAttrs.__filtered = filtered; const headers = this._var.refs.header.children; for (let i = 0; i < this.columns.length; ++i) { const ele = headers[i].querySelector('.filter'); if (ele == null) { continue; } if (this.columns[i].filterValues != null) { ele.classList.add('active'); } else { ele.classList.remove('active'); } } this._refreshSource(); return; } let length = this._var.currentSource?.length ?? 0; if (this.extraRows > 0) { length += this.extraRows; } this._var.containerHeight = length * (this.rowHeight + 1); if (!keep) { this._var.el.scrollTop = 0; // this._var.el.scrollLeft = 0; this._var.refs.table.style.top = '0px'; } this._var.refs.wrapper.style.height = `${this._var.containerHeight}px`; this._adjustRows(this._var.refs.body); this.refresh(); } refresh() { if (this._var.refs.body == null) { throw new Error('body has not been created.'); } const widths = {}; this._fillRows(this.tableRows, this.columns, widths); if (this._var.needResize && widths.flag) { this._var.needResize = false; this.columns.forEach((col, i) => { if (!this._get(col.key, 'autoResize')) { return; } let width = widths[i]; if (width < col.width) { width = col.width; } if (width > 0) { this._changeColumnWidth(i, width); } }); } } resetChange() { if (this._var.source == null) { return; } for (let row of this._var.source) { delete row.__changed; } } _getComparer(col, direction) { if (typeof col.sortFilter !== 'function') { if (isNaN(direction)) { direction = 1; } return (a, b) => { a = this._getItemProp(a.values, true, col); b = this._getItemProp(b.values, true, col); if (a == null && typeof b === 'number') { a = 0; } else if (typeof a === 'number' && b == null) { b = 0; } else if (a != null && b == null) { return direction; } else if (typeof a === 'string' && typeof b === 'string') { a = a.toLowerCase(); b = b.toLowerCase(); } return a === b ? 0 : (a > b ? 1 : -1) * direction; }; } return (a, b) => col.sortFilter(a.values, b.values) * direction; } sortColumn(reload) { const index = this.sortIndex; const col = this.columns[index]; if (col == null) { return; } this.sortArray = null; const direction = this.sortDirection; [...this._var.refs.header.children].forEach((th, i) => { const arrow = th.querySelector('.arrow'); if (arrow == null) { return; } if (i === index) { arrow.className = `arrow ${(direction !== 1 ? 'desc' : 'asc')}`; } else if (arrow.className !== 'arrow') { arrow.className = 'arrow'; } }); const comparer = this._getComparer(col, direction); this._var.source.sort(comparer); if (this._var.colAttrs.__filtered === true) { this._var.currentSource.sort(comparer); } if (this._var.rowCount < 0) { return; } if (reload) { this.reload(); } else { this.refresh(); } } sort(reload) { const sortArray = this.sortArray; if (sortArray == null || sortArray.length === 0) { return; } this.sortIndex = -1; const comparer = (a, b) => { for (let i = 0; i < sortArray.length; ++i) { const s = sortArray[i]; const col = this.columns.find(c => c.key === s.column && c.visible !== false); if (col != null) { const result = this._getComparer(col, s.order === 'desc' ? -1 : 1)(a, b); if (result !== 0) { return result; } } } return 0; }; this._var.source.sort(comparer); if (this._var.colAttrs.__filtered === true) { this._var.currentSource.sort(comparer); } if (this._var.rowCount < 0) { return; } if (reload) { this.reload(); } else { this.refresh(); } // arrow icon [...this._var.refs.header.children].forEach((th, i) => { const arrow = th.querySelector('.arrow'); if (arrow == null) { return; } const col = this.columns[i]; const s = sortArray.find(s => s.column === col.key && col.visible !== false); if (s != null) { arrow.className = `arrow ${s.order}`; } else if (arrow.className !== 'arrow') { arrow.className = 'arrow'; } }); } clearHeaderCheckbox() { const boxes = this._var.refs.header.querySelectorAll('.ui-check-wrapper>input'); boxes.forEach(box => box.checked = false); } showSortPanel() { const content = createElement('div', 'ui-sort-panel-content'); const buttonWrapper = createElement('div', 'ui-sort-panel-buttons'); const grid = new Grid(null, r); grid.langs = this.langs; const rowChanged = index => { buttonWrapper.querySelector('.ui-button-delete').disabled = index < 0; buttonWrapper.querySelector('.ui-button-copy').disabled = index < 0; buttonWrapper.querySelector('.ui-button-move-up').disabled = index < 1; buttonWrapper.querySelector('.ui-button-move-down').disabled = index >= grid.source.length - 1; }; grid.onSelectedRowChanged = rowChanged; const reload = index => { grid.selectedIndexes = [index]; grid.scrollTop = index * grid.rowHeight; rowChanged(index); } buttonWrapper.append( createElement('button', null, createIcon('fa-light', 'plus'), createElement('span', span => { span.innerText = this.langs.addLevel; span.addEventListener('click', () => { let index = grid.selectedIndex; const n = { column: '', order: 'asc' }; if (index >= 0) { index += 1; grid.addItem(n, index); } else { grid.addItem(n); index = grid.source.length - 1; } reload(index); }); }) ), createElement('button', 'ui-button-delete', createIcon('fa-light', 'times'), createElement('span', span => { span.innerText = this.langs.deleteLevel; span.addEventListener('click', () => { let index = grid.selectedIndex; if (index < 0) { return; } grid.removeItem(index); const length = grid.source.length; if (index >= length) { index = length - 1; } reload(index); }); }) ), createElement('button', 'ui-button-copy', createIcon('fa-light', 'copy'), createElement('span', span => { span.innerText = this.langs.copyLevel; span.addEventListener('click', () => { const index = grid.selectedIndex; if (index < 0) { return; } const item = grid.source[index]; if (item == null) { return; } grid.addItem(Object.assign({}, item), index + 1); reload(index + 1); }); }) ), createElement('button', button => { button.className = 'ui-button-move-up'; const icon = createIcon('fa-light', 'chevron-up'); icon.addEventListener('click', () => { const index = grid.selectedIndex; if (index < 1) { return; } const item = grid.source[index]; if (item == null) { return; } const it = grid.removeItem(index); grid.addItem(it, index - 1); reload(index - 1); }); button.appendChild(icon); }), createElement('button', button => { button.className = 'ui-button-move-down'; const icon = createIcon('fa-light', 'chevron-down'); icon.addEventListener('click', () => { const index = grid.selectedIndex; if (index >= grid.source.length - 1) { return; } const item = grid.source[index]; if (item == null) { return; } const it = grid.removeItem(index); grid.addItem(it, index + 1); reload(index + 1); }); button.appendChild(icon); }) ); const gridWrapper = createElement('div', 'ui-sort-panel-grid'); content.append(buttonWrapper, gridWrapper); const columnSource = this.columns.filter(c => c.sortable !== false && c.visible !== false); grid.columns = [ { key: 'column', caption: this.langs.column, width: 270, type: Grid.ColumnTypes.Dropdown, dropOptions: { textKey: 'caption', valueKey: 'key' }, source: columnSource, sortable: false, orderable: false }, { key: 'order', caption: this.langs.order, width: 150, type: Grid.ColumnTypes.Dropdown, source: [ { value: 'asc', text: this.langs.asc }, { value: 'desc', text: this.langs.desc } ], sortable: false, orderable: false } ]; const pop = new Popup({ title: this.langs.sort, content, resizable: true, buttons: [ { text: this.langs.ok, trigger: () => { const source = grid.source; if (source == null || source.length === 0) { this.sortArray = null; } else { const dict = {}; for (let i = 0; i < source.length; ++i) { const it = source[i]; if (it.column == null || it.column === '') { grid.selectedIndexes = [i]; grid.refresh(); showAlert(this.langs.sort, this.langs.requirePrompt, 'warn'); return false; } if (Object.prototype.hasOwnProperty.call(dict, it.column)) { grid.selectedIndexes = [i]; grid.refresh(); let name = columnSource.find(c => c.key === it.column); if (name == null) { name = it.column; } else { name = name.caption; } showAlert(this.langs.sort, this.langs.duplicatePrompt.replace('{column}', name), 'warn'); return false; } dict[it.column] = true; } this.sortArray = source; this.sortDirection = 1; this.sort(); } if (typeof this.onSorted === 'function') { this.onSorted(this.sortArray); } return true; } }, { text: this.langs.cancel } ], onResizeEnded: () => grid.resize() }); const source = this.sortArray || [{ column: '', order: 'asc' }]; pop.show(this._var.el).then(() => { pop.container.style.cssText += 'width: 520px; height: 400px'; grid.init(gridWrapper); grid.source = source.filter(s => s.column === '' || columnSource.find(c => c.key === s.column) != null); grid.selectedIndexes = [0]; grid.refresh(); rowChanged(0); }); } _createHeader(table) { const thead = createElement('thead'); if (this.headerVisible === false) { thead.style.display = 'none'; } table.appendChild(thead); const header = createElement('tr'); thead.appendChild(header); const sizer = this._var.refs.sizer; let left = 0; for (let col of this.columns) { if (col.visible === false) { const hidden = createElement('th', 'column'); hidden.style.display = 'none'; if (col.sortable !== false) { hidden.dataset.key = col.key; hidden.addEventListener('click', e => this._onHeaderClicked(e, col, true)); } header.appendChild(hidden); continue; } // style const isCheckbox = Grid.ColumnTypes.isCheckbox(col.type); let type = this._var.colTypes[col.key]; if (type == null) { if (isNaN(col.type)) { type = col.type; } else { type = ColumnTypes[col.type]; } type ??= GridColumn; this._var.colTypes[col.key] = type; } if (col.width > 0 || typeof type.createCaption === 'function') { // col.autoResize = false; } else { this._set(col.key, 'autoResize', true); this._var.needResize = true; sizer.innerText = col.caption ?? ''; let width = sizer.offsetWidth + 22; if (!this.readonly && col.enabled !== false && col.allcheck && isCheckbox) { width += 32; } if (col.allowFilter === true) { width += 14; } if (width < MiniColumnWidth) { width = MiniColumnWidth; } col.width = width; } col.align ??= isCheckbox ? 'center' : 'left'; if (col.sortable !== false) { col.sortable = true; } const w = `${col.width}px`; const style = { 'width': w, 'max-width': w, 'min-width': w, 'text-align': col.align }; this._set(col.key, 'style', style); // element const th = createElement('th', 'column'); const thStyle = { ...style }; if (col.isfixed) { th.classList.add('sticky'); thStyle.left = `${left}px`; } left += col.width; th.dataset.key = col.key; if (col.sortable) { thStyle.cursor = 'pointer'; th.addEventListener('click', e => this._onHeaderClicked(e, col)); } th.style.cssText = convertCssStyle(thStyle); if (col.orderable !== false) { col.orderable = true; th.addEventListener('mousedown', e => this._onDragStart(e, col)); } const wrapper = createElement('div'); if (col.align === 'right') { wrapper.style.justifyContent = 'flex-end'; } else if (col.align === 'center') { wrapper.style.justifyContent = 'center'; } th.appendChild(wrapper); if (!this.readonly && col.enabled !== false && col.allcheck && isCheckbox) { const check = createCheckbox({ onchange: e => this._onColumnAllChecked(col, e.target.checked) }); wrapper.appendChild(check); } let caption; if (typeof type.createCaption === 'function') { caption = type.createCaption(col); } else { caption = createElement('span'); caption.innerText = col.caption ?? ''; } if (caption instanceof HTMLElement) { if (col.captionStyle != null) { caption.style.cssText = convertCssStyle(col.captionStyle); } wrapper.appendChild(caption); } // order arrow if (col.sortable) { th.appendChild(createElement('layer', 'arrow')); } // filter if (col.allowFilter === true) { const filter = createElement('layer', 'filter'); filter.appendChild(createIcon('fa-solid', 'filter')); filter.addEventListener('mousedown', e => this._onFilter(e, col)); th.classList.add('header-filter'); th.appendChild(filter); } // resize spliter if (col.resizable !== false) { const spliter = createElement('layer', 'spliter'); spliter.addEventListener('mousedown', e => this._onResizeStart(e, col)); spliter.addEventListener('dblclick', e => this._onAutoResize(e, col)); th.appendChild(spliter); } // tooltip // !nullOrEmpty(col.tooltip) && setTooltip(th, col.tooltip); header.appendChild(th); } const dragger = createElement('div', 'dragger'); const draggerCursor = createElement('layer', 'dragger-cursor'); header.appendChild( createElement('th', null, dragger, draggerCursor, createElement('div'))); sizer.replaceChildren(); this._var.refs.header = header; this._var.refs.dragger = dragger; this._var.refs.draggerCursor = draggerCursor; return thead; } _createBody(table) { const body = createElement('tbody'); table.appendChild(body); const cols = this.columns; let width = 1; for (let col of cols) { if (col.visible !== false && !isNaN(col.width)) { width += col.width + 1; } } if (width > 0) { table.style.width = `${width}px`; } // body content body.addEventListener('mousedown', e => { let [parent, target] = this._getRowTarget(e.target); const rowIndex = indexOfParent(parent); let colIndex = indexOfParent(target); if (colIndex >= this.columns.length) { colIndex = -1; } this._onRowClicked(e, rowIndex, colIndex); }); body.addEventListener('dblclick', e => this._onRowDblClicked(e)); // this._adjustRows(); this._var.refs.body = body; // this.refresh(); return body; } _adjustRows() { let count = this._var.rowCount; if (isNaN(count) || count < 0 || !this.virtual) { count = this._var.currentSource?.length ?? 0; } const cols = this.columns; const content = this._var.refs.body; const exists = content.children.length; count -= exists; if (count > 0) { for (let i = 0; i < count; ++i) { const row = createElement('tr', 'ui-grid-row'); let left = 0; cols.forEach((col, j) => { const cell = createElement('td'); if (col.visible !== false) { let style = this._get(col.key, 'style') ?? {}; if (col.isfixed) { cell.classList.add('sticky'); style.left = `${left}px`; } left += col.width; cell.dataset.row = String(exists + i); cell.dataset.col = String(j); if (col.css != null) { style = { ...style, ...col.css }; } style = convertCssStyle(style); if (style !== '') { cell.style.cssText = style; } if (Grid.ColumnTypes.isCheckbox(col.type)) { cell.appendChild(GridCheckboxColumn.createEdit(e => this._onRowChanged(e, exists + i, col, e.target.checked, cell))); // this._var.colTypes[col.key] = GridCheckboxColumn; } else { let type = this._var.colTypes[col.key]; if (type == null) { if (isNaN(col.type)) { type = col.type; } else { type = ColumnTypes[col.type]; } type ??= GridColumn; this._var.colTypes[col.key] = type; } cell.appendChild(type.create(col)); } } else { cell.style.display = 'none'; } row.appendChild(cell); }); row.appendChild(createElement('td', td => td.innerText = '\u00a0')); content.appendChild(row); } } else if (count < 0) { for (let i = -1; i >= count; i -= 1) { // content.removeChild(content.children[exists + i]); content.children[exists + i].remove(); } } } _fillRows(rows, cols, widths) { const startIndex = this._var.startIndex; const selectedIndexes = this._var.selectedIndexes; const stateChanged = this._var.oldIndex !== startIndex || selectedIndexes == null || this._var.oldSelectedIndexes?.length !== selectedIndexes.length || this._var.oldSelectedIndexes.find((s, i) => s !== selectedIndexes[i]) != null; if (stateChanged) { this._var.oldIndex = startIndex; if (selectedIndexes != null) { this._var.oldSelectedIndexes = selectedIndexes.slice(); } } [...rows].forEach((row, i) => { const vals = this._var.currentSource[startIndex + i]; if (vals == null) { return; } if (!isPositive(row.children.length)) { return; } const item = vals.values; const selected = selectedIndexes.includes(startIndex + i); if (selected) { row.classList.add('selected'); } else if (row.classList.contains('selected')) { row.classList.remove('selected'); } // data cols.forEach((col, j) => { if (col.visible === false) { return; } const cell = row.children[j]; if (cell == null) { return; } let val; if (col.text != null) { val = col.text; } else if (typeof col.filter === 'function') { val = col.filter(item, selected, this._var.refs.body); } else { val = item[col.key]; if (val?.DisplayValue != null) { val = val.DisplayValue; } } val ??= ''; // fill if (typeof col.bgFilter === 'function') { const bgColor = col.bgFilter(item); cell.style.backgroundColor = bgColor ?? ''; } const isCheckbox = Grid.ColumnTypes.isCheckbox(col.type); const type = isCheckbox ? GridCheckboxColumn : this._var.colTypes[col.key] ?? GridColumn; let element; if (!isCheckbox && typeof type.createEdit === 'function') { if (vals.__editing?.[col.key]) { if (typeof type.leaveEdit === 'function') { type.leaveEdit(cell.children[0], this._var.el); } if (type.editing) { val = type.getValue({ target: cell.children[0] }, col); this._onRowChanged(null, i, col, val, cell, true); } } if (stateChanged) { element = selected ? type.createEdit(e => this._onRowChanged(e, i, col, type.getValue(e, col), cell), col, this._var.el, vals) : type.create(col); cell.replaceChildren(element); } else { element = cell.children[0]; } } else { element = cell.children[0]; } let enabled; if (this.readonly) { enabled = false; } else { enabled = col.enabled; if (typeof enabled === 'function') { enabled = enabled.call(col, item); } else if (typeof enabled === 'string') { enabled = item[enabled]; } } type.setValue(element, val, vals, col, this); let tip = col.tooltip; if (typeof tip === 'function') { tip = tip.call(col, item); } if (nullOrEmpty(tip)) { element.querySelector('.ui-tooltip-wrapper')?.remove(); } else { setTooltip(element, tip, false, this.element); } if (typeof type.setEnabled === 'function') { type.setEnabled(element, enabled); } // auto resize if (this._var.needResize && this._get(col.key, 'autoResize')) { const width = element.scrollWidth + 12; if (width > 0 && widths != null && (isNaN(widths[j]) || widths[j] < width)) { widths[j] = width; widths.flag = true; } } if (typeof col.styleFilter === 'function') { const style = col.styleFilter(item); if (style != null) { type.setStyle(element, style); } else { element.style.cssText = ''; } } if (col.events != null) { for (let ev of Object.entries(col.events)) { element[ev[0]] = ev[1].bind(item); } } if (col.attrs != null) { let attrs = col.attrs; if (typeof attrs === 'function') { attrs = attrs(item); } for (let attr of Object.entries(attrs)) { element.setAttribute(attr[0], attr[1]); } } }); if (vals.__editing != null) { delete vals.__editing; } }); } _changeColumnWidth(index, width) { const col = this.columns[index]; // const oldwidth = col.width; const w = `${width}px`; col.width = width; const style = this._get(col.key, 'style'); style.width = w; style['max-width'] = w; style['min-width'] = w; const headerCells = this._var.refs.header.children; let element = headerCells[index]; element.style.width = w; element.style.maxWidth = w; element.style.minWidth = w; let left = 0; if (col.isfixed) { left = element.offsetLeft + width; let l = left; for (let i = index + 1; i < this.columns.length; ++i) { if (this.columns[i].isfixed) { headerCells[i].style.left = `${l}px`; l += this.columns[i].width; } else { break; } } } for (let row of this.tableRows) { element = row.children[index]; if (element != null) { element.style.width = w; element.style.maxWidth = w; element.style.minWidth = w; if (col.isfixed) { let l = left; for (let i = index + 1; i < this.columns.length; ++i) { if (this.columns[i].isfixed) { row.children[i].style.left = `${l}px`; l += this.columns[i].width; } else { break; } } } } } } _changingColumnOrder(index, offset, mouse, draggerCellLeft) { const children = this._var.refs.header.children; let element = children[index]; this._var.refs.dragger.style.cssText = `left: ${element.offsetLeft - draggerCellLeft + offset}px; width: ${element.style.width}; display: block`; // offset = x + gridScrollLeft - element.offsetLeft; // getOffsetLeftFromWindow(element); offset += mouse; let idx; const toLeft = offset < 0; if (toLeft) { offset = -offset; for (let i = index - 1; i >= 0 && offset >= 0; i -= 1) { element = children[i]; if (element == null || !element.className || element.classList.contains('sticky')) { idx = i + 1; break; } if (offset < element.offsetWidth) { idx = (offset > element.offsetWidth / 2) ? i : i + 1; break; } offset -= element.offsetWidth; } idx ??= 0; } else { const count = children.length; for (let i = index; i < count - 1 && offset >= 0; ++i) { element = children[i]; if (element == null || !element.className || element.classList.contains('sticky')) { idx = i; break; } if (offset < element.offsetWidth) { idx = (offset > element.offsetWidth / 2) ? i + 1 : i; break; } offset -= element.offsetWidth; } idx ??= count - 1; } if (idx !== this._var.colAttrs.__orderIndex || this._var.refs.draggerCursor.style.display !== 'block') { element = children[idx]; if (element == null) { return; } this._var.colAttrs.__orderIndex = idx; // avoid `offsetLeft` of hidden header to be 0 let left; if (element.style.display === 'none') { left = 0; while (left === 0 && (element = children[++idx]) != null) { left = element.offsetLeft; } if (!toLeft && left === 0) { left = draggerCellLeft; } } else { left = element.offsetLeft; } // set position of dragger cursor this._var.refs.draggerCursor.style.cssText = `left: ${left - draggerCellLeft}px; display: block`; } } _changeColumnOrder(index) { this._var.refs.dragger.style.display = ''; this._var.refs.draggerCursor.style.display = ''; const orderIndex = this._var.colAttrs.__orderIndex; if (orderIndex >= 0 && orderIndex !== index) { let targetIndex = orderIndex - index; if (targetIndex >= 0 && targetIndex <= 1) { return; } const header = this._var.refs.header; const children = header.children; const rows = this.tableRows; const columns = this.columns; if (targetIndex > 1) { targetIndex = orderIndex - 1; // const current = columns[index]; // for (let i = index; i < targetIndex; ++i) { // columns[i] = columns[i + 1]; // } // columns[targetIndex] = current; const current = columns.splice(index, 1)[0]; columns.splice(targetIndex, 0, current); header.insertBefore(children[index], children[targetIndex].nextElementSibling); for (let row of rows) { row.insertBefore(row.children[index], row.children[targetIndex].nextElementSibling); } } else { targetIndex = orderIndex; // const current = columns[index]; // for (let i = index; i > targetIndex; i -= 1) { // columns[i] = columns[i - 1]; // } // columns[targetIndex] = current; const current = columns.splice(index, 1)[0]; columns.splice(targetIndex, 0, current); header.insertBefore(children[index], children[targetIndex]); for (let row of rows) { row.insertBefore(row.children[index], row.children[targetIndex]); } } if (this.sortArray == null || this.sortArray.length === 0) { // refresh sortIndex [...children].forEach((th, i) => { const arrow = th.querySelector('.arrow'); if (arrow == null) { return; } if (arrow.className !== 'arrow') { this.sortIndex = i; } }); } if (typeof this.onColumnChanged === 'function') { this.onColumnChanged(ColumnChangedType.Reorder, index, targetIndex); } } } _scrollToTop(top, reload) { const rowHeight = (this.rowHeight + 1); top -= (top % (rowHeight * 2)) + (RedumCount * rowHeight); if (top < 0) { top = 0; } else { let bottomTop = this._var.containerHeight - (reload ? 0 : this._var.rowCount * rowHeight); if (bottomTop < 0) { bottomTop = 0; } if (top > bottomTop) { top = bottomTop; } } if (this._var.scrollTop !== top) { this._var.scrollTop = top; if (this.virtual) { this._var.startIndex = top / rowHeight; } this.refresh(); if (this.virtual) { this._var.refs.table.style.top = `${top}px`; } } else if (reload) { this.refresh(); } return top; } _get(key, name) { const attr = this._var.colAttrs[key]; if (attr == null) { return null; } return attr[name]; } _set(key, name, value) { const attr = this._var.colAttrs[key]; if (attr == null) { this._var.colAttrs[key] = { [name]: value }; } else { attr[name] = value; } } _getItemProp(item, editing, col) { let value; if (typeof col?.filter === 'function') { value = col.filter(item, editing, this._var.refs.body); } else { value = item[col.key]; } if (value == null) { return value; } const prop = editing ? 'Value' : 'DisplayValue'; if (Object.prototype.hasOwnProperty.call(value, prop)) { return value[prop]; } return value; } _getRowTarget(target) { let parent; while ((parent = target.parentElement) != null && !parent.classList.contains('ui-grid-row')) { target = parent; } return [parent, target]; } _notHeader(tagName) { return /^(input|label|layer|svg|use)$/i.test(tagName); } _onHeaderClicked(e, col, force) { if (!force && (this._get(col.key, 'resizing') || this._get(col.key, 'dragging'))) { return; } if (!this._notHeader(e.target.tagName)) { const index = this.columns.indexOf(col); if (index < 0) { return; } if (this.sortIndex === index) { this.sortDirection = this.sortDirection === 1 ? -1 : 1; } else { this.sortIndex = index; } this.sortColumn(); if (typeof this.onColumnChanged === 'function') { this.onColumnChanged(ColumnChangedType.Sort, index, this.sortDirection); } } } _onCloseFilter(e) { if (e != null) { if ((e.target.tagName === 'LAYER' && e.target.classList.contains('filter')) || e.target.tagName === 'use') { return false; } } const panels = this._var.el.querySelectorAll('.filter-panel.active'); if (panels.length > 0) { panels.forEach(el => el.classList.remove('active')); setTimeout(() => this._var.el.querySelectorAll('.filter-panel').forEach(el => el.remove()), 120); const filtering = this._var.colAttrs.__filtering; if (filtering instanceof HTMLElement) { filtering.classList.remove('hover'); } delete this._var.colAttrs.__filtering; document.removeEventListener('mousedown', this._onCloseFilter); return true; } return false; } _onFilter(e, col) { if (this._onCloseFilter()) { return; } document.addEventListener('mousedown', this._onCloseFilter.bind(this)); const panel = createElement('div', 'filter-panel'); panel.addEventListener('mousedown', e => e.stopPropagation()); const filter = e.currentTarget; const th = filter.parentElement; const width = th.offsetWidth; panel.style.top = `${th.offsetHeight + this._var.el.scrollTop}px`; const offsetLeft = th.offsetLeft; const totalWidth = th.parentElement.offsetWidth; const left = offsetLeft + FilterPanelWidth > totalWidth ? totalWidth - FilterPanelWidth : offsetLeft + (width > FilterPanelWidth ? width - FilterPanelWidth : 0); panel.style.left = `${left}px`; // search let searchbox; if (col.allowSearch !== false) { const searchholder = createElement('div', 'filter-search-holder'); searchbox = createElement('input', 'filter-search-box ui-text'); searchbox.type = 'text'; const searchicon = createIcon('fa-regular', 'search'); searchicon.addEventListener('mousedown', e => { searchbox.focus(); e.preventDefault(); }); searchholder.append(searchbox, searchicon); panel.append(searchholder); } // list const itemlist = createElement('div', 'filter-item-list'); itemlist.addEventListener('scroll', e => throttle(this._onFilterScroll, RefreshInterval, this, col, itemlist, e.target.scrollTop), { passive: true }); // - all const itemall = createElement('div', 'filter-item filter-all'); itemall.appendChild(createCheckbox({ label: this.langs.all, onchange: e => { const checked = e.target.checked; itemlist.querySelectorAll('.filter-content input').forEach(box => box.checked = checked); for (let it of this._get(col.key, 'filterSource')) { it.__checked = checked; } } })); itemlist.appendChild(itemall); // - items let array; if (Array.isArray(col.filterSource)) { array = col.filterSource; } else if (typeof col.filterSource === 'function') { array = col.filterSource.call(this, col); } else { const dict = Object.create(null); for (let item of this._var.source) { let displayValue = this._getItemProp(item.values, false, col); if (displayValue == null) { displayValue = this.langs.null; } if (!Object.hasOwnProperty.call(dict, displayValue)) { const val = this._getItemProp(item.values, true, col); dict[displayValue] = { Value: val, DisplayValue: displayValue }; } } array = Object.values(dict) .sort((a, b) => { if (a == null && b == null) { return 0; } if (a == null && b != null) { return -1; } if (a != null && b == null) { return 1; } if (Object.prototype.hasOwnProperty.call(a, 'Value')) { a = a.Value; } if (Object.prototype.hasOwnProperty.call(b, 'Value')) { b = b.Value; } return a > b ? 1 : a < b ? -1 : 0; }); } array = array.map(i => { if (Object.prototype.hasOwnProperty.call(i, 'Value') && Object.prototype.hasOwnProperty.call(i, 'DisplayValue')) { return i; } return { Value: i, DisplayValue: i == null ? this.langs.null : i }; }); this._fillFilterList(col, itemlist, array, itemall); itemall.querySelector('input').checked = ![...itemlist.querySelectorAll('.filter-content input')].some(i => !i.checked); panel.appendChild(itemlist); if (searchbox != null) { searchbox.addEventListener('input', e => { const key = e.currentTarget.value.toLowerCase(); const items = key.length === 0 ? array : array.filter(i => { let displayValue; if (i != null && Object.prototype.hasOwnProperty.call(i, 'DisplayValue')) { displayValue = i.DisplayValue; } else { displayValue = i; } if (displayValue == null) { displayValue = this.langs.null; } return String(displayValue).toLowerCase().includes(key); }); this._fillFilterList(col, itemlist, items, itemall); }); } // function const functions = createElement('div', 'filter-function'); functions.append( createElement('button', ok => { ok.innerText = this.langs.ok; ok.addEventListener('click', () => { const array = this._get(col.key, 'filterSource').filter(i => i.__checked !== false); if (typeof col.onFilterOk === 'function') { col.onFilterOk.call(this, col, array); } else { col.filterValues = array.map(a => a.DisplayValue); } this._var.colAttrs.__filtered = true; this._refreshSource(); if (typeof col.onFiltered === 'function') { col.onFiltered.call(this, col); } filter.classList.add('active'); this._onCloseFilter(); }); }), createElement('button', reset => { reset.innerText = this.langs.reset; reset.addEventListener('click', () => { delete col.filterValues; this._var.colAttrs.__filtered = this.columns.some(c => c.filterValues != null) this._refreshSource(); if (typeof col.onFiltered === 'function') { col.onFiltered.call(this, col); } filter.classList.remove('active'); this._onCloseFilter(); }); }) ); panel.appendChild(functions); this._var.el.appendChild(panel); setTimeout(() => panel.classList.add('active'), 0); this._var.colAttrs.__filtering = filter; filter.classList.add('hover'); } _fillFilterList(col, list, array, all) { list.querySelector('.filter-holder')?.remove(); list.querySelector('.filter-content')?.remove(); const rowHeight = this.filterRowHeight; const height = array.length * rowHeight; this._set(col.key, 'filterHeight', height); const holder = createElement('div', 'filter-holder'); holder.style.height = `${height}px`; const content = createElement('div', 'filter-content'); content.style.top = `${rowHeight}px`; this._set(col.key, 'filterSource', array); for (let item of array) { const v = Object.prototype.hasOwnProperty.call(item, 'DisplayValue') ? item.DisplayValue : item; item.__checked = !Array.isArray(col.filterValues) || col.filterValues.includes(v); } if (array.length > 12) { array = array.slice(0, 12); } this._doFillFilterList(content, array, all); list.append(holder, content); } _doFillFilterList(content, array, all) { for (let item of array) { const div = createElement('div', 'filter-item'); div.appendChild(createCheckbox({ checked: item.__checked, label: Object.prototype.hasOwnProperty.call(item, 'DisplayValue') ? item.DisplayValue : item, onchange: e => { item.__checked = e.target.checked; all.querySelector('input').checked = ![...content.querySelectorAll('input')].some(i => !i.checked); } })); content.appendChild(div); } } _onFilterScroll(col, list, top) { const rowHeight = this.filterRowHeight; top -= (top % (rowHeight * 2)) + rowHeight; if (top < 0) { top = 0; } else { let bottomTop = this._get(col.key, 'filterHeight') - (12 * rowHeight); if (bottomTop < 0) { bottomTop = 0; } if (top > bottomTop) { top = bottomTop; } } if (this._get(col.key, 'filterTop') !== top) { this._set(col.key, 'filterTop', top); const startIndex = top / rowHeight; let array = this._get(col.key, 'filterSource'); if (startIndex + 12 < array.length) { array = array.slice(startIndex, startIndex + 12); } else { array = array.slice(-12); } const content = list.querySelector('.filter-content'); content.replaceChildren(); this._doFillFilterList(content, array, list.querySelector('.filter-all')); content.style.top = `${top + rowHeight}px`; } } _onDragStart(e, col) { if (this._notHeader(e.target.tagName)) { return; } if (e.currentTarget.classList.contains('sticky')) { return; } const index = indexOfParent(e.currentTarget); const cx = getClientX(e); const clearEvents = attr => { for (let event of ['mousemove', 'mouseup']) { if (Object.prototype.hasOwnProperty.call(attr, event)) { window.removeEventListener(event, attr[event]); delete attr[event]; } } }; let attr = this._var.colAttrs[col.key]; if (attr == null) { attr = this._var.colAttrs[col.key] = {}; } else { clearEvents(attr); } attr.dragging = true; const draggerCellLeft = this._var.refs.header.querySelector('th:last-child').offsetLeft; // const gridScrollLeft = this._var.el.scrollLeft; let p = this._var.el; let gridLeftFromWindow = p.offsetLeft; while ((p = p.offsetParent) != null) { gridLeftFromWindow += p.offsetLeft + p.clientLeft; } const mouse = cx - e.currentTarget.offsetLeft + this._var.el.scrollLeft - gridLeftFromWindow; const dragmove = e => { const cx2 = getClientX(e); const offset = cx2 - cx; let pos = attr.offset; let dragging; if (pos == null) { if (offset > MiniDragOffset || offset < -MiniDragOffset) { dragging = true; } } else if (pos !== offset) { dragging = true; } if (dragging) { this._changingColumnOrder(index, offset, mouse, draggerCellLeft); attr.offset = offset; } }; attr.mousemove = e => throttle(dragmove, RefreshInterval, this, e); attr.mouseup = () => { clearEvents(attr); if (attr.offset == null) { delete attr.dragging; } else { setTimeout(() => { delete attr.dragging; delete attr.offset; }); this._changeColumnOrder(index); } }; ['mousemove', 'mouseup'].forEach(event => window.addEventListener(event, attr[event])); } _onResizeStart(e, col) { const cx = getClientX(e); const width = col.width; const index = indexOfParent(e.currentTarget.parentElement); const window = this.window ?? global; const clearEvents = attr => { for (let event of ['mousemove', 'mouseup']) { if (Object.prototype.hasOwnProperty.call(attr, event)) { window.removeEventListener(event, attr[event]); delete attr[event]; } } }; let attr = this._var.colAttrs[col.key]; if (attr == null) { attr = this._var.colAttrs[col.key] = {}; } else { clearEvents(attr); } attr.resizing = width; const resizemove = e => { const cx2 = getClientX(e); const val = width + (cx2 - cx); if (val < MiniColumnWidth) { return; } attr.resizing = val; attr.sizing = true; this._changeColumnWidth(index, val); }; attr.mousemove = e => throttle(resizemove, RefreshInterval, this, e); attr.mouseup = e => { clearEvents(attr); const width = attr.resizing; if (width != null) { setTimeout(() => delete attr.resizing); if (attr.sizing) { delete attr.sizing; delete attr.autoResize; this._changeColumnWidth(index, width); if (typeof this.onColumnChanged === 'function') { this.onColumnChanged(ColumnChangedType.Resize, index, width); } } } e.stopPropagation(); e.preventDefault(); }; ['mousemove', 'mouseup'].forEach(event => window.addEventListener(event, attr[event])); } _onAutoResize(e, col) { const th = e.currentTarget.parentElement; const index = indexOfParent(th); let width = th.querySelector('div:first-child').scrollWidth; for (let row of this.tableRows) { const element = row.children[index].children[0]; const w = element.scrollWidth; if (w > width) { width = w; } } if (width < MiniColumnWidth) { width = MiniColumnWidth; } if (width > 0 && width !== col.width) { width += 12; this._changeColumnWidth(index, width); if (typeof this.onColumnChanged === 'function') { this.onColumnChanged(ColumnChangedType.Resize, index, width); } } } _onColumnAllChecked(col, flag) { if (this._var.currentSource == null) { return; } const key = col.key; const isFunction = typeof col.enabled === 'function'; const isString = typeof col.enabled === 'string'; if (typeof col.onAllChecked === 'function') { col.onAllChecked.call(this, col, flag); } else { for (let row of this._var.currentSource) { const item = row.values; if (item == null) { continue; } const enabled = isFunction ? col.enabled(item) : isString ? item[col.enabled] : col.enabled; if (enabled !== false) { item[key] = flag; row.__changed = true; if (typeof col.onChanged === 'function') { col.onChanged.call(this, item, flag); } } } this.refresh(); } } _onScroll(e) { if (this._var.colAttrs.__filtering != null) { this._onCloseFilter(); } if (this.onBodyScrolled === 'function') { this.onBodyScrolled(e); } if (!this.virtual) { return; } const top = e.target.scrollTop; this._scrollToTop(top); } _onBodyMouseMove(e, holder) { if (e.target.classList.contains('ui-grid-hover-holder')) { return; } let [parent, target] = this._getRowTarget(e.target); if (parent == null) { delete holder.dataset.row; delete holder.dataset.col; if (holder.classList.contains('active')) { holder.classList.remove('active'); } return; } const element = target.children[0]; if (element?.tagName !== 'SPAN') { if (holder.classList.contains('active')) { delete holder.dataset.row; delete holder.dataset.col; holder.classList.remove('active'); } return; } const row = target.dataset.row; const col = target.dataset.col; if (holder.dataset.row === row && holder.dataset.col === col) { return; } if (element.scrollWidth > element.offsetWidth) { holder.dataset.row = row; holder.dataset.col = col; holder.innerText = element.innerText; const top = target.offsetTop + this._var.refs.table.offsetTop; let left = target.offsetLeft; let width = holder.offsetWidth; if (width > this._var.bodyClientWidth) { width = this._var.bodyClientWidth; } const maxleft = this._var.bodyClientWidth + this._var.scrollLeft - width; if (left > maxleft) { left = maxleft; } const height = target.offsetHeight; holder.style.cssText = `top: ${top}px; left: ${left}px; max-width: ${this._var.bodyClientWidth}px; height: ${height - 2}px`; holder.classList.add('active'); } else if (holder.classList.contains('active')) { delete holder.dataset.row; delete holder.dataset.col; holder.classList.remove('active'); } } _onRowClicked(e, index, colIndex) { const startIndex = this._var.startIndex; const selectedIndex = startIndex + index; if (typeof this.willSelect === 'function' && !this.willSelect(selectedIndex, colIndex)) { return; } // multi-select let flag = false; const selectedIndexes = this._var.selectedIndexes; if (this.multiSelect) { if (e.ctrlKey) { const i = selectedIndexes.indexOf(selectedIndex); if (i < 0) { selectedIndexes.push(selectedIndex); } else { selectedIndexes.splice(i, 1); } flag = true; } else if (e.shiftKey && selectedIndexes.length > 0) { if (selectedIndexes.length > 1 || selectedIndexes[0] !== selectedIndex) { let start = selectedIndexes[selectedIndexes.length - 1]; let end; if (start > selectedIndex) { end = start; start = selectedIndex; } else { end = selectedIndex; } selectedIndexes.splice(0); for (let i = start; i <= end; ++i) { selectedIndexes.push(i); } flag = true; } } } if (!flag && (selectedIndexes.length !== 1 || selectedIndexes[0] !== selectedIndex)) { selectedIndexes.splice(0, selectedIndexes.length, selectedIndex); flag = true; } // apply style if (flag) { if (this.readonly !== true) { this.refresh(); } else { this.tableRows.forEach((row, i) => { if (selectedIndexes.includes(startIndex + i)) { row.classList.add('selected'); } else if (row.classList.contains('selected')) { row.classList.remove('selected'); } }); } if (typeof this.onSelectedRowChanged === 'function') { this.onSelectedRowChanged(selectedIndex); } } this._var.selectedColumnIndex = colIndex; if ((this.fullrowClick || colIndex >= 0) && e.buttons === 1 && typeof this.cellClicked === 'function') { if (this.cellClicked(selectedIndex, colIndex) === false) { e.stopPropagation(); e.preventDefault(); } } } _onRowDblClicked(e) { if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.tagName === 'LAYER' && e.target.className === 'ui-check-inner' || e.target.tagName === 'LABEL' && (e.target.className === 'ui-drop-text' || e.target.className === 'ui-drop-caret')) { return; } const index = this.selectedIndex; if (typeof this.onRowDblClicked === 'function') { this.onRowDblClicked(index); } if (typeof this.onCellDblClicked === 'function') { const colIndex = this._var.selectedColumnIndex; if (this.fullrowClick || colIndex >= 0) { this.onCellDblClicked(index, colIndex); } } } _onRowChanged(e, index, col, value, cell, blur) { if (this._var.currentSource == null) { return; } const row = this._var.currentSource[this._var.startIndex + index]; delete row.source; const item = row.values; if (item == null) { return; } let enabled = col.enabled; if (typeof enabled === 'function') { enabled = enabled.call(col, item); } else if (typeof enabled === 'string') { enabled = item[enabled]; } if (enabled !== false) { const val = item[col.key]; let oldValue; if (val != null && Object.prototype.hasOwnProperty.call(val, 'Value')) { oldValue = val.Value; val.Value = value; } else { oldValue = val; item[col.key] = value; } let tip = col.tooltip; if (typeof tip === 'function') { tip = tip.call(col, item); } if (nullOrEmpty(tip)) { cell.querySelector('.ui-tooltip-wrapper')?.remove(); } else { setTooltip(cell.children[0], tip, false, this.element); } row.__changed = true; if (blur) { if (typeof col.onInputEnded === 'function') { col.onInputEnded.call(this, item, value); } } else { if (typeof col.onChanged === 'function') { col.onChanged.call(this, item, value, oldValue, e); } } } } }