add: getText compatibility.

add: `AssetSelector` and `TemplateSelector`.
add: `popup-selector` style class.
add: `ui.resolvePopup` function.
add: `switch` in checkbox.
add: `GridColumn.filterTemplate` supports.
add: add `action` callback in `createIcon`.
change: replace `setTimeout(..., 0)` with `requestAnimationFrame`.
change: Popup result structure adjustment ({ result: any, popup: Popup }).
change: complete add work order flow.
change: reduce Popup title height.
fix: Grid column sort in number.
This commit is contained in:
2024-06-21 17:28:11 +08:00
parent 1a7aa1ab66
commit 5baf00de64
34 changed files with 1772 additions and 365 deletions

View File

@ -44,6 +44,14 @@ export class GridColumn {
* @type {boolean}
*/
/**
* 标记该类型是否支持列头批量操作
* @member
* @name GridColumn.headerEditing
* @readonly
* @type {boolean}
*/
/**
* 创建显示单元格时调用的方法
* @param {GridColumnDefinition} col - 列定义对象
@ -579,10 +587,12 @@ export class GridCheckboxColumn extends GridColumn {
/**
* @ignore
* @param {Function} trigger
* @param {GridColumnDefinition} col
* @returns {HTMLElement}
*/
static createEdit(trigger) {
static createEdit(trigger, col) {
const check = createCheckbox({
switch: col.switch,
onchange: trigger
});
return check;

View File

@ -9,11 +9,12 @@ import { setTooltip } from "../tooltip";
import { Popup, showAlert, showConfirm } from "../popup";
import { convertCssStyle } from "../extension";
import { GridColumn, GridInputColumn, GridTextColumn, GridDropdownColumn, GridCheckboxColumn, GridRadioboxColumn, GridIconColumn, GridDateColumn } from "./column";
import { requestAnimationFrame } from '../../ui';
/**
* @author Tsanie Lily <tsorgy@gmail.com>
* @license MIT
* @version 1.0.3
* @version 1.0.6
*/
const ScriptPath = (self.document == null ? self.location.href : self.document.currentScript?.src ?? '').replace(/ui\.min\.js\?.+$/, '');
@ -147,6 +148,14 @@ let r = lang;
* @this GridColumnDefinition
*/
/**
* 行数据过滤项模板回调函数
* @callback GridItemHtmlCallback
* @param {ValueItem} item - 行数据对象
* @returns {HTMLElement} 返回过滤项元素对象
* @this GridColumnDefinition
*/
/**
* 行数据字符串回调函数
* @callback GridItemStringCallback
@ -250,13 +259,15 @@ let r = lang;
* @property {GridItemObjectCallback} [styleFilter] - **已过时**<br/>_根据返回值填充单元格样式填充行列数据时读取_
* @property {(string | GridItemStringCallback)} [background] - 设置单元格背景色(填充行列数据时读取),支持直接设置颜色字符串或调用函数返回(若赋值则忽略 [bgFilter]{@linkcode GridColumnDefinition#bgFilter}
* @property {GridItemStringCallback} [bgFilter] - **已过时**<br/>_根据返回值设置单元格背景色_
* @property {KeyMap<Function>} [events] - 给单元格元素附加事件(事件函数上下文为数据行对象)
* @property {boolean} [switch=false] - 复选框为 `ui-switch` 样式 `@since 1.0.6`
* @property {(any | GridItemObjectCallback)} [attrs] - 根据返回值设置单元格元素的附加属性,允许直接设置对象也支持调用函数返回对象
* @property {KeyMap<Function>} [events] - 给单元格元素附加事件(事件函数上下文为数据行对象)
* @property {boolean} [allowFilter=false] - 是否允许进行列头过滤
* @property {any[]} [filterValues] - 过滤值的数组
* @property {boolean} [filterAllowNull=false] - 是否区分 `null` 与空字符串
* @property {(ValueItem[] | GridColumnFilterSourceCallback)} [filterSource] - 自定义列过滤器的数据源,支持调用函数返回数据源
* @property {boolean} [filterAsValue=false] - 列头过滤强制使用 `Value` 字段
* @property {GridItemHtmlCallback} [filterTemplate] - 列头过滤项的模板函数
* @property {GridItemSortCallback} [sortFilter] - 自定义列排序函数
* @property {boolean} [sortAsText=false] - 按照 `DisplayValue` 排序
* @property {DropdownOptions} [dropOptions] - 列为下拉列表类型时以该值设置下拉框的参数
@ -403,7 +414,7 @@ let r = lang;
/**
* 判断列是否始终编辑的回调函数
* @callback ColumnTypesEnumIsAlwaysEditing
* @param {number} type - 列类型
* @param {number | GridColumn} type - 列类型
* @returns {boolean} 返回是否始终编辑
*/
@ -432,7 +443,9 @@ const GridColumnTypeEnum = {
* 判断列是否为复选框列
* @type {ColumnTypesEnumIsAlwaysEditing}
*/
isAlwaysEditing(type) { return type === 3 || type === 7 }
isAlwaysEditing(type) {
return type?.headerEditing || type === 3 || type === 7;
}
};
/**
@ -1111,7 +1124,7 @@ export class Grid {
* @property {GridLanguages} [langs] - 多语言资源对象
* @property {number} [virtualCount=100] - 行数大于等于该值则启用虚模式
* @property {boolean} [autoResize=true] - 未设置宽度的列自动调整列宽
* @property {number} [rowHeight=36] - 表格行高
* @property {number} [rowHeight=36] - 表格行高,修改后同时需要在 `.ui-grid` 所在父容器重写 `--line-height` 的值以配合显示
* @property {number} [lineHeight=18] - 文本行高(多行文本列计算高度时使用)
* @property {string} [filterIcon=ellipsis-h] - 列头未过滤时的图标
* @property {string} [filteredIcon=filter] - 列头已过滤时的图标
@ -2030,13 +2043,15 @@ export class Grid {
}
}
source ??= [{ column: '', order: 'asc' }];
pop.create();
pop.rect = { width: 600, height: 460 };
pop.show(this._var.el).then(() => {
pop.container.style.cssText += 'width: 600px; height: 460px';
if (layout) {
const footer = pop.container.querySelector('.ui-popup-footer');
footer.insertBefore(createCheckbox({
label: this.langs.updateLayout,
className: 'ui-sort-layout',
switch: true,
enabled: false
}), footer.children[0]);
}
@ -2462,9 +2477,9 @@ export class Grid {
direction = 1;
}
const editing = col.sortAsText !== true;
return (a, b) => {
a = this._getItemSortProp(a.values, editing, col);
b = this._getItemSortProp(b.values, editing, col);
const comparer = (a, b) => {
a = this._getItemSortProp(a, editing, col);
b = this._getItemSortProp(b, editing, col);
if (editing) {
if (typeof a === 'boolean') {
a = a ? 2 : 1;
@ -2473,26 +2488,30 @@ export class Grid {
b = b ? 2 : 1;
}
if (a == null && typeof b === 'number') {
a = 0;
return (b >= 0 ? -1 : 1);
} else if (typeof a === 'number' && b == null) {
b = 0;
return (a >= 0 ? 1 : -1);
} else if (a == null && b != null) {
return -1;
} else if (a != null && b == null) {
return direction;
} else {
if (Array.isArray(a)) {
a = a.join(', ');
}
if (Array.isArray(b)) {
b = b.join(', ');
}
if (typeof a === 'string' && typeof b === 'string') {
a = a.toLowerCase();
b = b.toLowerCase();
}
return 1;
}
if (Array.isArray(a)) {
a = a.join(', ');
}
if (Array.isArray(b)) {
b = b.join(', ');
}
if (typeof a === 'string' && typeof b === 'string') {
a = a.toLowerCase();
b = b.toLowerCase();
}
} else {
if (a == null && b != null) {
return -1;
}
if (a != null && b == null) {
return direction;
return 1;
}
if (Array.isArray(a)) {
a = a.join(', ');
@ -2505,8 +2524,9 @@ export class Grid {
b = b.toLowerCase();
}
}
return a === b ? 0 : (a > b ? 1 : -1) * direction;
return a === b ? 0 : (a > b ? 1 : -1);
};
return (a, b) => comparer(a.values, b.values) * direction;
}
return (a, b) => col.sortFilter(a.values, b.values) * direction;
}
@ -2670,6 +2690,7 @@ export class Grid {
th.appendChild(wrapper);
if (!readonly && col.enabled !== false && col.allcheck && alwaysEditing) {
const check = createCheckbox({
switch: col.switch,
onchange: e => this._onColumnAllChecked(col, e.target.checked)
});
wrapper.appendChild(check);
@ -3625,11 +3646,14 @@ export class Grid {
/**
* @private
* @param {string} tagName
* @param {HTMLElement} e
* @returns {boolean}
*/
_notHeader(tagName) {
return /^(input|label|layer|svg|use)$/i.test(tagName);
_notHeader(e) {
if (e.parentElement.classList.contains('ui-switch')) {
return true;
}
return /^(input|label|layer|svg|use)$/i.test(e.tagName);
}
/**
@ -3642,7 +3666,7 @@ export class Grid {
if (!force && (this._get(col.key, 'resizing') || this._get(col.key, 'dragging'))) {
return;
}
if (!this._notHeader(e.target.tagName)) {
if (!this._notHeader(e.target)) {
if (Array.isArray(this.sortArray) && this.sortArray.length > 0) {
showConfirm(this.langs.sort, this.langs.sortArrayExists, [
{
@ -3652,8 +3676,8 @@ export class Grid {
{
text: this.langs.cancel
}
]).then(result => {
if (result?.key === 'yes') {
]).then(r => {
if (r.result === 'yes') {
const sortCol = this.sortArray.find(c => c.column === col.key);
this.sortDirection = sortCol?.order === 'asc' ? -1 : 1;
this._onDoHeaderSort(col);
@ -3790,22 +3814,27 @@ export class Grid {
if (!Object.hasOwnProperty.call(dict, display)) {
dict[display] = {
Value: vals[i],
DisplayValue: display
DisplayValue: display,
RowItem: item.values
};
}
});
} else if (!Object.hasOwnProperty.call(dict, displayValue)) {
dict[displayValue] = {
Value: this._getItemProp(item.values, true, col),
DisplayValue: displayValue
DisplayValue: displayValue,
RowItem: item.values
};
}
}
const filterAsValue = col.filterAsValue;
const type = this._var.colTypes[col.key];
const isDateColumn = type === GridDateColumn || type instanceof GridDateColumn;
array = Object.values(dict)
.sort((itemA, itemB) => {
array = Object.values(dict);
if (typeof col.sortFilter === 'function') {
array.sort((a, b) => col.sortFilter(a.RowItem, b.RowItem));
} else {
const type = this._var.colTypes[col.key];
const isDateColumn = type === GridDateColumn || type instanceof GridDateColumn;
const filterAsValue = col.filterAsValue;
array.sort((itemA, itemB) => {
let a = itemA.Value;
let b = itemB.Value;
if (a instanceof Date || b instanceof Date) {
@ -3828,6 +3857,7 @@ export class Grid {
}
return a > b ? 1 : (a < b ? -1 : 0);
});
}
}
array = array.map(i => {
if (Object.prototype.hasOwnProperty.call(i, 'Value') &&
@ -3907,7 +3937,7 @@ export class Grid {
panel.appendChild(functions);
this._var.el.appendChild(panel);
setTimeout(() => panel.classList.add('active'), 0);
requestAnimationFrame(() => panel.classList.add('active'));
this._var.colAttrs.__filtering = filter;
filter.classList.add('hover');
}
@ -3943,24 +3973,32 @@ export class Grid {
if (array.length > 12) {
array = array.slice(0, 12);
}
this._doFillFilterList(content, array, all);
this._doFillFilterList(col, content, array, all);
list.append(holder, content);
}
/**
* @private
* @param {GridColumnDefinition} col
* @param {HTMLDivElement} content
* @param {ValueItem[]} array
* @param {HTMLDivElement} all
*/
_doFillFilterList(content, array, all) {
_doFillFilterList(col, content, array, all) {
for (let item of array) {
const div = createElement('div', 'filter-item');
const display = Object.prototype.hasOwnProperty.call(item, 'DisplayValue') ? item.DisplayValue : item;
const title = Object.prototype.hasOwnProperty.call(item, 'DisplayValue') ? item.DisplayValue : item;
let display;
if (typeof col.filterTemplate === 'function') {
display = col.filterTemplate(item);
}
if (display == null) {
display = title && String(title).replace(/( |\r\n|\n|<br[ \t]*\/?>)/g, '\u00a0');
}
div.appendChild(createCheckbox({
checked: item.__checked,
label: display && String(display).replace(/( |\r\n|\n|<br[ \t]*\/?>)/g, '\u00a0'),
title: display,
label: display,
title,
onchange: e => {
item.__checked = e.target.checked;
all.querySelector('input').checked = ![...content.querySelectorAll('input')].some(i => !i.checked);
@ -4001,7 +4039,7 @@ export class Grid {
}
const content = list.querySelector('.filter-content');
content.replaceChildren();
this._doFillFilterList(content, array, list.querySelector('.filter-all'));
this._doFillFilterList(col, content, array, list.querySelector('.filter-all'));
content.style.top = `${top + rowHeight}px`;
}
}
@ -4012,7 +4050,7 @@ export class Grid {
* @param {GridColumnDefinition} col
*/
_onDragStart(e, col) {
if (this._notHeader(e.target.tagName)) {
if (this._notHeader(e.target)) {
return;
}
if (e.currentTarget.classList.contains('sticky')) {
@ -4066,7 +4104,7 @@ export class Grid {
if (attr.offset == null) {
delete attr.dragging;
} else {
setTimeout(() => {
requestAnimationFrame(() => {
delete attr.dragging;
delete attr.offset;
});
@ -4116,7 +4154,7 @@ export class Grid {
clearEvents(attr);
const width = attr.resizing;
if (width != null) {
setTimeout(() => delete attr.resizing);
requestAnimationFrame(() => delete attr.resizing);
if (attr.sizing) {
delete attr.sizing;
delete attr.autoResize;