diff --git a/lib/ui/grid/grid.js b/lib/ui/grid/grid.js index 2b2a2e2..4d555c5 100644 --- a/lib/ui/grid/grid.js +++ b/lib/ui/grid/grid.js @@ -10,6 +10,12 @@ import { Popup, showAlert } from "../popup"; import { convertCssStyle } from "../extension"; import { GridColumn, GridInputColumn, GridTextColumn, GridDropdownColumn, GridCheckboxColumn, GridIconColumn, GridDateColumn } from "./column"; +/** + * @author Tsanie Lily + * @license MIT + * @version 1.0.1 + */ + const ColumnChangedType = { Reorder: 'reorder', Resize: 'resize', @@ -61,9 +67,7 @@ let r = lang; /** * 键值字典 * @template T - * @typedef KeyMap - * @type1 {{[key: string]: T}} - * @type {Map} + * @typedef {Map} KeyMap */ /** @@ -570,6 +574,12 @@ export class Grid { * @private */ currentSource: null, + /** + * 合计行数据 + * @type {GridRowItem} + * @private + */ + total: null, /** * Grid 是否只读 * @type {boolean} @@ -771,12 +781,6 @@ export class Grid { * @ignore */ columns = []; - /** - * 合计行数据 - * @type {GridRowItem} - * @ignore - */ - total = null; /** * 多语言资源对象 * @type {GridLanguages} @@ -1010,7 +1014,6 @@ export class Grid { * @param {string} [getText.def] - 默认资源 * @param {string} getText.{returns} 返回的多语言 * @property {GridColumnDefinition[]} columns - 列定义的数组 - * @property {GridRowItem} [total] - 合计行数据 * @property {GridLanguages} [langs] - 多语言资源对象 * @property {number} [virtualCount=100] - 行数大于等于该值则启用虚模式 * @property {boolean} [autoResize=true] - 未设置宽度的列自动调整列宽 @@ -1065,263 +1068,14 @@ export class Grid { */ get allSource() { return this._var.source?.map(s => s.values) } - /** - * 获取或设置 Grid 是否为只读 - * @type {boolean} - */ - get readonly() { return this._var.readonly === true } - set readonly(flag) { - this._var.readonly = flag; - this.refresh(); - } - - /** - * 获取已过滤的数据数组,或者设置数据并刷新列表 - * @type {GridRowItem[]} - */ - get source() { return this._var.currentSource?.map(s => s.values) } - set source(list) { - 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); - } - /** * 获取已过滤的数据中的扩展对象数组 + * @readonly * @type {GridExpandableObject[]} * @property {HTMLElement} element - 扩展行元素 */ get sourceExpandable() { return this._var.currentSource?.map(s => s.__expandable_object) } - /** - * 设置单行数据 - * @param {number} index - 行索引 - * @param {GridRowItem} item - 待设置的行数据对象 - */ - 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; - if (this.sortIndex >= 0) { - this.sortColumn(); - } else if (this.sortArray?.length > 0) { - this.sort(); - } else { - this.refresh(); - } - } - - /** - * 添加行数据 - * @param {GridRowItem} item - 待添加的行数据值 - * @param {number} [index] - 待添加的行索引 - * @returns {GridRowItem} 返回已添加的行数据 - */ - 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); - } - } - if (this.sortIndex >= 0) { - this.sortColumn(true); - } else if (this.sortArray?.length > 0) { - this.sort(true); - } else { - this.reload(); - } - return item; - } - - /** - * 批量添加行数据 - * @param {GridRowItem[]} array - 待添加的行数据数组 - * @param {number} [index] - 待添加的行索引 - * @returns {GridRowItem[]} 返回已添加的行数据数组 - */ - 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); - } - } - if (this.sortIndex >= 0) { - this.sortColumn(true); - } else if (this.sortArray?.length > 0) { - this.sort(true); - } else { - this.reload(); - } - return array; - } - - /** - * 删除行数据 - * @param {number} index - 待删除的行索引 - * @returns {GridRowItem} 返回已删除的行数据 - */ - 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; - } - - /** - * 批量删除行数据 - * @param {number[]} [indexes] - 待删除的行索引数组,未传值时删除所有行 - * @returns {GridRowItem[]} 返回已删除的行数据数组 - */ - 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; - } - - /** - * @private - * @param {GridItemWrapper[]} list - */ - _refreshSource(list) { - list ??= this._var.source; - if (this._var.colAttrs.__filtered === true) { - this._var.currentSource = list.filter(it => { - for (let col of this.columns) { - const nullValue = col.filterAllowNull ? null : ''; - if (Array.isArray(col.filterValues)) { - const v = this._getItemProp(it.values, false, col) ?? nullValue; - 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; - - this.resize(true, false, () => { - if (this.sortIndex >= 0) { - this.sortColumn(true); - } else if (this.sortArray?.length > 0) { - this.sort(true); - } else { - this.reload(); - } - }); - } - /** * 获取当前是否为虚模式状态 * @readonly @@ -1373,6 +1127,53 @@ export class Grid { */ get startIndex() { return this._var.startIndex } + /** + * 获取当前选中行的索引,为 -1 则当前没有选中行 + * @readonly + * @type {number} + */ + get selectedIndex() { return (this._var.selectedIndexes && this._var.selectedIndexes[0]) ?? -1 } + + /** + * 获取或设置 Grid 是否为只读 + * @type {boolean} + */ + get readonly() { return this._var.readonly === true } + set readonly(flag) { + this._var.readonly = flag; + this.refresh(); + } + + /** + * 获取已过滤的数据数组,或者设置数据并刷新列表 + * @type {GridRowItem[]} + */ + get source() { return this._var.currentSource?.map(s => s.values) } + set source(list) { + 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); + } + + /** + * 获取或设置合计行数据 + * @type {GridRowItem} + * @since 1.0.1 + */ + get total() { return this._var.total } + set total(total) { + this._var.total = total; + this.reload(true); + } + /** * 获取或设置当前选中的行索引的数组,设置后会刷新列表 * @type {number[]} @@ -1397,13 +1198,6 @@ export class Grid { } } - /** - * 获取当前选中行的索引,为 -1 则当前没有选中行 - * @readonly - * @type {number} - */ - get selectedIndex() { return (this._var.selectedIndexes && this._var.selectedIndexes[0]) ?? -1 } - /** * 获取或设置 Grid 当前的加载状态 * @type {boolean} @@ -1645,6 +1439,9 @@ export class Grid { * @param {boolean} [keep] - 是否保持当前滚动位置 */ reload(keep) { + if (this._var.rendering || this._var.el == null) { + return; + } const filtered = this.columns.some(c => c.filterValues != null); if ((filtered ^ this._var.colAttrs.__filtered) === 1) { this._var.colAttrs.__filtered = filtered; @@ -1746,44 +1543,6 @@ export class Grid { } } - /** - * @private - * @callback PrivateGridComparerCallback - * @param {GridItemWrapper} a - * @param {GridItemWrapper} b - * @returns {number} - */ - - /** - * @private - * @param {GridColumnDefinition} col - * @param {GridColumnDirection} direction - * @returns {PrivateGridComparerCallback} - */ - _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; - } - /** * 根据当前排序字段进行列排序 * @param {boolean} [reload] - 为 `true` 则在列排序后调用 [reload]{@linkcode Grid#reload} 方法 @@ -1883,6 +1642,7 @@ export class Grid { /** * 显示多列排序设置面板 + * @since 1.0.1 */ showSortPanel() { const content = createElement('div', 'ui-sort-panel-content'); @@ -2083,6 +1843,270 @@ export class Grid { }); } + /** + * 设置单行数据 + * @param {number} index - 行索引 + * @param {GridRowItem} item - 待设置的行数据对象 + * @since 1.0.1 + */ + 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; + if (this.sortIndex >= 0) { + this.sortColumn(); + } else if (this.sortArray?.length > 0) { + this.sort(); + } else { + this.refresh(); + } + } + + /** + * 添加行数据 + * @param {GridRowItem} item - 待添加的行数据值 + * @param {number} [index] - 待添加的行索引 + * @returns {GridRowItem} 返回已添加的行数据 + * @since 1.0.1 + */ + 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); + } + } + if (this.sortIndex >= 0) { + this.sortColumn(true); + } else if (this.sortArray?.length > 0) { + this.sort(true); + } else { + this.reload(); + } + return item; + } + + /** + * 批量添加行数据 + * @param {GridRowItem[]} array - 待添加的行数据数组 + * @param {number} [index] - 待添加的行索引 + * @returns {GridRowItem[]} 返回已添加的行数据数组 + * @since 1.0.1 + */ + 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); + } + } + if (this.sortIndex >= 0) { + this.sortColumn(true); + } else if (this.sortArray?.length > 0) { + this.sort(true); + } else { + this.reload(); + } + return array; + } + + /** + * 删除行数据 + * @param {number} index - 待删除的行索引 + * @returns {GridRowItem} 返回已删除的行数据 + * @since 1.0.1 + */ + 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; + } + + /** + * 批量删除行数据 + * @param {number[]} [indexes] - 待删除的行索引数组,未传值时删除所有行 + * @returns {GridRowItem[]} 返回已删除的行数据数组 + * @since 1.0.1 + */ + 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; + } + + /** + * @private + * @callback PrivateGridComparerCallback + * @param {GridItemWrapper} a + * @param {GridItemWrapper} b + * @returns {number} + */ + + /** + * @private + * @param {GridColumnDefinition} col + * @param {GridColumnDirection} direction + * @returns {PrivateGridComparerCallback} + */ + _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; + } + + /** + * @private + * @param {GridItemWrapper[]} list + */ + _refreshSource(list) { + list ??= this._var.source; + if (this._var.colAttrs.__filtered === true) { + this._var.currentSource = list.filter(it => { + for (let col of this.columns) { + const nullValue = col.filterAllowNull ? null : ''; + if (Array.isArray(col.filterValues)) { + const v = this._getItemProp(it.values, false, col) ?? nullValue; + 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; + + this.resize(true, false, () => { + if (this.sortIndex >= 0) { + this.sortColumn(true); + } else if (this.sortArray?.length > 0) { + this.sort(true); + } else { + this.reload(); + } + }); + } + /** * @private * @param {HTMLTableElement} table diff --git a/package-lock.json b/package-lock.json index a6e51fe..5febb65 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2016,9 +2016,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.675", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.675.tgz", - "integrity": "sha512-+1u3F/XPNIdUwv8i1lDxHAxCvNNU0QIqgb1Ycn+Jnng8ITzWSvUqixRSM7NOazJuwhf65IV17f/VbKj8DmL26A==", + "version": "1.4.677", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.677.tgz", + "integrity": "sha512-erDa3CaDzwJOpyvfKhOiJjBVNnMM0qxHq47RheVVwsSQrgBA9ZSGV9kdaOfZDPXcHzhG7lBxhj6A7KvfLJBd6Q==", "dev": true }, "node_modules/entities": { @@ -3297,9 +3297,9 @@ } }, "node_modules/sass": { - "version": "1.71.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.71.0.tgz", - "integrity": "sha512-HKKIKf49Vkxlrav3F/w6qRuPcmImGVbIXJ2I3Kg0VMA+3Bav+8yE9G5XmP5lMj6nl4OlqbPftGAscNaNu28b8w==", + "version": "1.71.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.71.1.tgz", + "integrity": "sha512-wovtnV2PxzteLlfNzbgm1tFXPLoZILYAMJtvoXXkD7/+1uP41eKkIt1ypWq5/q2uT94qHjXehEYfmjKOvjL9sg==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0",