From 984496e08e97653f26e30d77e3332065731e54e8 Mon Sep 17 00:00:00 2001 From: Tsanie Date: Fri, 26 Jan 2024 14:09:52 +0800 Subject: [PATCH] fixed: column drag issue. optimized: documentation. --- lib/ui/dropdown.d.ts | 16 +++- lib/ui/grid/column.d.ts | 173 ++++++++++++++++++++++++++++++++++++---- lib/ui/grid/column.js | 4 +- lib/ui/grid/grid.d.ts | 25 +++++- lib/ui/grid/grid.js | 173 +++++++++++++++++++++++++++------------- 5 files changed, 313 insertions(+), 78 deletions(-) diff --git a/lib/ui/dropdown.d.ts b/lib/ui/dropdown.d.ts index ef22018..5e93352 100644 --- a/lib/ui/dropdown.d.ts +++ b/lib/ui/dropdown.d.ts @@ -32,7 +32,7 @@ export interface DropdownOptions { search?: boolean; /** 搜索的关键字数组 */ searchKeys?: Array; - /** 搜索提示文本,默认值取语言资源 searchHolder "Search..." */ + /** 搜索提示文本,默认值取语言资源 `searchHolder` "Search..." */ searchPlaceholder?: string; /** 焦点索引 */ tabIndex?: Number; @@ -47,7 +47,7 @@ export interface DropdownOptions { /** 下拉框类 */ export class Dropdown { /** - * 把父元素下的所有 select 元素修改为统一下拉框组件 + * 把父元素下的所有 `select` 元素修改为统一下拉框组件 * @param dom 父元素 * @returns 返回该父元素 */ @@ -64,16 +64,24 @@ export class Dropdown { /** * 选中时触发 * @param item 选中的条目 + * @eventProperty */ onSelected: (item: DropdownItem) => void; /** * 选中多个时触发 * @param list 选中的条目数组 + * @eventProperty */ onSelectedList: (list: Array) => void; - /** 下拉框展开时触发 */ + /** + * 下拉框展开时触发 + * @eventProperty + */ onExpanded: () => void; - /** 下拉框收缩时触发 */ + /** + * 下拉框收缩时触发 + * @eventProperty + */ onCollapsed: () => void; /** 获取下拉框是否禁用 */ diff --git a/lib/ui/grid/column.d.ts b/lib/ui/grid/column.d.ts index a125b7a..6da6c40 100644 --- a/lib/ui/grid/column.d.ts +++ b/lib/ui/grid/column.d.ts @@ -35,13 +35,18 @@ export interface GridColumnDefinition { align?: "left" | "center" | "right"; /** * 列是否可用(可编辑),允许以下类型

- * boolean 则直接使用该值

- * string 则以该值为关键字从行数据中取值作为判断条件

- * (item: GridItem) => boolean 则调用该函数(上下文为列定义对象),以返回值作为判断条件

+ * `boolean` 则直接使用该值

+ * `string` 则以该值为关键字从行数据中取值作为判断条件

+ * `(item: GridItem) => boolean` 则调用该函数(上下文为列定义对象),以返回值作为判断条件

*/ enabled?: boolean | string | ((item: GridItem) => boolean); - /** 单元格取值采用该方法返回的值 */ - filter?: (item: GridItem) => any; + /** + * 单元格取值采用该方法返回的值 + * @param item 行数据对象 + * @param editing 是否处于编辑状态 + * @param body Grid 控件的 `<tbody>` 部分 + */ + filter?: (item: GridItem, editing: boolean, body?: HTMLElement) => any; /** 单元格以该值填充内容,忽略filter与关键字属性 */ text?: string; /** 列是否可见 */ @@ -76,8 +81,8 @@ export interface GridColumnDefinition { source?: Array | ((item: GridItem) => Array | Promise>); /** 下拉列表数据源是否缓存结果(即行数据未发生变化时仅从source属性获取一次值) */ sourceCache?: boolean; - /** 列为图标类型时以该值设置图标样式(函数上下文为列定义对象),允许值为 fa-lightfa-regularfa-solid */ - iconType?: string; + /** 列为图标类型时以该值设置图标样式(函数上下文为列定义对象),默认值 `fa-light` */ + iconType?: "fa-light" | "fa-regular" | "fa-solid"; /** 列为图标类型时以该值作为单元格元素的额外样式类型(函数上下文为列定义对象) */ iconClassName?: string | ((item: GridItem) => string); /** 列为日期类型时以该值作为最小可选日期值 */ @@ -92,6 +97,7 @@ export interface GridColumnDefinition { * @param this 上下文为 Grid 对象 * @param col 列定义对象 * @param flag 是否选中 + * @eventProperty */ onAllChecked?: (this: Grid, col: GridColumnDefinition, flag: boolean) => void; /** @@ -101,6 +107,7 @@ export interface GridColumnDefinition { * @param value 修改后的值 * @param oldValue 修改前的值 * @param e 列修改事件传递过来的任意对象 + * @eventProperty */ onChanged?: (this: Grid, item: GridItem, value: boolean | string | Number, oldValue: boolean | string | Number, e?: any) => void; /** @@ -108,6 +115,7 @@ export interface GridColumnDefinition { * @param this 上下文为 Grid 对象 * @param item 数据行对象 * @param value 修改后的文本框值 + * @eventProperty */ onInputEnded?: (this: Grid, item: GridItem, value: string) => void; /** @@ -115,12 +123,14 @@ export interface GridColumnDefinition { * @param this 上下文为 Grid 对象 * @param col 列定义对象 * @param selected 选中的过滤项 + * @eventProperty */ onFilterOk?: (this: Grid, col: GridColumnDefinition, selected: Array) => void; /** * 列过滤后触发的事件 * @param this 上下文为 Grid 对象 * @param col 列定义对象 + * @eventProperty */ onFiltered?: (this: Grid, col: GridColumnDefinition) => void; /** @@ -128,6 +138,7 @@ export interface GridColumnDefinition { * @param this 上下文为列定义对象 * @param item 数据行对象 * @param drop 下拉框对象 + * @eventProperty */ onDropExpanded?: (this: GridColumnDefinition, item: GridItem, drop: Dropdown) => void; } @@ -138,19 +149,28 @@ export class GridColumn { * 创建显示单元格时调用的方法 * @param col 列定义对象 * @returns 返回创建的单元格元素 + * @virtual */ static create(col: GridColumnDefinition): HTMLElement; /** * 创建编辑单元格时调用的方法

- * 元素修改后设置行包装对象的 __editing 后,支持在离开编辑状态时及时触发 {@linkcode leaveEdit} 方法
+ * 元素修改后设置行包装对象的 `__editing` 后,支持在离开编辑状态时及时触发 {@linkcode leaveEdit} 方法
* 更多例子参考代码中 {@linkcode GridDropdownColumn} 的实现。 * @param trigger 编辑事件回调函数,e 参数会传递给 {@linkcode getValue} 方法 * @param col 列定义对象 * @param container 父容器元素 - * @param vals 行包装对象,其 values 属性为行数据对象 + * @param vals 行包装对象,其 `values` 属性为行数据对象 * @returns 返回创建的编辑状态的单元格元素 + * @virtual */ static createEdit(trigger: (e: any) => void, col: GridColumnDefinition, container: HTMLElement, vals: GridItemWrapper): HTMLElement; + /** + * 创建列头时调用的方法 + * @param col 列定义对象 + * @returns 返回创建的列头元素 + * @virtual + */ + static createCaption?(col: GridColumnDefinition): HTMLElement; /** * 设置单元格值时调用的方法 * @param element 单元格元素 @@ -158,59 +178,178 @@ export class GridColumn { * @param vals 行包装对象 * @param col 列定义对象 * @param grid {@linkcode Grid} 对象 + * @virtual */ static setValue(element: HTMLElement, val: string | boolean | Number, vals: GridItemWrapper, col: GridColumnDefinition, grid: Grid): void; /** * 获取编辑状态单元格值时调用的方法 - * @param e 由 {@linkcode createEdit} 方法中 trigger 函数传递来的对象 + * @param e 由 {@linkcode createEdit} 方法中 `trigger` 函数传递来的对象 * @param col 列定义对象 * @returns 返回单元格的值 + * @virtual */ static getValue(e: any, col: GridColumnDefinition): string | boolean | Number; /** * 设置单元格样式时调用的方法 * @param element 单元格元素 * @param style 样式对象 + * @virtual */ static setStyle(element: HTMLElement, style: { [key: string]: string }): void; /** * 设置单元格可用性时调用的方法 * @param element 单元格元素 * @param enabled 启用值,为false时代表禁用 + * @virtual */ static setEnabled(element: HTMLElement, enabled?: boolean): void; /** - * 单元格离开编辑元素时触发,需要由行包装对象的 __editing 来确定是否触发。 + * 单元格离开编辑元素时触发,需要由行包装对象的 `__editing` 来确定是否触发。 * @param element 单元格元素 * @param container 父容器元素 + * @virtual */ - static leaveEdit(element: HTMLElement, container: HTMLElement): void; + static leaveEdit?(element: HTMLElement, container: HTMLElement): void; } /** 单行文本列 */ export class GridInputColumn extends GridColumn { /** * 设置该类型是否支持触发 {@linkcode GridColumnDefinition.onInputEnded} 方法
- * 该属性返回 true 后,在任意事件中修改行包装对象的 __editing 值,则会在行列元素变动时及时触发 {@linkcode GridColumnDefinition.onInputEnded} 方法,避免例如文本框还未触发 onchange 事件就被移除元素而导致的问题
+ * 该属性返回 `true` 后,在任意事件中修改行包装对象的 `__editing` 值,则会在行列元素变动时及时触发 {@linkcode GridColumnDefinition.onInputEnded} 方法,避免例如文本框还未触发 `onchange` 事件就被移除元素而导致的问题
* 更多例子参考代码中 {@linkcode GridInputColumn} 的实现 */ static get editing(): boolean; + /** + * @inheritdoc GridColumn.createEdit + * @override + */ + static createEdit(trigger: (e: any) => void, col: GridColumnDefinition, container: HTMLElement, vals: GridItemWrapper): HTMLElement; + /** + * @inheritdoc GridColumn.setValue + * @override + */ + static setValue(element: HTMLElement, val: string, vals: GridItemWrapper, col: GridColumnDefinition, grid: Grid): void; + /** + * @inheritdoc GridColumn.getValue + * @override + */ + static getValue(e: any): string; + /** + * @inheritdoc GridColumn.setEnabled + * @override + */ + static setEnabled(element: HTMLElement, enabled?: boolean): void; } /** 多行文本列 */ -export class GridTextColumn extends GridInputColumn { } +export class GridTextColumn extends GridInputColumn { + /** + * @inheritdoc GridInputColumn.createEdit + * @override + */ + static createEdit(trigger: (e: any) => void, col: GridColumnDefinition, container: HTMLElement, vals: GridItemWrapper): HTMLElement; + /** + * @inheritdoc GridInputColumn.setValue + * @override + */ + static setValue(element: HTMLElement, val: string, vals: GridItemWrapper, col: GridColumnDefinition, grid: Grid): void; +} /** 下拉选择列 */ -export class GridDropdownColumn extends GridColumn { } +export class GridDropdownColumn extends GridColumn { + /** + * @inheritdoc GridColumn.createEdit + * @override + */ + static createEdit(trigger: (e: any) => void, col: GridColumnDefinition, container: HTMLElement, vals: GridItemWrapper): HTMLElement; + /** + * @inheritdoc GridColumn.setValue + * @override + */ + static setValue(element: HTMLElement, val: string, vals: GridItemWrapper, col: GridColumnDefinition): void; + /** + * @inheritdoc GridColumn.getValue + * @override + */ + static getValue(e: any, col: GridColumnDefinition): string; + /** + * @inheritdoc GridColumn.setEnabled + * @override + */ + static setEnabled(element: HTMLElement, enabled?: boolean): void; + /** + * @inheritdoc GridColumn.leaveEdit + * @override + */ + static leaveEdit?(element: HTMLElement, container: HTMLElement): void; +} /** 复选框列 */ -export class GridCheckboxColumn extends GridColumn { } +export class GridCheckboxColumn extends GridColumn { + /** + * @inheritdoc GridColumn.createEdit + * @override + */ + static createEdit(trigger: (e: any) => void): HTMLElement; + /** + * @inheritdoc GridColumn.setValue + * @override + */ + static setValue(element: HTMLElement, val: boolean): void; + /** + * @inheritdoc GridColumn.getValue + * @override + */ + static getValue(e: any): boolean; + /** + * @inheritdoc GridColumn.setEnabled + * @override + */ + static setEnabled(element: HTMLElement, enabled?: boolean): void; +} /** 图标列 */ -export class GridIconColumn extends GridColumn { } +export class GridIconColumn extends GridColumn { + /** + * @inheritdoc GridColumn.create + * @override + */ + static create(): HTMLElement; + /** + * @inheritdoc GridColumn.setValue + * @override + */ + static setValue(element: HTMLElement, val: string, vals: GridItemWrapper, col: GridColumnDefinition): void; + /** + * @inheritdoc GridColumn.setEnabled + * @override + */ + static setEnabled(element: HTMLElement, enabled?: boolean): void; +} /** 日期选择列 */ export class GridDateColumn extends GridColumn { + /** + * @inheritdoc GridColumn.createEdit + * @override + */ + static createEdit(trigger: (e: any) => void, col: GridColumnDefinition, container: HTMLElement, vals: GridItemWrapper): HTMLElement; + /** + * @inheritdoc GridColumn.setValue + * @override + */ + static setValue(element: HTMLElement, val: string | Number): void; + /** + * @inheritdoc GridColumn.getValue + * @override + */ + static getValue(e: any): string | Number; + /** + * @inheritdoc GridColumn.setEnabled + * @override + */ + static setEnabled(element: HTMLElement, enabled?: boolean): void; /** * 格式化日期对象为 M/d/yyyy 格式的字符串 * @param date 日期对象 diff --git a/lib/ui/grid/column.js b/lib/ui/grid/column.js index ad8dbc9..df07cf5 100644 --- a/lib/ui/grid/column.js +++ b/lib/ui/grid/column.js @@ -250,7 +250,7 @@ export class GridCheckboxColumn extends GridColumn { export class GridIconColumn extends GridColumn { static create() { return createElement('span', 'col-icon') } - static setValue(element, val, item, col, _grid) { + static setValue(element, val, item, col) { let className = col.iconClassName; if (typeof className === 'function') { className = className.call(col, item.values); @@ -264,7 +264,7 @@ export class GridIconColumn extends GridColumn { if (typeof type === 'function') { type = type.call(col, item.values); } - type ??= 'fa-regular'; + type ??= 'fa-light'; if (element.dataset.type !== type || element.dataset.icon !== val) { const icon = createIcon(type, val); // const layer = element.children[0]; diff --git a/lib/ui/grid/grid.d.ts b/lib/ui/grid/grid.d.ts index 599e1fb..b44ee7e 100644 --- a/lib/ui/grid/grid.d.ts +++ b/lib/ui/grid/grid.d.ts @@ -24,6 +24,22 @@ interface GridSourceItem { text: string; } +/** Grid 语言资源接口 */ +interface GridLanguages { + /** + * “所有”文本 + * + * @default `( All )` + */ + all: string; + /** “确定”文本,默认值 OK */ + ok: string; + /** “重置”文本,默认值 Reset */ + reset: string; + /** “空”文本,默认值 ( Null ) */ + null: string +} + /** 列排序枚举 */ declare enum GridColumnDirection { /** 倒序 */ @@ -70,7 +86,7 @@ export class Grid { /** 列定义的数组 */ columns: Array; /** 多语言资源对象 */ - langs?: { all: string, ok: string, reset: string }; + langs?: GridLanguages; /** 行数大于等于该值则启用虚模式,默认值 100 */ virtualCount?: Number; /** 表格行高,默认值 36 */ @@ -112,29 +128,34 @@ export class Grid { * 即将选中行时触发,返回 false、null、undefined、0 等则取消选中动作 * @param index 即将选中的行索引 * @param colIndex 即将选中的列索引 + * @eventProperty */ willSelect?: (index: Number, colIndex: Number) => boolean; /** * 单元格单击时触发,colIndex 为 -1 则表示点击的是行的空白处,返回 false 则取消事件冒泡 * @param index 点击的行索引 * @param colIndex 点击的列索引 + * @eventProperty */ cellClicked?: (index: Number, colIndex: Number) => boolean; /** * 选中行发生变化时触发的事件 * @param index 选中的行索引 + * @eventProperty */ onSelectedRowChanged?: (index?: Number) => void; /** * 单元格双击时触发的事件,colIndex 为 -1 则表示点击的是行的空白处 * @param index 双击的行索引 * @param colIndex 双击的列索引 + * @eventProperty */ onCellDblClicked?: (index: Number, colIndex: Number) => void; /** * 行双击时触发的事件 * @param index 双击的行索引 + * @eventProperty */ onRowDblClicked?: (index: Number) => void; /** @@ -145,11 +166,13 @@ export class Grid { * "sort" 为发生列排序事件,此时 value 为 1(升序)或 -1(倒序) * @param colIndex 发生变化事件的列索引 * @param value 变化的值 + * @eventProperty */ onColumnChanged?: (type: GridColumnColumnEvent, colIndex: Number, value: Number | GridColumnDirection) => void; /** * 列滚动时触发的事件 * @param e 滚动事件对象 + * @eventProperty */ onBodyScrolled?: (e: Event) => void; diff --git a/lib/ui/grid/grid.js b/lib/ui/grid/grid.js index 43b44e3..02472cb 100644 --- a/lib/ui/grid/grid.js +++ b/lib/ui/grid/grid.js @@ -17,7 +17,7 @@ const ColumnChangedType = { const RefreshInterval = isMobile() ? 32 : 0; const HoverInternal = 200; const RedumCount = 4; -const MiniDragOffset = 4; +const MiniDragOffset = 10; const MiniColumnWidth = 50; const FilterPanelWidth = 200; @@ -29,15 +29,6 @@ function getClientX(e) { return cx ?? e.clientX; } -function getOffsetLeftFromWindow(element) { - let left = 0; - while (element != null) { - left += element.offsetLeft; - element = element.offsetParent; - } - return left; -} - function indexOfParent(target) { // return [...target.parentElement.children].indexOf(target); return Array.prototype.indexOf.call(target.parentElement.children, target); @@ -127,7 +118,8 @@ export class Grid { this.langs = { all: r('allItem', '( All )'), ok: r('ok', 'OK'), - reset: r('reset', 'Reset') + reset: r('reset', 'Reset'), + null: r('null', '( Null )') }; } @@ -190,7 +182,7 @@ export class Grid { this._var.currentSource = list.filter(it => { for (let col of this.columns) { if (Array.isArray(col.filterValues)) { - const v = this._getItemValue(it.values, col.key, col.filter); + const v = this._getItemProp(it.values, false, col); if (col.filterValues.indexOf(v) < 0) { return false; } @@ -485,8 +477,8 @@ export class Grid { direction = 1; } comparer = (a, b) => { - a = this._getItemValue(a.values, col.key, col.filter); - b = this._getItemValue(b.values, col.key, col.filter); + 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) { @@ -533,7 +525,7 @@ export class Grid { let left = 0; for (let col of this.columns) { if (col.visible === false) { - const hidden = createElement('th'); + const hidden = createElement('th', 'column'); hidden.style.display = 'none'; if (col.sortable !== false) { hidden.dataset.key = col.key; @@ -544,7 +536,17 @@ export class Grid { } // style const isCheckbox = Grid.ColumnTypes.isCheckbox(col.type); - if (col.width > 0) { + 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); @@ -600,12 +602,19 @@ export class Grid { }); wrapper.appendChild(check); } - const caption = createElement('span'); - if (col.captionStyle != null) { - caption.style.cssText = convertCssStyle(col.captionStyle); + 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); } - caption.innerText = col.caption ?? ''; - wrapper.appendChild(caption); // order arrow if (col.sortable) { th.appendChild(createElement('layer', 'arrow')); @@ -906,13 +915,15 @@ export class Grid { } } - _changingColumnOrder(index, offset, x, offsetLeft, scrollLeft) { + _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 - offsetLeft + offset}px; width: ${element.style.width}; display: block`; - offset = x + scrollLeft - element.offsetLeft; // getOffsetLeftFromWindow(element); + 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; - if (offset < 0) { + const toLeft = offset < 0; + if (toLeft) { offset = -offset; for (let i = index - 1; i >= 0 && offset >= 0; i -= 1) { element = children[i]; @@ -943,13 +954,27 @@ export class Grid { } idx ??= count - 1; } - if (idx !== this._var.colAttrs.__orderIndex) { - this._var.colAttrs.__orderIndex = idx; + if (idx !== this._var.colAttrs.__orderIndex || this._var.refs.draggerCursor.style.display !== 'block') { element = children[idx]; if (element == null) { return; } - this._var.refs.draggerCursor.style.cssText = `left: ${element.offsetLeft - offsetLeft}px; display: block`; + 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`; } } @@ -1057,14 +1082,21 @@ export class Grid { } } - _getItemValue(item, key, filter) { + _getItemProp(item, editing, col) { let value; - if (typeof filter === 'function') { - value = filter(item, false, this._var.refs.body); + if (typeof col?.filter === 'function') { + value = col.filter(item, editing, this._var.refs.body); } else { - value = item[key]; + value = item[col.key]; } - return value?.Value ?? value; + if (value == null) { + return value; + } + const prop = editing ? 'Value' : 'DisplayValue'; + if (Object.prototype.hasOwnProperty.call(value, prop)) { + return value[prop]; + } + return value; } _getRowTarget(target) { @@ -1174,19 +1206,35 @@ export class Grid { } else { const dict = Object.create(null); for (let item of this._var.source) { - const val = this._getItemValue(item.values, col.key, col.filter); - if (!Object.hasOwnProperty.call(dict, val)) { - const v = item.values[col.key]; - dict[val] = { + 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: typeof col.filter === 'function' ? col.filter(item.values) : v?.DisplayValue ?? v + DisplayValue: displayValue }; } } array = Object.values(dict) .sort((a, b) => { - a = a?.Value ?? a; - b = b?.Value ?? 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; }); } @@ -1197,7 +1245,7 @@ export class Grid { } return { Value: i, - DisplayValue: i == null ? '' : i + DisplayValue: i == null ? this.langs.null : i }; }); this._fillFilterList(col, itemlist, array, itemall); @@ -1207,8 +1255,16 @@ export class Grid { searchbox.addEventListener('input', e => { const key = e.currentTarget.value.toLowerCase(); const items = key.length === 0 ? array : array.filter(i => { - const displayValue = i?.DisplayValue ?? i; - return String(displayValue ?? '').toLowerCase().includes(key); + 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); }); @@ -1223,7 +1279,7 @@ export class Grid { if (typeof col.onFilterOk === 'function') { col.onFilterOk.call(this, col, array); } else { - col.filterValues = array.map(a => a.Value); + col.filterValues = array.map(a => a.DisplayValue); } this._var.colAttrs.__filtered = true; this._refreshSource(); @@ -1268,7 +1324,8 @@ export class Grid { content.style.top = `${rowHeight}px`; this._set(col.key, 'filterSource', array); for (let item of array) { - item.__checked = !Array.isArray(col.filterValues) || col.filterValues.includes(item.Value ?? item); + 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); @@ -1282,7 +1339,7 @@ export class Grid { const div = createElement('div', 'filter-item'); div.appendChild(createCheckbox({ checked: item.__checked, - label: item?.DisplayValue ?? item, + 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); @@ -1333,7 +1390,7 @@ export class Grid { const cx = getClientX(e); const clearEvents = attr => { for (let event of ['mousemove', 'mouseup']) { - if (attr.hasOwnProperty(event)) { + if (Object.prototype.hasOwnProperty.call(attr, event)) { window.removeEventListener(event, attr[event]); delete attr[event]; } @@ -1346,20 +1403,28 @@ export class Grid { clearEvents(attr); } attr.dragging = true; - const offsetLeft = this._var.refs.header.querySelector('th:last-child').offsetLeft; - const scrollLeft = this._var.el.scrollLeft; + 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 && (offset > MiniDragOffset || offset < -MiniDragOffset)) { - dragging = true; + if (pos == null) { + if (offset > MiniDragOffset || offset < -MiniDragOffset) { + dragging = true; + } } else if (pos !== offset) { dragging = true; } if (dragging) { - this._changingColumnOrder(index, offset, cx2, offsetLeft, scrollLeft); + this._changingColumnOrder(index, offset, mouse, draggerCellLeft); attr.offset = offset; } }; @@ -1386,7 +1451,7 @@ export class Grid { const window = this.window ?? global; const clearEvents = attr => { for (let event of ['mousemove', 'mouseup']) { - if (attr.hasOwnProperty(event)) { + if (Object.prototype.hasOwnProperty.call(attr, event)) { window.removeEventListener(event, attr[event]); delete attr[event]; } @@ -1648,7 +1713,7 @@ export class Grid { if (enabled !== false) { const val = item[col.key]; let oldValue; - if (val?.Value != null) { + if (val != null && Object.prototype.hasOwnProperty.call(val, 'Value') != null) { oldValue = val.Value; val.Value = value; } else {