This commit is contained in:
2024-02-20 14:31:24 +08:00
parent 3d2c0e7a97
commit 45205cd63d
3 changed files with 454 additions and 110 deletions

View File

@ -6,6 +6,36 @@ import { createCheckbox } from "../checkbox";
import { Dropdown } from "../dropdown";
import { convertCssStyle } from "../extension";
import { createDateInput, formatDate, setDateValue, getDateValue } from "../date";
// definition
import { DropdownOptions, GridItemBooleanCallback, GridDropdownSourceCallback, DateFormatterCallback, GridSourceItem, GridItemWrapper, Grid } from "./grid";
/**
* @ignore
* @callback DropExpandedCallback
* @param {Map<string, ValueItem>} item - 行数据对象
* @param {Dropdown} drop - 下拉框对象
* @this GridColumnDefinition
*/
/**
* 列定义接口
* @ignore
* @typedef GridColumnDefinition
* @property {string} key - 列关键字,默认以该关键字从行数据中提取单元格值,行数据的关键字属性值里包含 DisplayValue 则优先显示此值
* @property {(boolean | string | GridItemBooleanCallback)} [enabled] - 列是否可用(可编辑),允许以下类型
*
* * `boolean` 则直接使用该值
* * `string` 则以该值为关键字从行数据中取值作为判断条件
* * `GridItemBooleanCallback` 则调用如下回调,以返回值作为判断条件
* @property {DropdownOptions} [dropOptions] - 列为下拉列表类型时以该值设置下拉框的参数
* @property {(GridSourceItem[] | Promise<GridSourceItem[]> | GridDropdownSourceCallback)} [source] - 列为下拉列表类型时以该值设置下拉列表数据源,支持返回异步对象,也支持如下函数返回
* @property {boolean} [sourceCache=false] - 下拉列表数据源是否缓存结果即行数据未发生变化时仅从source属性获取一次值
* @property {("fa-light" | "fa-regular" | "fa-solid")} [iconType=fa-light] - 列为图标类型时以该值设置图标样式
* @property {string} [dateMin] - 列为日期类型时以该值作为最小可选日期值
* @property {string} [dateMax] - 列为日期类型时以该值作为最大可选日期值
* @property {DateFormatterCallback} [dateValueFormatter] - 列为日期类型时自定义日期格式化函数
* @property {DropExpandedCallback} [onDropExpanded] - 列为下拉框类型时在下拉列表展开时触发的事件
*/
/**
* 列定义基类
@ -14,6 +44,7 @@ import { createDateInput, formatDate, setDateValue, getDateValue } from "../date
* <img src="./assets/column-refresh.jpg" alt="Column Refresh"/>
* @class
* @static
* @hideconstructor
*/
export class GridColumn {
/**
@ -49,7 +80,7 @@ export class GridColumn {
* @param {Function} trigger - 编辑事件回调函数,`e` 参数会传递给 [getValue]{@linkcode GridColumn.getValue} 方法
* @param {GridColumnDefinition} col - 列定义对象
* @param {HTMLElement} container - 父容器元素
* @param {GridItemWrapper} vals - 行包装对象,其 `values` 属性为行数据对象
* @param {GridItemWrapper} wrapper - 行包装对象,其 `values` 属性为行数据对象
* @returns {HTMLElement} 返回创建的编辑状态的单元格元素
* @virtual
*/
@ -67,7 +98,7 @@ export class GridColumn {
* 获取编辑状态单元格值时调用的方法
* @method
* @name GridColumn.getValue
* @param {any} `e` 由 [createEdit]{@linkcode GridColumn.createEdit} 方法中 `trigger` 函数传递来的对象
* @param {any} e - 由 [createEdit]{@linkcode GridColumn.createEdit} 方法中 `trigger` 函数传递来的对象
* @param {GridColumnDefinition} col - 列定义对象
* @returns {(string | boolean | number)} 返回单元格的值
* @virtual
@ -77,7 +108,7 @@ export class GridColumn {
* 设置单元格值时调用的方法
* @param {HTMLElement} element - 单元格元素
* @param {(string | boolean | number)} val - 待设置的单元格值
* @param {GridItemWrapper} vals - 行包装对象
* @param {GridItemWrapper} wrapper - 行包装对象
* @param {GridColumnDefinition} col - 列定义对象
* @param {Grid} grid - Grid 对象
* @virtual
@ -132,8 +163,7 @@ export class GridColumn {
*/
/**
* 复写 [toString]{@linkcode String#toString} 方法
* @private
* @ignore
*/
static toString() { return '[object Column]' }
}
@ -141,29 +171,44 @@ export class GridColumn {
/**
* 单行文本输入列
* @class
* @static
* @extends GridColumn
* @hideconstructor
*/
export class GridInputColumn extends GridColumn {
static get editing() { return true };
static createEdit(trigger, col, _wrapper, vals) {
/**
* @ignore
* @param {Function} trigger
* @param {GridColumnDefinition} col
* @param {HTMLElement} _container
* @param {GridItemWrapper} wrapper
* @returns {HTMLElement}
*/
static createEdit(trigger, col, _container, wrapper) {
const input = createElement('input');
input.setAttribute('type', 'text');
if (typeof trigger === 'function') {
input.addEventListener('change', trigger);
}
input.addEventListener('input', () => {
if (vals.__editing == null) {
vals.__editing = {
if (wrapper.__editing == null) {
wrapper.__editing = {
[col.key]: true
}
} else {
vals.__editing[col.key] = true;
wrapper.__editing[col.key] = true;
}
});
return input;
}
/**
* @ignore
* @param {HTMLElement} element
* @param {string} val
*/
static setValue(element, val) {
if (element.tagName !== 'INPUT') {
super.setValue(element, val);
@ -172,33 +217,67 @@ export class GridInputColumn extends GridColumn {
}
}
/**
* @ignore
* @param {Event} e
* @returns {string}
*/
static getValue(e) { return e.target.value }
/**
* @ignore
* @param {HTMLElement} element
* @param {boolean} enabled
*/
static setEnabled(element, enabled) {
super.setEnabled(element, enabled);
element.disabled = enabled === false;
}
}
/**
* 多行文本输入列
* @class
* @static
* @extends GridInputColumn
* @hideconstructor
* @ignore
*/
export class GridTextColumn extends GridInputColumn {
static createEdit(trigger, col, _wrapper, vals) {
/**
* @ignore
* @param {Function} trigger
* @param {GridColumnDefinition} col
* @param {HTMLElement} _container
* @param {GridItemWrapper} wrapper
* @returns {HTMLElement}
*/
static createEdit(trigger, col, _container, wrapper) {
const input = createElement('textarea');
if (typeof trigger === 'function') {
input.addEventListener('change', trigger);
}
input.addEventListener('input', () => {
if (vals.__editing == null) {
vals.__editing = {
if (wrapper.__editing == null) {
wrapper.__editing = {
[col.key]: true
}
} else {
vals.__editing[col.key] = true;
wrapper.__editing[col.key] = true;
}
});
return input;
}
static setValue(element, val, _item, _col, grid) {
/**
* @ignore
* @param {HTMLElement} element
* @param {string} val
* @param {GridItemWrapper} _wrapper
* @param {GridColumnDefinition} _col
* @param {Grid} grid
*/
static setValue(element, val, _wrapper, _col, grid) {
if (element.tagName !== 'TEXTAREA') {
super.setValue(element, val);
} else {
@ -217,31 +296,49 @@ const SymbolDropdown = Symbol.for('ui-dropdown');
/**
* 下拉选择列
* @class
* @static
* @extends GridColumn
* @hideconstructor
*/
export class GridDropdownColumn extends GridColumn {
static createEdit(trigger, col, container, it) {
/**
* @ignore
* @param {Function} trigger
* @param {GridColumnDefinition} col
* @param {HTMLElement} container
* @param {GridItemWrapper} wrapper
* @returns {HTMLElement}
*/
static createEdit(trigger, col, container, wrapper) {
const drop = new Dropdown({
...col.dropOptions,
wrapper: container.parentElement
});
drop.onSelected = trigger;
drop.onExpanded = () => {
if (it.__editing == null) {
it.__editing = {
if (wrapper.__editing == null) {
wrapper.__editing = {
[col.key]: true
}
} else {
it.__editing[col.key] = true;
wrapper.__editing[col.key] = true;
}
if (typeof col.onDropExpanded === 'function') {
col.onDropExpanded.call(col, it.values, drop);
col.onDropExpanded.call(col, wrapper.values, drop);
}
};
return drop.create();
}
/**
* @private
* @param {HTMLElement} element
* @returns {Dropdown}
*/
static _getDrop(element) {
/**
* @type {Map<string, Dropdown>}
*/
const dropGlobal = global[SymbolDropdown];
if (dropGlobal == null) {
return null;
@ -256,32 +353,39 @@ export class GridDropdownColumn extends GridColumn {
/**
* @private
* @param {Map<string, ValueItem>} item
* @param {GridItemWrapper} wrapper
* @param {GridColumnDefinition} col
* @returns {GridSourceItem[]}
*/
static _getSource(item, col) {
static _getSource(wrapper, col) {
let source;
if (col.sourceCache !== false) {
source = item.source?.[col.key];
source = wrapper.source?.[col.key];
if (source != null) {
return source;
}
}
source = col.source;
if (typeof source === 'function') {
source = source(item.values);
source = source(wrapper.values);
}
if (col.sourceCache !== false) {
if (item.source == null) {
item.source = { [col.key]: source };
if (wrapper.source == null) {
wrapper.source = { [col.key]: source };
} else {
item.source[col.key] = source;
wrapper.source[col.key] = source;
}
}
return source;
}
/**
* @private
* @param {GridSourceItem[]} source
* @param {HTMLElement} element
* @param {any} val
* @param {DropdownOptions} opts
*/
static _setValue(source, element, val, opts) {
const data = source?.find(v => v[opts?.valueKey ?? 'value'] === val);
if (data != null) {
@ -290,9 +394,16 @@ export class GridDropdownColumn extends GridColumn {
super.setValue(element, val);
}
static setValue(element, val, item, col) {
/**
* @ignore
* @param {HTMLElement} element
* @param {any} val
* @param {GridItemWrapper} wrapper
* @param {GridColumnDefinition} col
*/
static setValue(element, val, wrapper, col) {
if (element.tagName !== 'DIV') {
let source = this._getSource(item, col);
let source = this._getSource(wrapper, col);
if (source instanceof Promise) {
source.then(s => this._setValue(s, element, val, col.dropOptions));
} else {
@ -305,7 +416,7 @@ export class GridDropdownColumn extends GridColumn {
return;
}
if (drop.source == null || drop.source.length === 0) {
let source = this._getSource(item, col);
let source = this._getSource(wrapper, col);
if (source instanceof Promise) {
source.then(s => {
drop.source = s;
@ -319,10 +430,21 @@ export class GridDropdownColumn extends GridColumn {
drop.select(val, true);
}
/**
* @ignore
* @param {GridSourceItem} e
* @param {GridColumnDefinition} col
* @returns {any}
*/
static getValue(e, col) {
return e[col.dropOptions?.valueKey ?? 'value'];
}
/**
* @ignore
* @param {HTMLElement} element
* @param {string} name
*/
static setClass(element, name) {
if (element.tagName === 'DIV') {
element.className = `ui-drop-wrapper ${name ?? ''}`;
@ -331,6 +453,11 @@ export class GridDropdownColumn extends GridColumn {
}
}
/**
* @ignore
* @param {HTMLElement} element
* @param {boolean} enabled
*/
static setEnabled(element, enabled) {
super.setEnabled(element, enabled);
const drop = this._getDrop(element);
@ -340,6 +467,11 @@ export class GridDropdownColumn extends GridColumn {
drop.disabled = enabled === false;
}
/**
* @ignore
* @param {HTMLElement} element
* @param {HTMLElement} container
*/
static leaveEdit(element, container) {
container.parentElement.querySelectorAll('.ui-drop-box.active').forEach(e => {
if (e != null) {
@ -356,7 +488,20 @@ export class GridDropdownColumn extends GridColumn {
}
}
/**
* 复选框列
* @class
* @static
* @extends GridColumn
* @hideconstructor
* @ignore
*/
export class GridCheckboxColumn extends GridColumn {
/**
* @ignore
* @param {Function} trigger
* @returns {HTMLElement}
*/
static createEdit(trigger) {
const check = createCheckbox({
onchange: typeof trigger === 'function' ? trigger : null
@ -364,12 +509,27 @@ export class GridCheckboxColumn extends GridColumn {
return check;
}
/**
* @ignore
* @param {HTMLElement} element
* @param {boolean} val
*/
static setValue(element, val) {
element.querySelector('input').checked = val;
}
/**
* @ignore
* @param {Event} e
* @returns {boolean}
*/
static getValue(e) { return e.target.checked }
/**
* @ignore
* @param {HTMLElement} element
* @param {string} name
*/
static setClass(element, name) {
if (element.tagName === 'LABEL') {
element.className = `ui-check-wrapper ${name ?? ''}`;
@ -378,19 +538,43 @@ export class GridCheckboxColumn extends GridColumn {
}
}
/**
* @ignore
* @param {HTMLElement} element
* @param {boolean} enabled
*/
static setEnabled(element, enabled) {
super.setEnabled(element, enabled);
element.querySelector('input').disabled = enabled === false;
}
}
/**
* 图标列
* @class
* @static
* @extends GridColumn
* @hideconstructor
* @ignore
*/
export class GridIconColumn extends GridColumn {
/**
* @ignore
* @returns {HTMLElement}
*/
static create() { return createElement('span', 'col-icon') }
static setValue(element, val, item, col) {
/**
* @ignore
* @param {HTMLElement} element
* @param {string} val
* @param {GridItemWrapper} wrapper
* @param {GridColumnDefinition} col
*/
static setValue(element, val, wrapper, col) {
// let className = col.iconClassName;
// if (typeof className === 'function') {
// className = className.call(col, item.values);
// className = className.call(col, wrapper.values);
// }
// if (className == null) {
// element.className = 'col-icon';
@ -399,7 +583,7 @@ export class GridIconColumn extends GridColumn {
// }
let type = col.iconType;
if (typeof type === 'function') {
type = type.call(col, item.values);
type = type.call(col, wrapper.values);
}
type ??= 'fa-light';
if (element.dataset.type !== type || element.dataset.icon !== val) {
@ -412,10 +596,20 @@ export class GridIconColumn extends GridColumn {
}
}
/**
* @ignore
* @param {HTMLElement} element
* @param {string} name
*/
static setClass(element, name) {
element.className = `col-icon ${name ?? ''}`;
}
/**
* @ignore
* @param {HTMLElement} element
* @param {boolean} enabled
*/
static setEnabled(element, enabled) {
super.setEnabled(element, enabled);
if (enabled === false) {
@ -426,41 +620,72 @@ export class GridIconColumn extends GridColumn {
}
}
/**
* 日期选择列
* @class
* @static
* @extends GridColumn
* @hideconstructor
*/
export class GridDateColumn extends GridColumn {
static get editing() { return true };
static createEdit(trigger, col, _container, vals) {
/**
* @ignore
* @param {Function} trigger
* @param {GridColumnDefinition} col
* @param {HTMLElement} _container
* @param {GridItemWrapper} wrapper
* @returns {HTMLElement}
*/
static createEdit(trigger, col, _container, wrapper) {
let enabled = col.enabled;
if (typeof enabled === 'string') {
enabled = vals.values[enabled];
enabled = wrapper.values[enabled];
} else if (typeof enabled === 'function') {
enabled = col.enabled(vals.values);
enabled = col.enabled(wrapper.values);
}
if (enabled === false) {
return super.create();
}
const date = createDateInput(col.dateMin, col.dateMax);
date.addEventListener('change', () => {
if (vals.__editing == null) {
vals.__editing = {
if (wrapper.__editing == null) {
wrapper.__editing = {
[col.key]: true
}
} else {
vals.__editing[col.key] = true;
wrapper.__editing[col.key] = true;
}
});
date.addEventListener('blur', trigger);
return date;
}
/**
* @ignore
* @param {HTMLElement} element
* @param {(string | number | Date)} val
*/
static setValue(element, val) {
setDateValue(element, val);
}
/**
* @ignore
* @param {Event} e
* @param {GridColumnDefinition} col
* @returns {string}
*/
static getValue(e, col) {
return getDateValue(e.target, col.dateValueFormatter);
}
/**
* @ignore
* @param {HTMLElement} element
* @param {string} name
*/
static setClass(element, name) {
if (element.tagName === 'INPUT') {
element.className = `ui-date-cell ${name ?? ''}`;
@ -469,10 +694,26 @@ export class GridDateColumn extends GridColumn {
}
}
/**
* @ignore
* @param {HTMLElement} element
* @param {boolean} enabled
*/
static setEnabled(element, enabled) {
element.disabled = enabled === false;
}
/**
* 格式化日期字符串
* @param {(string | number | Date)} date - 要格式化的日期值
*
* 支持以下几种数据类型
* * `"2024-01-26"`
* * `"1/26/2024"`
* * `"638418240000000000"`
* * `new Date('2024-01-26')`
* @returns {string} 格式化为 M/d/yyyy 的日期字符串
*/
static formatDate(date) {
return formatDate(date);
}