feature: expandable rows

add: comments
This commit is contained in:
lchen committed 2024-02-01 17:29:30 +08:00
1 parent 24b5a22595
commit 5d199a2bfb
5 files changed
+573 -110

No files matched your search

+1
View File
@@ -26,3 +26,4 @@ desktop.ini
# User definition # User definition
docs docs
jsdoc
+17
View File
@@ -283,6 +283,22 @@
background-color: var(--row-bg-color); background-color: var(--row-bg-color);
} }
&.ui-expandable {
&>svg {
width: 24px;
height: 34px;
padding: 8px 3px;
box-sizing: border-box;
display: block;
cursor: pointer;
transition: opacity .12s ease;
&:hover {
opacity: .4;
}
}
}
>span { >span {
padding: var(--spacing-cell); padding: var(--spacing-cell);
display: block; display: block;
@@ -402,6 +418,7 @@
white-space: pre; white-space: pre;
display: flex; display: flex;
align-items: center; align-items: center;
overflow: hidden;
visibility: hidden; visibility: hidden;
opacity: 0; opacity: 0;
transition: visibility 0s linear .12s, opacity .12s ease; transition: visibility 0s linear .12s, opacity .12s ease;
+1 -1
View File
@@ -261,7 +261,7 @@ export class DateSelector {
* @static * @static
* @param {HTMLElement} [dom=document.body] 父元素 * @param {HTMLElement} [dom=document.body] 父元素
* @param {Function} [trigger] 日期设置事件触发函数 * @param {Function} [trigger] 日期设置事件触发函数
* @param {Date} trigger.[this]] - 上下文为触发设置日期的 `DateSelector` 实例 * @param {Date} trigger.{this} - 上下文为触发设置日期的 `DateSelector` 实例
* @param {Date} trigger.date - 修改后的日期值 * @param {Date} trigger.date - 修改后的日期值
* @example <caption>HTML</caption> * @example <caption>HTML</caption>
* <input id="dateFrom" data-type="date" data-min="1980-01-01"/> * <input id="dateFrom" data-type="date" data-min="1980-01-01"/>
+34 -20
View File
@@ -12,6 +12,17 @@ import { createDateInput, formatDate, setDateValue, getDateValue } from "../date
* @class * @class
*/ */
export class GridColumn { export class GridColumn {
/**
* 设置该类型是否支持触发 {@linkcode GridColumnDefinition}`.onInputEnded` 方法<br/>
* 该属性返回 `true` 后,在任意事件中修改行包装对象的 `__editing` 值,则会在行列元素变动时及时触发 [onInputEnded]{@linkcode GridColumnDefinition#onInputEnded} 方法,避免例如文本框还未触发 `onchange` 事件就被移除元素而导致的问题
*
* 更多例子参考代码中 {@linkcode GridInputColumn} 的实现
* @member
* @name GridColumn.editing
* @readonly
* @type {boolean}
*/
/** /**
* 创建显示单元格时调用的方法 * 创建显示单元格时调用的方法
* @param {GridColumnDefinition} col - 列定义对象 * @param {GridColumnDefinition} col - 列定义对象
@@ -27,6 +38,8 @@ export class GridColumn {
* *
* 元素修改后设置行包装对象的 `__editing` 后,支持在离开编辑状态时及时触发 [leaveEdit]{@linkcode GridColumn.leaveEdit} 方法<br/> * 元素修改后设置行包装对象的 `__editing` 后,支持在离开编辑状态时及时触发 [leaveEdit]{@linkcode GridColumn.leaveEdit} 方法<br/>
* 更多例子参考代码中 {@linkcode GridDropdownColumn} 的实现。 * 更多例子参考代码中 {@linkcode GridDropdownColumn} 的实现。
* @method
* @name GridColumn.createEdit
* @param {Function} trigger - 编辑事件回调函数,`e` 参数会传递给 [getValue]{@linkcode GridColumn.getValue} 方法 * @param {Function} trigger - 编辑事件回调函数,`e` 参数会传递给 [getValue]{@linkcode GridColumn.getValue} 方法
* @param {GridColumnDefinition} col - 列定义对象 * @param {GridColumnDefinition} col - 列定义对象
* @param {HTMLElement} container - 父容器元素 * @param {HTMLElement} container - 父容器元素
@@ -34,15 +47,25 @@ export class GridColumn {
* @returns {HTMLElement} 返回创建的编辑状态的单元格元素 * @returns {HTMLElement} 返回创建的编辑状态的单元格元素
* @virtual * @virtual
*/ */
static createEdit() { }
/** /**
* 创建列头时调用的方法 * 创建列头时调用的方法
* @method
* @name GridColumn.createCaption
* @param {GridColumnDefinition} col - 列定义对象 * @param {GridColumnDefinition} col - 列定义对象
* @returns {HTMLElement} 返回创建的列头元素 * @returns {HTMLElement} 返回创建的列头元素
* @virtual * @virtual
*/ */
static createCaption() { }
/**
* 获取编辑状态单元格值时调用的方法
* @method
* @name GridColumn.getValue
* @param {any} `e` 由 [createEdit]{@linkcode GridColumn.createEdit} 方法中 `trigger` 函数传递来的对象
* @param {GridColumnDefinition} col - 列定义对象
* @returns {(string | boolean | number)} 返回单元格的值
* @virtual
*/
/** /**
* 设置单元格值时调用的方法 * 设置单元格值时调用的方法
@@ -57,15 +80,6 @@ export class GridColumn {
element.innerText = val; element.innerText = val;
} }
/**
* 获取编辑状态单元格值时调用的方法
* @param {any} `e` 由 [createEdit]{@linkcode GridColumn.createEdit} 方法中 `trigger` 函数传递来的对象
* @param {GridColumnDefinition} col - 列定义对象
* @returns {(string | boolean | number)} 返回单元格的值
* @virtual
*/
static getValue() { }
/** /**
* 设置单元格样式时调用的方法 * 设置单元格样式时调用的方法
* @param {HTMLElement} element - 单元格元素 * @param {HTMLElement} element - 单元格元素
@@ -94,27 +108,22 @@ export class GridColumn {
/** /**
* 单元格离开编辑元素时触发,需要由行包装对象的 `__editing` 来确定是否触发。 * 单元格离开编辑元素时触发,需要由行包装对象的 `__editing` 来确定是否触发。
* @method
* @name GridColumn.leaveEdit
* @param {HTMLElement} element - 单元格元素 * @param {HTMLElement} element - 单元格元素
* @param {HTMLElement} container - 父容器元素 * @param {HTMLElement} container - 父容器元素
* @virtual * @virtual
*/ */
static leaveEdit() { }
static toString() { return '[object Column]' } static toString() { return '[object Column]' }
} }
/** /**
* 单行文本列 * 单行文本输入列
* @class
* @extends GridColumn * @extends GridColumn
*/ */
export class GridInputColumn extends GridColumn { export class GridInputColumn extends GridColumn {
/**
* 设置该类型是否支持触发 [onInputEnded]{@linkcode GridColumnDefinition.onInputEnded} 方法<br/>
* 该属性返回 `true` 后,在任意事件中修改行包装对象的 `__editing` 值,则会在行列元素变动时及时触发 [onInputEnded]{@linkcode GridColumnDefinition.onInputEnded} 方法,避免例如文本框还未触发 `onchange` 事件就被移除元素而导致的问题<br/>
* 更多例子参考代码中 {@linkcode GridInputColumn} 的实现
* @readonly
* @type {boolean}
*/
static get editing() { return true }; static get editing() { return true };
static createEdit(trigger, col, _wrapper, vals) { static createEdit(trigger, col, _wrapper, vals) {
@@ -185,6 +194,11 @@ export class GridTextColumn extends GridInputColumn {
const SymbolDropdown = Symbol.for('ui-dropdown'); const SymbolDropdown = Symbol.for('ui-dropdown');
/**
* 下拉选择列
* @class
* @extends GridColumn
*/
export class GridDropdownColumn extends GridColumn { export class GridDropdownColumn extends GridColumn {
static createEdit(trigger, col, container, it) { static createEdit(trigger, col, container, it) {
const drop = new Dropdown({ const drop = new Dropdown({
+520 -89
View File
@@ -15,12 +15,13 @@ const ColumnChangedType = {
Resize: 'resize', Resize: 'resize',
Sort: 'sort' Sort: 'sort'
}; };
const RefreshInterval = isMobile() ? 32 : 0; const RefreshInterval = isMobile() ? 32 : 10;
const HoverInternal = 200; const HoverInternal = 200;
const RedumCount = 4; const RedumCount = 4;
const MiniDragOffset = 10; const MiniDragOffset = 10;
const MiniColumnWidth = 50; const MiniColumnWidth = 50;
const FilterPanelWidth = 200; const FilterPanelWidth = 200;
const ExpandableWidth = 24;
function getClientX(e) { function getClientX(e) {
if (e == null) { if (e == null) {
@@ -58,9 +59,9 @@ let r = lang;
* 行数据包装接口 * 行数据包装接口
* @typedef GridItemWrapper * @typedef GridItemWrapper
* @property {object} values - 真实数据对象 * @property {object} values - 真实数据对象
* @property {(GridItem | any)} values.[key]] 数据属性 * @property {(GridItem | any)} values.{key} 数据属性
* @property {object} source - 下拉数据源缓存对象 * @property {object} source - 下拉数据源缓存对象
* @property {GridSourceItem[]} source.[key]] 数据源 * @property {GridSourceItem[]} source.{key} 数据源
*/ */
/** /**
@@ -84,6 +85,7 @@ let r = lang;
* @param {GridItem} item - 行数据对象 * @param {GridItem} item - 行数据对象
* @param {boolean} editing - 是否处于编辑状态 * @param {boolean} editing - 是否处于编辑状态
* @param {HTMLElement} [body] - Grid 控件的 `<tbody>` 部分 * @param {HTMLElement} [body] - Grid 控件的 `<tbody>` 部分
* @returns {any} - 返回过滤后的显示或编辑值
* @this GridColumnDefinition * @this GridColumnDefinition
*/ */
@@ -175,11 +177,46 @@ let r = lang;
* @this GridColumnDefinition * @this GridColumnDefinition
*/ */
/**
* 下拉列表参数对象
* @typedef DropdownOptions
* @property {string} [textKey=text] - 文本关键字
* @property {string} [valueKey=value] - 值关键字
* @property {string} [htmlKey=html] - 源码显示的关键字
* @property {number} [maxLength=500] - 最大输入长度
* @property {boolean} [multiSelect] - 是否允许多选
* @property {string} [selected] - 选中值
* @property {string[]} [selectedList] - 选中的数组
* @property {boolean} [disabled] - 是否禁用
* @property {boolean} [input] - 是否支持输入
* @property {boolean} [search] - 是否支持搜索
* @property {string[]} [searchKeys] - 搜索的关键字数组
* @property {string} [searchPlaceholder] - 搜索提示文本,默认值取语言资源 `searchHolder` "Search..."
* @property {number} [tabIndex] - 焦点索引
* @property {string} [placeholder] - 输入框的提示文本
* @property {boolean} [slideFixed] - 是否固定为向下展开
* @property {HTMLElement} [wrapper] - 父元素,默认添加到头元素之后
*/
/**
* 自定义日期格式化回调函数
* @callback DateFormatterCallback
* @param {Date} date - 日期值
* @returns {any} 返回格式化后的结果
*/
/** /**
* 列定义接口 * 列定义接口
* @typedef {object} GridColumnDefinition * @typedef {object} GridColumnDefinition
* @property {string} key - 列关键字,默认以该关键字从行数据中提取单元格值,行数据的关键字属性值里包含 DisplayValue 则优先显示此值 * @property {string} key - 列关键字,默认以该关键字从行数据中提取单元格值,行数据的关键字属性值里包含 DisplayValue 则优先显示此值
* @property {(GridColumnTypeEnum | GridColumn)} [type=Grid.ColumnTypes.Common] - 列的类型,可以为 {@linkcode GridColumn} 的子类,或者内置类型 {@linkcode GridColumnTypeEnum} * @property {(GridColumnTypeEnum | GridColumn)} [type=Grid.ColumnTypes.Common] - 列的类型,可以为 {@linkcode GridColumn} 的子类,或者如下内置类型 {@linkcode Grid.ColumnTypes}
* @property {number} type.Common=0 - 通用列(只读)
* @property {number} type.Input=1 - 单行文本列
* @property {number} type.Dropdown=2 - 下拉选择列
* @property {number} type.Checkbox=3 - 复选框列
* @property {number} type.Icon=4 - 图标列
* @property {number} type.Text=5 - 多行文本列
* @property {number} type.Date=6 - 日期选择列
* @property {string} [caption] - 列标题文本 * @property {string} [caption] - 列标题文本
* @property {object} [captionStyle] - 列标题的元素样式 * @property {object} [captionStyle] - 列标题的元素样式
* @property {number} [width] - 大于 0 则设置为该宽度,否则根据列内容自动调整列宽 * @property {number} [width] - 大于 0 则设置为该宽度,否则根据列内容自动调整列宽
@@ -188,8 +225,16 @@ let r = lang;
* *
* * `boolean` 则直接使用该值 * * `boolean` 则直接使用该值
* * `string` 则以该值为关键字从行数据中取值作为判断条件 * * `string` 则以该值为关键字从行数据中取值作为判断条件
* * `(item: GridItem) => boolean` 则调用该函数(上下文为列定义对象),以返回值作为判断条件 * * `GridItemBooleanCallback` 则调用如下回调,以返回值作为判断条件
* @property {GridColumnDefinition} enabled.{this} - 上下文为列定义对象
* @property {GridItem} enabled.item - 行数据对象
* @property {boolean} enabled.{returns} - 返回是否启用
* @property {GridItemFilterCallback} [filter] - 单元格取值采用该函数返回的值 * @property {GridItemFilterCallback} [filter] - 单元格取值采用该函数返回的值
* @property {GridColumnDefinition} filter.{this} - 上下文为列定义对象
* @property {GridItem} filter.item - 行数据对象
* @property {boolean} filter.editing - 是否处于编辑状态
* @property {HTMLElement} [filter.body] - Grid 控件的 `<tbody>` 部分
* @property {any} filter.{returns} - 返回过滤后的显示或编辑值
* @property {string} [text] - 单元格以该值填充内容,忽略filter与关键字属性 * @property {string} [text] - 单元格以该值填充内容,忽略filter与关键字属性
* @property {boolean} [visible=true] - 列是否可见 * @property {boolean} [visible=true] - 列是否可见
* @property {boolean} [resizable=true] - 列是否允许调整宽度 * @property {boolean} [resizable=true] - 列是否允许调整宽度
@@ -198,30 +243,72 @@ let r = lang;
* @property {boolean} [allcheck=false] - 列为复选框类型时是否在列头增加全选复选框 * @property {boolean} [allcheck=false] - 列为复选框类型时是否在列头增加全选复选框
* @property {object} [css] - 单元格css样式对象(仅在重建行元素时读取) * @property {object} [css] - 单元格css样式对象(仅在重建行元素时读取)
* @property {GridItemObjectCallback} [styleFilter] - 根据返回值填充单元格样式(填充行列数据时读取) * @property {GridItemObjectCallback} [styleFilter] - 根据返回值填充单元格样式(填充行列数据时读取)
* @property {GridColumnDefinition} styleFilter.{this} - 上下文为列定义对象
* @property {GridItem} styleFilter.item - 行数据对象
* @property {object} styleFilter.{returns} - 返回样式对象
* @property {GridItemStringCallback} [bgFilter] - 根据返回值设置单元格背景色 * @property {GridItemStringCallback} [bgFilter] - 根据返回值设置单元格背景色
* @property {GridColumnDefinition} bgFilter.{this} - 上下文为列定义对象
* @property {GridItem} bgFilter.item - 行数据对象
* @property {string} bgFilter.{returns} - 返回单元格背景色字符串
* @property {object} [events] - 给单元格元素附加事件(事件函数上下文为数据行对象) * @property {object} [events] - 给单元格元素附加事件(事件函数上下文为数据行对象)
* @property {Function} events.[event]] - 事件回调函数 * @property {Function} events.{event} - 事件回调函数
* @property {(object | GridItemObjectCallback)} [attrs] - 根据返回值设置单元格元素的附加属性,允许直接设置对象也支持函数返回对象 * @property {(object | GridItemObjectCallback)} [attrs] - 根据返回值设置单元格元素的附加属性,允许直接设置对象也支持调用如下函数返回对象
* @property {GridColumnDefinition} attrs.{this} - 上下文为列定义对象
* @property {GridItem} attrs.item - 行数据对象
* @property {object} attrs.{returns} - 返回附加属性对象
* @property {boolean} [allowFilter=false] - 是否允许进行列头过滤 * @property {boolean} [allowFilter=false] - 是否允许进行列头过滤
* @property {(GridItem[] | GridColumnFilterSourceCallback)} [filterSource] - 自定义列过滤器的数据源(函数上下文为Grid) * @property {(GridItem[] | GridColumnFilterSourceCallback)} [filterSource] - 自定义列过滤器的数据源,允许调用如下函数
* @property {Grid} filterSource.{this} - 上下文为 Grid
* @property {GridColumnDefinition} filterSource.col - 列定义对象
* @property {GridItem[]} filterSource.{returns} - 返回过滤器的数据数组
* @property {GridItemSortCallback} [sortFilter] - 自定义列排序函数 * @property {GridItemSortCallback} [sortFilter] - 自定义列排序函数
* @property {GridItem} sortFilter.a - 对比行数据1
* @property {GridItem} sortFilter.b - 对比行数据2
* @property {number} sortFilter.{returns} - 返回大小对比结果
* @property {DropdownOptions} [dropOptions] - 列为下拉列表类型时以该值设置下拉框的参数 * @property {DropdownOptions} [dropOptions] - 列为下拉列表类型时以该值设置下拉框的参数
* @property {(GridSourceItem[] | GridDropdownSourceCallback | Promise<GridSourceItem[]>)} [source] - 列为下拉列表类型时以该值设置下拉列表数据源,支持函数返回,也支持返回异步对象 * @property {(GridSourceItem[] | Promise<GridSourceItem[]> | GridDropdownSourceCallback)} [source] - 列为下拉列表类型时以该值设置下拉列表数据源,支持返回异步对象,也支持如下函数返回
* @property {GridItem} source.item - 行数据对象
* @property {GridSourceItem[]} source.{returns} - 返回行下拉列表数据源
* @property {boolean} [sourceCache=false] - 下拉列表数据源是否缓存结果(即行数据未发生变化时仅从source属性获取一次值) * @property {boolean} [sourceCache=false] - 下拉列表数据源是否缓存结果(即行数据未发生变化时仅从source属性获取一次值)
* @property {("fa-light" | "fa-regular" | "fa-solid")} [iconType=fa-light] - 列为图标类型时以该值设置图标样式(函数上下文为列定义对象) * @property {("fa-light" | "fa-regular" | "fa-solid")} [iconType=fa-light] - 列为图标类型时以该值设置图标样式
* @property {(string | GridItemStringCallback)} [iconClassName] - 列为图标类型时以该值作为单元格元素的额外样式类型(函数上下文为列定义对象) * @property {(string | GridItemStringCallback)} [iconClassName] - 列为图标类型时以该值作为单元格元素的额外样式类型,支持直接使用字符串或者调用如下函数
* @property {GridColumnDefinition} iconClassName.{this} - 上下文为列定义对象
* @property {GridItem} iconClassName.item - 行数据对象
* @property {string} iconClassName.{returns} - 返回额外样式类型字符串
* @property {string} [dateMin] - 列为日期类型时以该值作为最小可选日期值 * @property {string} [dateMin] - 列为日期类型时以该值作为最小可选日期值
* @property {string} [dateMax] - 列为日期类型时以该值作为最大可选日期值 * @property {string} [dateMax] - 列为日期类型时以该值作为最大可选日期值
* @property {DateFormatterCallback} [dateValueFormatter] - 列为日期类型时自定义日期格式化函数 * @property {DateFormatterCallback} [dateValueFormatter] - 列为日期类型时自定义日期格式化函数
* @property {(string | GridItemStringCallback)} [tooltip] - 以返回值额外设置单元格的tooltip(函数上下文为列定义对象) * @property {Date} dateValueFormatter.date - 日期值
* @property {any} dateValueFormatter.{returns} - 返回格式化后的结果
* @property {(string | GridItemStringCallback)} [tooltip] - 额外设置单元格的 tooltip,支持直接使用字符串或者调用如下函数
* @property {GridColumnDefinition} tooltip.{this} - 上下文为列定义对象
* @property {GridItem} tooltip.item - 行数据对象
* @property {string} tooltip.{returns} - 返回额外 tooltip 字符串
* @property {GridColumnCheckedCallback} [onAllChecked] - 列头复选框改变时触发 * @property {GridColumnCheckedCallback} [onAllChecked] - 列头复选框改变时触发
* @property {Grid} onAllChecked.{this} - 上下文为 Grid
* @property {GridColumnDefinition} onAllChecked.col - 列定义对象 * @property {GridColumnDefinition} onAllChecked.col - 列定义对象
* @property {boolean} onAllChecked.flag - 是否选中 * @property {boolean} onAllChecked.flag - 是否选中
* @property {GridCellChangedCallback} [onChanged] - 单元格发生变化时触发 * @property {GridCellChangedCallback} [onChanged] - 单元格发生变化时触发
* @property {Grid} onChanged.{this} - 上下文为 Grid
* @property {GridItem} onChanged.item - 行数据对象
* @property {(boolean | string | number)} onChanged.value - 修改后的值
* @property {(boolean | string | number)} onChanged.oldValue - 修改前的值
* @property {any} [onChanged.e] - 列修改事件传递过来的任意对象
* @property {GridCellInputEndedCallback} [onInputEnded] - 文本单元格在输入完成时触发的事件 * @property {GridCellInputEndedCallback} [onInputEnded] - 文本单元格在输入完成时触发的事件
* @property {Grid} onInputEnded.{this} - 上下文为 Grid
* @property {GridColumnDefinition} onInputEnded.col - 列定义对象
* @property {string} onInputEnded.value - 修改后的文本框值
* @property {GridColumnFilterOkCallback} [onFilterOk] - 列过滤点击 `OK` 时触发的事件 * @property {GridColumnFilterOkCallback} [onFilterOk] - 列过滤点击 `OK` 时触发的事件
* @property {Grid} onFilterOk.{this} - 上下文为 Grid
* @property {GridColumnDefinition} onFilterOk.col - 列定义对象
* @property {GridItem[]} onFilterOk.selected - 选中的过滤项
* @property {GridColumnFilteredCallback} [onFiltered] - 列过滤后触发的事件 * @property {GridColumnFilteredCallback} [onFiltered] - 列过滤后触发的事件
* @property {Grid} onFiltered.{this} - 上下文为 Grid
* @property {GridColumnDefinition} onFiltered.col - 列定义对象
* @property {GridColumnDropExpandedCallback} [onDropExpanded] - 列为下拉框类型时在下拉列表展开时触发的事件 * @property {GridColumnDropExpandedCallback} [onDropExpanded] - 列为下拉框类型时在下拉列表展开时触发的事件
* @property {GridColumnDefinition} onDropExpanded.{this} - 上下文为列定义对象
* @property {GridItem} onDropExpanded.item - 行数据对象
* @property {Dropdown} onDropExpanded.drop - 拉框对象
*/ */
/** /**
@@ -275,6 +362,20 @@ const GridColumnDirection = {
* @property {("asc" | "desc")} order - 升序或降序 * @property {("asc" | "desc")} order - 升序或降序
*/ */
/**
* 扩展行对象接口
* @typedef GridExpandableObject
* @property {HTMLElement} element - 扩展行元素
*/
/**
* 扩展行生成回调函数
* @callback GridExpandableObjectCallback
* @param {GridItem} item - 行数据对象
* @returns {GridExpandableObject} 返回扩展行对象
* @this Grid
*/
/** /**
* Grid 控件基础类 * Grid 控件基础类
* *
@@ -324,30 +425,168 @@ const GridColumnDirection = {
*/ */
export class Grid { export class Grid {
_var = { _var = {
/**
* 父容器元素
* @type {HTMLElement}
*/
parent: null,
/**
* Grid 元素 - `div.ui-grid`
* @type {HTMLDivElement}
*/
el: null,
/**
* 全部数据数组
* @type {GridItemWrapper[]}
*/
source: null,
/**
* 当前已过滤显示的数据数组
* @type {GridItemWrapper[]}
*/
currentSource: null,
/**
* 当前选中的列索引
* @type {number}
*/
selectedColumnIndex: -1, selectedColumnIndex: -1,
/**
* 当前选中的行索引数组
* @type {number[]}
*/
selectedIndexes: null,
/**
* 虚模式头部索引
* @type {number}
*/
startIndex: 0, startIndex: 0,
/**
* 旧选择索引数组
* @type {number[]}
*/
oldSelectedIndexes: null,
/**
* 旧虚模式头部索引
*/
oldIndex: null,
/**
* 当前滚动上边距
* @type {number}
*/
scrollTop: 0,
/**
* 当前滚动左边距
* @type {number}
*/
scrollLeft: 0,
/**
* 一页高度可显示的行数
* @type {number}
*/
rowCount: -1, rowCount: -1,
/**
* 列类型缓存字典
* @property {GridColumn} {key} - 关键字对应列的类型缓存对象
*/
colTypes: {}, colTypes: {},
colAttrs: {} /**
* 列属性字典
* @property {object} {key} - 关键字对应列的拖拽、调整大小暂存对象
* @property {boolean} {key}.dragging - 列正在拖拽
* @property {number} {key}.offset - 列拖拽偏移
* @property {Function} {key}.mousemove - 拖拽或调整大小移动回调函数
* @property {Function} {key}.mouseup - 拖拽或调整大小鼠标释放回调函数
* @property {number} {key}.resizing - 列临时大小
* @property {boolean} {key}.sizing - 列已进入修改大小的状态
* @property {boolean} {key}.autoResize - 列需要自动调整大小
* @property {object} {key}.style - 列样式对象
* @property {GridItem[]} {key}.filterSource - 列过滤面板数据源
* @property {number} {key}.filterHeight - 列过滤面板高度
* @property {number} {key}.filterTop - 列过滤面板滚动头部间距
*/
colAttrs: {
/**
* 有已过滤的列
* @type {boolean}
*/
__filtered: false,
/**
* 过滤面板已打开
* @type {boolean}
*/
__filtering: false,
/**
* 上一个目标排序列索引
* @type {number}
*/
__orderIndex: -1,
},
/**
* 是否处于渲染中
* @type {boolean}
*/
rendering: false,
/**
* 正文高度
* @type {number}
*/
containerHeight: null,
/**
* 正文宽度
* @type {number}
*/
bodyClientWidth: null,
/**
* 是否需要 resize
* @type {boolean}
*/
needResize: null,
/**
* 页面元素引用
*/
refs: {
/**
* 表格引用 - table.ui-grid-table
* @type {HTMLTableElement}
*/
table: null,
/**
* 表格正文引用 - tbody
* @type {HTMLTableSectionElement}
*/
body: null,
/**
* 表格头部引用 - thead>tr
* @type {HTMLTableSectionElement}
*/
header: null,
/**
* 加载状态元素引用 - div.ui-grid-loading
* @type {HTMLDivElement}
*/
loading: null,
/**
* 大小计算元素引用 - span.ui-grid-sizer
* @type {HTMLSpanElement}
*/
sizer: null,
/**
* 包装元素引用 - div.ui-grid-wrapper
* @type {HTMLDivElement}
*/
wrapper: null,
/**
* 拖拽块引用 - div.dragger
* @type {HTMLDivElement}
*/
dragger: null,
/**
* 拖拽光标引用 - layer.dragger-cursor
* @type {HTMLElement}
*/
draggerCursor: null,
}
}; };
// _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 = [];
/** /**
* 列定义的数组 * 列定义的数组
@@ -460,8 +699,26 @@ export class Grid {
* 排序列数组 * 排序列数组
* @type {GridColumnSortDefinition[]} * @type {GridColumnSortDefinition[]}
* @default null * @default null
* @property {string} column - 排序列的关键字
* @property {("asc" | "desc")} order - 升序或降序
*/ */
sortArray = null; sortArray = null;
/**
* 是否支持点击扩展
* @type {boolean}
* @default false
*/
expandable;
/**
* 扩展行生成器
* @type {GridExpandableObjectCallback}
* @property {Grid} {this} - 上下文为 Grid
* @property {GridItem} item - 行数据对象
* @property {GridExpandableObject} {returns} - 返回扩展行对象
* @property {number} {returns}.index - 行索引
* @property {HTMLElement} {returns}.element - 扩展行元素
*/
expandableGenerator;
/** /**
* 即将选中行时触发 * 即将选中行时触发
@@ -469,6 +726,7 @@ export class Grid {
* @param {number} index - 即将选中的行索引 * @param {number} index - 即将选中的行索引
* @param {number} colIndex - 即将选中的列索引 * @param {number} colIndex - 即将选中的列索引
* @returns {boolean} 返回 `false`、`null`、`undefined`、`0` 等则取消选中动作 * @returns {boolean} 返回 `false`、`null`、`undefined`、`0` 等则取消选中动作
* @this Grid
*/ */
willSelect; willSelect;
/** /**
@@ -477,6 +735,7 @@ export class Grid {
* @param {number} index - 点击的行索引 * @param {number} index - 点击的行索引
* @param {number} colIndex - 点击的列索引 * @param {number} colIndex - 点击的列索引
* @returns {boolean} 返回 false 则取消事件冒泡 * @returns {boolean} 返回 false 则取消事件冒泡
* @this Grid
*/ */
cellClicked; cellClicked;
@@ -484,6 +743,7 @@ export class Grid {
* 选中行发生变化时触发的事件 * 选中行发生变化时触发的事件
* @event * @event
* @param {number} index - 选中的行索引 * @param {number} index - 选中的行索引
* @this Grid
*/ */
onSelectedRowChanged; onSelectedRowChanged;
/** /**
@@ -491,12 +751,14 @@ export class Grid {
* @event * @event
* @param {number} index - 双击的行索引 * @param {number} index - 双击的行索引
* @param {number} colIndex - 双击的列索引 * @param {number} colIndex - 双击的列索引
* @this Grid
*/ */
onCellDblClicked; onCellDblClicked;
/** /**
* 行双击时触发的事件 * 行双击时触发的事件
* @event * @event
* @param {number} index - 双击的行索引 * @param {number} index - 双击的行索引
* @this Grid
*/ */
onRowDblClicked; onRowDblClicked;
/** /**
@@ -509,31 +771,56 @@ export class Grid {
* * "sort" 为发生列排序事件,此时 value 为 1(升序)或 -1(倒序) * * "sort" 为发生列排序事件,此时 value 为 1(升序)或 -1(倒序)
* @param {number} colIndex - 发生变化事件的列索引 * @param {number} colIndex - 发生变化事件的列索引
* @param {number | GridColumnDirection} value - 变化的值 * @param {number | GridColumnDirection} value - 变化的值
* @this Grid
*/ */
onColumnChanged; onColumnChanged;
/** /**
* 列滚动时触发的事件 * 列滚动时触发的事件
* @event * @event
* @param {Event} e - 滚动事件对象 * @param {Event} e - 滚动事件对象
* @this Grid
*/ */
onBodyScrolled; onBodyScrolled;
/**
* 扩展行展开时触发的事件
* @event
* @param {GridItem} item - 行数据对象
* @param {GridExpandableObject} expandableObject - 由 [expandableGenerator]{@linkcode Grid#expandableGenerator} 产生的行扩展对象
* @param {HTMLElement} expandableObject.element - 扩展行元素
* @this Grid
*/
onRowExpanded;
/**
* 扩展行收缩时触发的事件
* @event
* @param {GridItem} item - 行数据对象
* @param {GridExpandableObject} expandableObject - 由 [expandableGenerator]{@linkcode Grid#expandableGenerator} 产生的行扩展对象
* @param {HTMLElement} expandableObject.element - 扩展行元素
* @this Grid
*/
onRowCollapsed;
/** /**
* 列类型枚举 * 列类型枚举
* @readonly * @readonly
* @type {GridColumnTypeEnum} * @type {GridColumnTypeEnum}
* @property {number} Common=0 - 通用列(只读)
* @property {number} Input=1 - 单行文本列
* @property {number} Dropdown=2 - 下拉选择列
* @property {number} Checkbox=3 - 复选框列
* @property {number} Icon=4 - 图标列
* @property {number} Text=5 - 多行文本列
* @property {number} Date=6 - 日期选择列
*/ */
static get ColumnTypes() { return GridColumnTypeEnum } static get ColumnTypes() { return GridColumnTypeEnum }
/** /**
* * Grid 控件构造函数
* @param {(string | HTMLElement)?} container Grid 控件所在的父容器,可以是 string 表示选择器,也可以是 HTMLElement 对象 * @param {(string | HTMLElement)?} container Grid 控件所在的父容器,可以是 string 表示选择器,也可以是 HTMLElement 对象<br/>_**构造时可以不进行赋值,但是调用 init 函数时则必须进行赋值**_
* Grid 控件构造函数<br/>
* _**构造时可以不进行赋值,但是调用 init 函数时则必须进行赋值**_
* @param {Function} [getText] 获取多语言文本的函数代理 * @param {Function} [getText] 获取多语言文本的函数代理
* @param {string} getText.id - 资源 ID * @param {string} getText.id - 资源 ID
* @param {string} [getText.def] - 默认资源 * @param {string} [getText.def] - 默认资源
* @param {string} getText.[returns]] 返回的多语言 * @param {string} getText.{returns} 返回的多语言
*/ */
constructor(container, getText) { constructor(container, getText) {
this._var.parent = typeof container === 'string' ? document.querySelector(container) : container; this._var.parent = typeof container === 'string' ? document.querySelector(container) : container;
@@ -589,6 +876,8 @@ export class Grid {
/** /**
* 获取已过滤的数据数组,或者设置数据并刷新列表 * 获取已过滤的数据数组,或者设置数据并刷新列表
* @type {GridItem[]} * @type {GridItem[]}
* @property {any} Value - 值
* @property {string} DisplayValue - 显示值
*/ */
get source() { return this._var.currentSource?.map(s => s.values) } get source() { return this._var.currentSource?.map(s => s.values) }
set source(list) { set source(list) {
@@ -608,6 +897,13 @@ export class Grid {
this._refreshSource(list); this._refreshSource(list);
} }
/**
* 获取已过滤的数据中的扩展对象数组
* @type {GridExpandableObject[]}
* @property {HTMLElement} element - 扩展行元素
*/
get sourceExpandable() { return this._var.currentSource?.map(s => s.__expandable_object) }
/** /**
* 设置单行数据 * 设置单行数据
* @param {number} index - 行索引 * @param {number} index - 行索引
@@ -840,9 +1136,13 @@ export class Grid {
return this.columns[this.sortIndex]?.key; return this.columns[this.sortIndex]?.key;
} }
get tableRows() { get _tableRows() {
// return [...this._var.refs.body.children]; // return [...this._var.refs.body.children];
return Array.prototype.slice.call(this._var.refs.body.children); return Array.prototype.slice.call(this._var.refs.body.querySelectorAll('&>.ui-grid-row'));
}
get _headerCells() {
return Array.prototype.slice.call(this._var.refs.header.querySelectorAll('&>th.column'));
} }
/** /**
@@ -856,7 +1156,7 @@ export class Grid {
if (this.readonly !== true) { if (this.readonly !== true) {
this.refresh(); this.refresh();
} else { } else {
this.tableRows.forEach((row, i) => { this._tableRows.forEach((row, i) => {
if (indexes.includes(startIndex + i)) { if (indexes.includes(startIndex + i)) {
row.classList.add('selected'); row.classList.add('selected');
} else if (row.classList.contains('selected')) { } else if (row.classList.contains('selected')) {
@@ -955,6 +1255,38 @@ export class Grid {
e.stopPropagation(); e.stopPropagation();
} }
}); });
grid.addEventListener('mousedown', e => {
if (e.target === this._var.el) {
// cancel selections
const selectedIndexes = this._var.selectedIndexes;
if (selectedIndexes == null || selectedIndexes.length === 0) {
return;
}
selectedIndexes.splice(0);
if (this.readonly !== true) {
this.refresh();
} else {
this._tableRows.forEach(row => {
row.classList.remove('selected');
});
}
if (typeof this.onSelectedRowChanged === 'function') {
this.onSelectedRowChanged(selectedIndex);
}
this._var.selectedColumnIndex = -1;
return;
}
let [parent, target] = this._getRowTarget(e.target);
if (parent == null) {
return;
}
const rowIndex = indexOfParent(parent);
let colIndex = indexOfParent(target) - (this.expandable ? 1 : 0);
if (colIndex >= this.columns.length) {
colIndex = -1;
}
this._onRowClicked(e, rowIndex, colIndex);
});
container.replaceChildren(grid); container.replaceChildren(grid);
const sizer = createElement('span', 'ui-grid-sizer'); const sizer = createElement('span', 'ui-grid-sizer');
grid.appendChild(sizer); grid.appendChild(sizer);
@@ -969,7 +1301,7 @@ export class Grid {
const table = createElement('table', 'ui-grid-table'); const table = createElement('table', 'ui-grid-table');
this._var.refs.table = table; this._var.refs.table = table;
this._createHeader(table); this._createHeader(table);
const body = this._createBody(table); this._createBody(table);
wrapper.appendChild(table); wrapper.appendChild(table);
// tooltip // tooltip
if (!this.tooltipDisabled) { if (!this.tooltipDisabled) {
@@ -985,7 +1317,7 @@ export class Grid {
}); });
holder.addEventListener('dblclick', e => this._onRowDblClicked(e)); holder.addEventListener('dblclick', e => this._onRowDblClicked(e));
wrapper.appendChild(holder); wrapper.appendChild(holder);
body.addEventListener('mousemove', e => throttle(this._onBodyMouseMove, HoverInternal, this, e, holder), { passive: true }); wrapper.addEventListener('mousemove', e => throttle(this._onGridMouseMove, HoverInternal, this, e, holder), { passive: true });
} }
// loading // loading
@@ -1057,7 +1389,7 @@ export class Grid {
const filtered = this.columns.some(c => c.filterValues != null); const filtered = this.columns.some(c => c.filterValues != null);
if ((filtered ^ this._var.colAttrs.__filtered) === 1) { if ((filtered ^ this._var.colAttrs.__filtered) === 1) {
this._var.colAttrs.__filtered = filtered; this._var.colAttrs.__filtered = filtered;
const headers = this._var.refs.header.children; const headers = this._headerCells;
for (let i = 0; i < this.columns.length; ++i) { for (let i = 0; i < this.columns.length; ++i) {
const ele = headers[i].querySelector('.filter'); const ele = headers[i].querySelector('.filter');
if (ele == null) { if (ele == null) {
@@ -1076,7 +1408,7 @@ export class Grid {
if (this.extraRows > 0) { if (this.extraRows > 0) {
length += this.extraRows; length += this.extraRows;
} }
this._var.containerHeight = length * (this.rowHeight + 1); this._var.containerHeight = (length + 1) * (this.rowHeight + 1);
if (!keep) { if (!keep) {
this._var.el.scrollTop = 0; this._var.el.scrollTop = 0;
// this._var.el.scrollLeft = 0; // this._var.el.scrollLeft = 0;
@@ -1095,7 +1427,7 @@ export class Grid {
throw new Error('body has not been created.'); throw new Error('body has not been created.');
} }
const widths = {}; const widths = {};
this._fillRows(this.tableRows, this.columns, widths); this._fillRows(this._tableRows, this.columns, widths);
if (this._var.needResize && widths.flag) { if (this._var.needResize && widths.flag) {
this._var.needResize = false; this._var.needResize = false;
this.columns.forEach((col, i) => { this.columns.forEach((col, i) => {
@@ -1161,7 +1493,7 @@ export class Grid {
} }
this.sortArray = null; this.sortArray = null;
const direction = this.sortDirection; const direction = this.sortDirection;
[...this._var.refs.header.children].forEach((th, i) => { [...this._headerCells].forEach((th, i) => {
const arrow = th.querySelector('.arrow'); const arrow = th.querySelector('.arrow');
if (arrow == null) { if (arrow == null) {
return; return;
@@ -1223,7 +1555,7 @@ export class Grid {
this.refresh(); this.refresh();
} }
// arrow icon // arrow icon
[...this._var.refs.header.children].forEach((th, i) => { [...this._headerCells].forEach((th, i) => {
const arrow = th.querySelector('.arrow'); const arrow = th.querySelector('.arrow');
if (arrow == null) { if (arrow == null) {
return; return;
@@ -1263,7 +1595,7 @@ export class Grid {
grid.onSelectedRowChanged = rowChanged; grid.onSelectedRowChanged = rowChanged;
const reload = index => { const reload = index => {
grid.selectedIndexes = [index]; grid.selectedIndexes = [index];
grid.scrollTop = index * grid.rowHeight; grid.scrollTop = index * (grid.rowHeight + 1);
rowChanged(index); rowChanged(index);
} }
buttonWrapper.append( buttonWrapper.append(
@@ -1457,7 +1789,19 @@ export class Grid {
const header = createElement('tr'); const header = createElement('tr');
thead.appendChild(header); thead.appendChild(header);
const sizer = this._var.refs.sizer; const sizer = this._var.refs.sizer;
let left = 0; let left = this.expandable ? ExpandableWidth : 0;
if (this.expandable) {
header.appendChild(createElement('th', th => {
th.className = 'ui-expandable sticky';
const w = `${ExpandableWidth}px`;
th.style.cssText = convertCssStyle({
'width': w,
'max-width': w,
'min-width': w,
'left': '0px'
});
}, createElement('div')));
}
for (let col of this.columns) { for (let col of this.columns) {
if (col.visible === false) { if (col.visible === false) {
const hidden = createElement('th', 'column'); const hidden = createElement('th', 'column');
@@ -1600,42 +1944,47 @@ export class Grid {
width += col.width + 1; width += col.width + 1;
} }
} }
if (width > 0) { if (this.expandable) {
table.style.width = `${width}px`; width += ExpandableWidth;
} }
table.style.width = `${width}px`;
// body content // 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)); body.addEventListener('dblclick', e => this._onRowDblClicked(e));
// this._adjustRows(); // this._adjustRows(body);
this._var.refs.body = body; this._var.refs.body = body;
// this.refresh(); // this.refresh();
return body; return body;
} }
_adjustRows() { _adjustRows(content) {
let count = this._var.rowCount; let count = this._var.rowCount;
if (isNaN(count) || count < 0 || !this.virtual) { if (isNaN(count) || count < 0 || !this.virtual) {
count = this._var.currentSource?.length ?? 0; count = this._var.currentSource?.length ?? 0;
} }
const cols = this.columns; const cols = this.columns;
const content = this._var.refs.body; const exists = content.querySelectorAll('&>.ui-grid-row').length;
const exists = content.children.length;
count -= exists; count -= exists;
if (count > 0) { if (count > 0) {
for (let i = 0; i < count; ++i) { for (let i = 0; i < count; ++i) {
const row = createElement('tr', 'ui-grid-row'); const row = createElement('tr', 'ui-grid-row');
let left = 0; let left = this.expandable ? ExpandableWidth : 0;
if (this.expandable) {
const icon = createIcon('fa-solid', 'caret-right');
icon.dataset.expanded = '0';
row.appendChild(createElement('td', td => {
td.className = 'ui-expandable sticky';
td.style.cssText = 'left: 0px';
td.addEventListener('mousedown', e => {
this._onExpandable(e, exists + i, row);
e.stopPropagation();
});
},
icon
));
}
cols.forEach((col, j) => { cols.forEach((col, j) => {
const cell = createElement('td'); const cell = createElement('td', 'ui-grid-cell');
if (col.visible !== false) { if (col.visible !== false) {
let style = this._get(col.key, 'style') ?? {}; let style = this._get(col.key, 'style') ?? {};
if (col.isfixed) { if (col.isfixed) {
@@ -1677,9 +2026,11 @@ export class Grid {
content.appendChild(row); content.appendChild(row);
} }
} else if (count < 0) { } else if (count < 0) {
for (let i = -1; i >= count; i -= 1) { let last = content.querySelectorAll('&>.ui-grid-row')[exists + count];
// content.removeChild(content.children[exists + i]); while (last != null) {
content.children[exists + i].remove(); const next = last.nextElementSibling;
last.remove();
last = next;
} }
} }
} }
@@ -1698,6 +2049,7 @@ export class Grid {
this._var.oldSelectedIndexes = selectedIndexes.slice(); this._var.oldSelectedIndexes = selectedIndexes.slice();
} }
} }
const offset = this.expandable ? 1 : 0;
[...rows].forEach((row, i) => { [...rows].forEach((row, i) => {
const vals = this._var.currentSource[startIndex + i]; const vals = this._var.currentSource[startIndex + i];
if (vals == null) { if (vals == null) {
@@ -1714,11 +2066,59 @@ export class Grid {
row.classList.remove('selected'); row.classList.remove('selected');
} }
// data // data
if (this.expandable) {
const expanded = vals.__expanded;
let rowExpanded = row.nextElementSibling;
if (rowExpanded?.className !== 'ui-grid-row-expanded') {
rowExpanded = null;
}
if (expanded) {
let expandableObject = vals.__expandable_object;
if (expandableObject == null && typeof this.expandableGenerator === 'function') {
expandableObject = this.expandableGenerator(item);
if (expandableObject?.element == null) {
return;
}
expandableObject.element = createElement('td', td => {
td.colSpan = cols.length + 2;
},
expandableObject.element
);
vals.__expandable_object = expandableObject;
}
if (rowExpanded == null) {
rowExpanded = createElement('tr', 'ui-grid-row-expanded');
this._var.refs.body.insertBefore(rowExpanded, row.nextElementSibling);
} else {
rowExpanded.style.display = '';
}
rowExpanded.replaceChildren(expandableObject.element);
} else {
if (rowExpanded != null) {
rowExpanded.style.display = 'none';
}
}
const iconCell = row.children[0];
if (iconCell.children[0].dataset.expanded !== (expanded ? '1' : '0')) {
const icon = createIcon('fa-solid', expanded ? 'caret-down' : 'caret-right');
icon.dataset.expanded = expanded ? '1' : '0';
iconCell.replaceChildren(icon);
if (expanded) {
if (typeof this.onRowExpanded === 'function') {
this.onRowExpanded(vals.values, vals.__expandable_object);
}
} else {
if (typeof this.onRowCollapsed === 'function') {
this.onRowCollapsed(vals.values, vals.__expandable_object);
}
}
}
}
cols.forEach((col, j) => { cols.forEach((col, j) => {
if (col.visible === false) { if (col.visible === false) {
return; return;
} }
const cell = row.children[j]; const cell = row.children[j + offset];
if (cell == null) { if (cell == null) {
return; return;
} }
@@ -1833,12 +2233,13 @@ export class Grid {
style.width = w; style.width = w;
style['max-width'] = w; style['max-width'] = w;
style['min-width'] = w; style['min-width'] = w;
const headerCells = this._var.refs.header.children; const headerCells = this._headerCells;
let element = headerCells[index]; let element = headerCells[index];
element.style.width = w; element.style.width = w;
element.style.maxWidth = w; element.style.maxWidth = w;
element.style.minWidth = w; element.style.minWidth = w;
let left = 0; // element.style.cssText += `width: ${w}; max-width: ${w}; min-width: ${w}`;
let left = this.expandable ? ExpandableWidth : 0;
if (col.isfixed) { if (col.isfixed) {
left = element.offsetLeft + width; left = element.offsetLeft + width;
let l = left; let l = left;
@@ -1851,15 +2252,17 @@ export class Grid {
} }
} }
} }
for (let row of this.tableRows) { const offset = this.expandable ? 1 : 0;
element = row.children[index]; for (let row of this._tableRows) {
element = row.children[index + offset];
if (element != null) { if (element != null) {
element.style.width = w; element.style.width = w;
element.style.maxWidth = w; element.style.maxWidth = w;
element.style.minWidth = w; element.style.minWidth = w;
// element.style.cssText += `width: ${w}; max-width: ${w}; min-width: ${w}`;
if (col.isfixed) { if (col.isfixed) {
let l = left; let l = left;
for (let i = index + 1; i < this.columns.length; ++i) { for (let i = index + offset + 1; i < this.columns.length; ++i) {
if (this.columns[i].isfixed) { if (this.columns[i].isfixed) {
row.children[i].style.left = `${l}px`; row.children[i].style.left = `${l}px`;
l += this.columns[i].width; l += this.columns[i].width;
@@ -1873,7 +2276,7 @@ export class Grid {
} }
_changingColumnOrder(index, offset, mouse, draggerCellLeft) { _changingColumnOrder(index, offset, mouse, draggerCellLeft) {
const children = this._var.refs.header.children; const children = this._headerCells;
let element = children[index]; let element = children[index];
this._var.refs.dragger.style.cssText = `left: ${element.offsetLeft - draggerCellLeft + offset}px; width: ${element.style.width}; display: block`; 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 = x + gridScrollLeft - element.offsetLeft; // getOffsetLeftFromWindow(element);
@@ -1945,9 +2348,10 @@ export class Grid {
return; return;
} }
const header = this._var.refs.header; const header = this._var.refs.header;
const children = header.children; const children = this._headerCells;
const rows = this.tableRows; const rows = this._tableRows;
const columns = this.columns; const columns = this.columns;
const offset = this.expandable ? 1 : 0;
if (targetIndex > 1) { if (targetIndex > 1) {
targetIndex = orderIndex - 1; targetIndex = orderIndex - 1;
// const current = columns[index]; // const current = columns[index];
@@ -1959,7 +2363,7 @@ export class Grid {
columns.splice(targetIndex, 0, current); columns.splice(targetIndex, 0, current);
header.insertBefore(children[index], children[targetIndex].nextElementSibling); header.insertBefore(children[index], children[targetIndex].nextElementSibling);
for (let row of rows) { for (let row of rows) {
row.insertBefore(row.children[index], row.children[targetIndex].nextElementSibling); row.insertBefore(row.children[index + offset], row.children[targetIndex + offset].nextElementSibling);
} }
} else { } else {
targetIndex = orderIndex; targetIndex = orderIndex;
@@ -1972,7 +2376,7 @@ export class Grid {
columns.splice(targetIndex, 0, current); columns.splice(targetIndex, 0, current);
header.insertBefore(children[index], children[targetIndex]); header.insertBefore(children[index], children[targetIndex]);
for (let row of rows) { for (let row of rows) {
row.insertBefore(row.children[index], row.children[targetIndex]); row.insertBefore(row.children[index + offset], row.children[targetIndex + offset]);
} }
} }
if (this.sortArray == null || this.sortArray.length === 0) { if (this.sortArray == null || this.sortArray.length === 0) {
@@ -2066,6 +2470,13 @@ export class Grid {
return [parent, target]; return [parent, target];
} }
_getParentElement(element) {
while (element != null && element.className !== 'ui-grid') {
element = element.parentElement;
}
return element;
}
_notHeader(tagName) { _notHeader(tagName) {
return /^(input|label|layer|svg|use)$/i.test(tagName); return /^(input|label|layer|svg|use)$/i.test(tagName);
} }
@@ -2243,7 +2654,11 @@ export class Grid {
if (typeof col.onFilterOk === 'function') { if (typeof col.onFilterOk === 'function') {
col.onFilterOk.call(this, col, array); col.onFilterOk.call(this, col, array);
} else { } else {
col.filterValues = array.map(a => a.DisplayValue); if (GridColumnTypeEnum.isCheckbox(col.type)) {
col.filterValues = array.map(a => a.Value);
} else {
col.filterValues = array.map(a => a.DisplayValue);
}
} }
this._var.colAttrs.__filtered = true; this._var.colAttrs.__filtered = true;
this._refreshSource(); this._refreshSource();
@@ -2287,8 +2702,9 @@ export class Grid {
const content = createElement('div', 'filter-content'); const content = createElement('div', 'filter-content');
content.style.top = `${rowHeight}px`; content.style.top = `${rowHeight}px`;
this._set(col.key, 'filterSource', array); this._set(col.key, 'filterSource', array);
const propKey = GridColumnTypeEnum.isCheckbox(col.type) ? 'Value' : 'DisplayValue';
for (let item of array) { for (let item of array) {
const v = Object.prototype.hasOwnProperty.call(item, 'DisplayValue') ? item.DisplayValue : item; const v = Object.prototype.hasOwnProperty.call(item, propKey) ? item[propKey] : item;
item.__checked = !Array.isArray(col.filterValues) || col.filterValues.includes(v); item.__checked = !Array.isArray(col.filterValues) || col.filterValues.includes(v);
} }
if (array.length > 12) { if (array.length > 12) {
@@ -2350,7 +2766,7 @@ export class Grid {
if (e.currentTarget.classList.contains('sticky')) { if (e.currentTarget.classList.contains('sticky')) {
return; return;
} }
const index = indexOfParent(e.currentTarget); const index = indexOfParent(e.currentTarget) - (this.expandable ? 1 : 0);
const cx = getClientX(e); const cx = getClientX(e);
const clearEvents = attr => { const clearEvents = attr => {
for (let event of ['mousemove', 'mouseup']) { for (let event of ['mousemove', 'mouseup']) {
@@ -2411,7 +2827,7 @@ export class Grid {
_onResizeStart(e, col) { _onResizeStart(e, col) {
const cx = getClientX(e); const cx = getClientX(e);
const width = col.width; const width = col.width;
const index = indexOfParent(e.currentTarget.parentElement); const index = indexOfParent(e.currentTarget.parentElement) - (this.expandable ? 1 : 0);
const window = this.window ?? global; const window = this.window ?? global;
const clearEvents = attr => { const clearEvents = attr => {
for (let event of ['mousemove', 'mouseup']) { for (let event of ['mousemove', 'mouseup']) {
@@ -2462,9 +2878,10 @@ export class Grid {
_onAutoResize(e, col) { _onAutoResize(e, col) {
const th = e.currentTarget.parentElement; const th = e.currentTarget.parentElement;
const index = indexOfParent(th); const index = indexOfParent(th);
const offset = this.expandable ? 1 : 0;
let width = th.querySelector('div:first-child').scrollWidth; let width = th.querySelector('div:first-child').scrollWidth;
for (let row of this.tableRows) { for (let row of this._tableRows) {
const element = row.children[index].children[0]; const element = row.children[index + offset].children[0];
const w = element.scrollWidth; const w = element.scrollWidth;
if (w > width) { if (w > width) {
width = w; width = w;
@@ -2475,9 +2892,9 @@ export class Grid {
} }
if (width > 0 && width !== col.width) { if (width > 0 && width !== col.width) {
width += 12; width += 12;
this._changeColumnWidth(index, width); this._changeColumnWidth(index - offset, width);
if (typeof this.onColumnChanged === 'function') { if (typeof this.onColumnChanged === 'function') {
this.onColumnChanged(ColumnChangedType.Resize, index, width); this.onColumnChanged(ColumnChangedType.Resize, index - offset, width);
} }
} }
} }
@@ -2524,7 +2941,8 @@ export class Grid {
this._scrollToTop(top); this._scrollToTop(top);
} }
_onBodyMouseMove(e, holder) { _onGridMouseMove(e, holder) {
e.stopPropagation();
if (e.target.classList.contains('ui-grid-hover-holder')) { if (e.target.classList.contains('ui-grid-hover-holder')) {
return; return;
} }
@@ -2537,6 +2955,10 @@ export class Grid {
} }
return; return;
} }
if (this._getParentElement(parent) !== this._var.el) {
// sub ui-grid
return;
}
const element = target.children[0]; const element = target.children[0];
if (element?.tagName !== 'SPAN') { if (element?.tagName !== 'SPAN') {
if (holder.classList.contains('active')) { if (holder.classList.contains('active')) {
@@ -2621,7 +3043,7 @@ export class Grid {
if (this.readonly !== true) { if (this.readonly !== true) {
this.refresh(); this.refresh();
} else { } else {
this.tableRows.forEach((row, i) => { this._tableRows.forEach((row, i) => {
if (selectedIndexes.includes(startIndex + i)) { if (selectedIndexes.includes(startIndex + i)) {
row.classList.add('selected'); row.classList.add('selected');
} else if (row.classList.contains('selected')) { } else if (row.classList.contains('selected')) {
@@ -2705,4 +3127,13 @@ export class Grid {
} }
} }
} }
_onExpandable(_e, index, _row) {
if (this._var.currentSource == null) {
return;
}
const vals = this._var.currentSource[this._var.startIndex + index];
vals.__expanded = !vals.__expanded
this.refresh();
}
} }