add internal sort panel.
This commit is contained in:
parent
984496e08e
commit
ac605895c5
@ -297,7 +297,7 @@ export function getMessageStatus(comm, r, _var) {
|
||||
let statusTips;
|
||||
if (statusUpdatable !== false || ls.length > 1) {
|
||||
statusTips = createElement('div', tip => {
|
||||
for (let i = 0; i < msgs.length; i++) {
|
||||
for (let i = 0; i < msgs.length; ++i) {
|
||||
tip.appendChild(createElement('div', t => {
|
||||
const p = msgs[i];
|
||||
if (statusUpdatable !== false && p.StatusChanged) {
|
||||
|
@ -546,6 +546,61 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ui-sort-panel-content {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
>.ui-sort-panel-buttons {
|
||||
flex: 0 0 auto;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
|
||||
>button {
|
||||
margin-right: 6px;
|
||||
border: none;
|
||||
line-height: 28px;
|
||||
color: var(--title-color);
|
||||
border-radius: var(--corner-radius);
|
||||
padding: 0 10px;
|
||||
box-sizing: border-box;
|
||||
height: 28px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
background-color: var(--title-bg-color);
|
||||
transition: opacity .12s ease;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
&:hover {
|
||||
opacity: .8;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: .6;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
>svg {
|
||||
flex: 0 0 auto;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
fill: var(--title-color);
|
||||
}
|
||||
|
||||
>span {
|
||||
flex: 1 1 auto;
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
>.ui-sort-panel-grid {
|
||||
flex: 1 1 auto;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*@media (prefers-color-scheme: dark) {
|
||||
|
14
lib/ui/dropdown.d.ts
vendored
14
lib/ui/dropdown.d.ts
vendored
@ -5,19 +5,19 @@ export interface DropdownItem {
|
||||
/** 显示文本 */
|
||||
text: string;
|
||||
/** 源码显示内容 */
|
||||
html?: HTMLElement
|
||||
html?: HTMLElement | string;
|
||||
}
|
||||
|
||||
/** 下拉框选项接口 */
|
||||
export interface DropdownOptions {
|
||||
/** 文本关键字,默认值 text */
|
||||
/** 文本关键字,默认值 `text` */
|
||||
textKey?: string;
|
||||
/** 值关键字,默认值 value */
|
||||
/** 值关键字,默认值 `value` */
|
||||
valueKey?: string;
|
||||
/** 源码显示的关键字,默认值 html */
|
||||
/** 源码显示的关键字,默认值 `html` */
|
||||
htmlKey?: string;
|
||||
/** 最大输入长度,默认值 500 */
|
||||
maxLength?: Number;
|
||||
/** 最大输入长度,默认值 `500` */
|
||||
maxLength?: number;
|
||||
/** 是否允许多选 */
|
||||
multiSelect?: boolean;
|
||||
/** 选中值 */
|
||||
@ -35,7 +35,7 @@ export interface DropdownOptions {
|
||||
/** 搜索提示文本,默认值取语言资源 `searchHolder` "Search..." */
|
||||
searchPlaceholder?: string;
|
||||
/** 焦点索引 */
|
||||
tabIndex?: Number;
|
||||
tabIndex?: number;
|
||||
/** 输入框的提示文本 */
|
||||
placeholder?: string;
|
||||
/** 是否固定为向下展开 */
|
||||
|
25
lib/ui/grid/column.d.ts
vendored
25
lib/ui/grid/column.d.ts
vendored
@ -30,7 +30,7 @@ export interface GridColumnDefinition {
|
||||
/** 列标题的元素样式 */
|
||||
captionStyle?: { [key: string]: string };
|
||||
/** 大于 0 则设置为该宽度,否则根据列内容自动调整列宽 */
|
||||
width?: Number;
|
||||
width?: number;
|
||||
/** 列对齐方式 */
|
||||
align?: "left" | "center" | "right";
|
||||
/**
|
||||
@ -89,6 +89,8 @@ export interface GridColumnDefinition {
|
||||
dateMin?: string;
|
||||
/** 列为日期类型时以该值作为最大可选日期值 */
|
||||
dateMax?: string;
|
||||
/** 列为日期类型时自定义日期转字符串函数 */
|
||||
dateValueFormatter?: (date: Date) => string;
|
||||
/** 以返回值额外设置单元格的tooltip(函数上下文为列定义对象) */
|
||||
tooltip?: string | ((item: GridItem) => string);
|
||||
|
||||
@ -109,7 +111,7 @@ export interface GridColumnDefinition {
|
||||
* @param e 列修改事件传递过来的任意对象
|
||||
* @eventProperty
|
||||
*/
|
||||
onChanged?: (this: Grid, item: GridItem, value: boolean | string | Number, oldValue: boolean | string | Number, e?: any) => void;
|
||||
onChanged?: (this: Grid, item: GridItem, value: boolean | string | number, oldValue: boolean | string | number, e?: any) => void;
|
||||
/**
|
||||
* 文本单元格在输入完成时触发的事件
|
||||
* @param this 上下文为 Grid 对象
|
||||
@ -145,6 +147,8 @@ export interface GridColumnDefinition {
|
||||
|
||||
/** 列定义基类 */
|
||||
export class GridColumn {
|
||||
/** @ignore */
|
||||
constructor();
|
||||
/**
|
||||
* 创建显示单元格时调用的方法
|
||||
* @param col 列定义对象
|
||||
@ -180,7 +184,7 @@ export class GridColumn {
|
||||
* @param grid {@linkcode Grid} 对象
|
||||
* @virtual
|
||||
*/
|
||||
static setValue(element: HTMLElement, val: string | boolean | Number, vals: GridItemWrapper, col: GridColumnDefinition, grid: Grid): void;
|
||||
static setValue(element: HTMLElement, val: string | boolean | number, vals: GridItemWrapper, col: GridColumnDefinition, grid: Grid): void;
|
||||
/**
|
||||
* 获取编辑状态单元格值时调用的方法
|
||||
* @param e 由 {@linkcode createEdit} 方法中 `trigger` 函数传递来的对象
|
||||
@ -188,7 +192,7 @@ export class GridColumn {
|
||||
* @returns 返回单元格的值
|
||||
* @virtual
|
||||
*/
|
||||
static getValue(e: any, col: GridColumnDefinition): string | boolean | Number;
|
||||
static getValue(e: any, col: GridColumnDefinition): string | boolean | number;
|
||||
/**
|
||||
* 设置单元格样式时调用的方法
|
||||
* @param element 单元格元素
|
||||
@ -336,15 +340,22 @@ export class GridDateColumn extends GridColumn {
|
||||
*/
|
||||
static createEdit(trigger: (e: any) => void, col: GridColumnDefinition, container: HTMLElement, vals: GridItemWrapper): HTMLElement;
|
||||
/**
|
||||
* @inheritdoc GridColumn.setValue
|
||||
* 设置单元格值时调用的方法<br/><br/>
|
||||
* 支持以下几种数据类型<br/><br/>
|
||||
* `"2024-01-26"`<br/>
|
||||
* `"1/26/2024"`<br/>
|
||||
* `"638418240000000000"`<br/>
|
||||
* `new Date('2024-01-26')`<br/>
|
||||
* @param element 单元格元素
|
||||
* @param val 待设置的单元格值
|
||||
* @override
|
||||
*/
|
||||
static setValue(element: HTMLElement, val: string | Number): void;
|
||||
static setValue(element: HTMLElement, val: string | number): void;
|
||||
/**
|
||||
* @inheritdoc GridColumn.getValue
|
||||
* @override
|
||||
*/
|
||||
static getValue(e: any): string | Number;
|
||||
static getValue(e: any): string | number;
|
||||
/**
|
||||
* @inheritdoc GridColumn.setEnabled
|
||||
* @override
|
||||
|
@ -111,7 +111,7 @@ export class GridDropdownColumn extends GridColumn {
|
||||
wrapper: container.parentElement
|
||||
});
|
||||
drop.onSelected = trigger;
|
||||
drop.onExpanded = function () {
|
||||
drop.onExpanded = () => {
|
||||
if (it.__editing == null) {
|
||||
it.__editing = {
|
||||
[col.key]: true
|
||||
@ -161,10 +161,10 @@ export class GridDropdownColumn extends GridColumn {
|
||||
return source;
|
||||
}
|
||||
|
||||
static _setValue(source, element, val) {
|
||||
const data = source?.find(v => v.value === val);
|
||||
static _setValue(source, element, val, opts) {
|
||||
const data = source?.find(v => v[opts?.valueKey ?? 'value'] === val);
|
||||
if (data != null) {
|
||||
val = data.text;
|
||||
val = data[opts?.textKey ?? 'text'];
|
||||
}
|
||||
super.setValue(element, val);
|
||||
}
|
||||
@ -173,9 +173,9 @@ export class GridDropdownColumn extends GridColumn {
|
||||
if (element.tagName !== 'DIV') {
|
||||
let source = this._getSource(item, col);
|
||||
if (source instanceof Promise) {
|
||||
source.then(s => this._setValue(s, element, val));
|
||||
source.then(s => this._setValue(s, element, val, col.dropOptions));
|
||||
} else {
|
||||
this._setValue(source, element, val);
|
||||
this._setValue(source, element, val, col.dropOptions);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -312,26 +312,35 @@ export class GridDateColumn extends GridColumn {
|
||||
|
||||
static setValue(element, val) {
|
||||
if (element.tagName === 'INPUT') {
|
||||
if (isNaN(val) || /^\d{4}-\d{2}-\d{2}$/.test(val)) {
|
||||
element.value = val;
|
||||
if (isNaN(val)) {
|
||||
if (/^\d{4}-\d{2}-\d{2}$/.test(val)) {
|
||||
element.value = val;
|
||||
} else if (/^\d{1,2}\/\d{1,2}\/\d{4}$/.test(val)) {
|
||||
element.value = this._toDateValue(new Date(val));
|
||||
} else {
|
||||
element.value = '';
|
||||
}
|
||||
} else {
|
||||
val = new Date((val - 621355968e9) / 1e4);
|
||||
const month = String(val.getMonth() + 1).padStart(2, '0');
|
||||
const date = String(val.getDate()).padStart(2, '0');
|
||||
element.value = `${val.getFullYear()}-${month}-${date}`;
|
||||
if (!(val instanceof Date)) {
|
||||
val = new Date((val - 621355968e9) / 1e4);
|
||||
}
|
||||
element.value = this._toDateValue(val);
|
||||
}
|
||||
} else {
|
||||
element.innerText = this.formatDate(val);
|
||||
}
|
||||
}
|
||||
|
||||
static getValue(e) {
|
||||
static getValue(e, col) {
|
||||
const date = e.target?.valueAsDate;
|
||||
if (date instanceof Date && !isNaN(date)) {
|
||||
const year = date.getFullYear();
|
||||
if (year < 1900 || year > 9999) {
|
||||
return '';
|
||||
}
|
||||
if (typeof col.dateValueFormatter === 'function') {
|
||||
return col.dateValueFormatter(date);
|
||||
}
|
||||
return String(date.getTime() * 1e4 + 621355968e9);
|
||||
}
|
||||
return '';
|
||||
@ -341,6 +350,15 @@ export class GridDateColumn extends GridColumn {
|
||||
element.disabled = enabled === false;
|
||||
}
|
||||
|
||||
static _toDateValue(dt) {
|
||||
if (isNaN(dt)) {
|
||||
return '';
|
||||
}
|
||||
const month = String(dt.getMonth() + 1).padStart(2, '0');
|
||||
const date = String(dt.getDate()).padStart(2, '0');
|
||||
return `${dt.getFullYear()}-${month}-${date}`;
|
||||
}
|
||||
|
||||
static _resolveDate(s) {
|
||||
if (s instanceof Date) {
|
||||
return s;
|
||||
|
145
lib/ui/grid/grid.d.ts
vendored
145
lib/ui/grid/grid.d.ts
vendored
@ -1,4 +1,12 @@
|
||||
import { GridColumnDefinition } from "./column"
|
||||
/**
|
||||
* 单元格点击回调函数
|
||||
*
|
||||
* @callback cellClickedCallback
|
||||
* @param {number} index - 点击的行索引
|
||||
* @param {number} colIndex - 点击的列索引
|
||||
* @returns {boolean} 返回 `false` 则取消事件冒泡
|
||||
*/
|
||||
|
||||
/** 列数据接口 */
|
||||
interface GridItem {
|
||||
@ -27,17 +35,33 @@ interface GridSourceItem {
|
||||
/** Grid 语言资源接口 */
|
||||
interface GridLanguages {
|
||||
/**
|
||||
* “所有”文本
|
||||
*
|
||||
* @default `( All )`
|
||||
*/
|
||||
* “所有”文本,默认值 `( All )` */
|
||||
all: string;
|
||||
/** “确定”文本,默认值 OK */
|
||||
/** “确定”文本,默认值 `OK` */
|
||||
ok: string;
|
||||
/** “重置”文本,默认值 Reset */
|
||||
/** “重置”文本,默认值 `Reset` */
|
||||
reset: string;
|
||||
/** “空”文本,默认值 ( Null ) */
|
||||
null: string
|
||||
cancel: string;
|
||||
/** “空”文本,默认值 `( Null )` */
|
||||
null: string;
|
||||
addLevel: string;
|
||||
deleteLevel: string;
|
||||
copyLevel: string;
|
||||
asc: string;
|
||||
desc: string;
|
||||
column: string;
|
||||
order: string;
|
||||
sort: string;
|
||||
requirePrompt: string;
|
||||
duplicatePrompt: string;
|
||||
}
|
||||
|
||||
/** Grid 列排序定义接口 */
|
||||
interface GridColumnSortDefinition {
|
||||
/** 排序列的关键字 */
|
||||
column: string;
|
||||
/** 升序或降序 */
|
||||
order: "asc" | "desc";
|
||||
}
|
||||
|
||||
/** 列排序枚举 */
|
||||
@ -49,7 +73,7 @@ declare enum GridColumnDirection {
|
||||
}
|
||||
|
||||
/** 列事件枚举 */
|
||||
declare enum GridColumnColumnEvent {
|
||||
declare enum GridColumnEvent {
|
||||
/** 重排事件 */
|
||||
Reorder = "reorder",
|
||||
/** 宽调整事件 */
|
||||
@ -80,41 +104,43 @@ export class Grid {
|
||||
* 判断列是否为复选框列
|
||||
* @param type 列类型
|
||||
*/
|
||||
isCheckbox(type: Number): boolean;
|
||||
isCheckbox(type: number): boolean;
|
||||
};
|
||||
|
||||
/** 列定义的数组 */
|
||||
columns: Array<GridColumnDefinition>;
|
||||
/** 多语言资源对象 */
|
||||
langs?: GridLanguages;
|
||||
/** 行数大于等于该值则启用虚模式,默认值 100 */
|
||||
virtualCount?: Number;
|
||||
/** 表格行高,默认值 36 */
|
||||
rowHeight?: Number;
|
||||
/** 文本行高,默认值 24 */
|
||||
lineHeight?: Number;
|
||||
/** 列表底部留出额外行的空白,默认值 0 */
|
||||
extraRows?: Number;
|
||||
/** 过滤条件列表的行高,默认值 30 */
|
||||
filterRowHeight?: Number;
|
||||
/** 列表高度值,为 0 时列表始终显示全部内容(自增高),为非数字或者小于 0 则根据容器高度来确定虚模式的渲染行数,默认值 null */
|
||||
height?: Number;
|
||||
/** 列表是否为只读,默认值 false */
|
||||
/** 行数大于等于该值则启用虚模式,默认值 `100` */
|
||||
virtualCount?: number;
|
||||
/** 表格行高,默认值 `36` */
|
||||
rowHeight?: number;
|
||||
/** 文本行高,默认值 `24` */
|
||||
lineHeight?: number;
|
||||
/** 列表底部留出额外行的空白,默认值 `0` */
|
||||
extraRows?: number;
|
||||
/** 过滤条件列表的行高,默认值 `30` */
|
||||
filterRowHeight?: number;
|
||||
/** 列表高度值,为 0 时列表始终显示全部内容(自增高),为非数字或者小于 0 则根据容器高度来确定虚模式的渲染行数,默认值 `null` */
|
||||
height?: number;
|
||||
/** 列表是否为只读,默认值 `false` */
|
||||
readonly?: boolean;
|
||||
/** 是否允许多选,默认值 false */
|
||||
/** 是否允许多选,默认值 `false` */
|
||||
multiSelect?: boolean;
|
||||
/** 为 false 时只有点击在单元格内才会选中行,默认值 true */
|
||||
/** 为 false 时只有点击在单元格内才会选中行,默认值 `true` */
|
||||
fullrowClick?: boolean;
|
||||
/** 单元格 tooltip 是否禁用,默认值 false */
|
||||
/** 单元格 tooltip 是否禁用,默认值 `false` */
|
||||
tooltipDisabled?: boolean;
|
||||
/** 列头是否显示,默认值 true */
|
||||
/** 列头是否显示,默认值 `true` */
|
||||
headerVisible?: boolean;
|
||||
/** 监听事件的窗口载体,默认值 window */
|
||||
/** 监听事件的窗口载体,默认值 `window` */
|
||||
window?: Window
|
||||
/** 排序列的索引,默认值 -1 */
|
||||
sortIndex?: Number;
|
||||
/** 排序方式,正数升序,负数倒序,默认值 1 */
|
||||
/** 排序列的索引,默认值 `-1` */
|
||||
sortIndex?: number;
|
||||
/** 排序方式,正数升序,负数倒序,默认值 `1` */
|
||||
sortDirection?: GridColumnDirection;
|
||||
/** 排序列 */
|
||||
sortArray?: Array<GridColumnSortDefinition>;
|
||||
|
||||
/**
|
||||
* Grid 控件构造函数
|
||||
@ -130,34 +156,33 @@ export class Grid {
|
||||
* @param colIndex 即将选中的列索引
|
||||
* @eventProperty
|
||||
*/
|
||||
willSelect?: (index: Number, colIndex: Number) => boolean;
|
||||
willSelect?: (index: number, colIndex: number) => boolean;
|
||||
/**
|
||||
* 单元格单击时触发,colIndex 为 -1 则表示点击的是行的空白处,返回 false 则取消事件冒泡
|
||||
* @param index 点击的行索引
|
||||
* @param colIndex 点击的列索引
|
||||
* @property {cellClickedCallback}
|
||||
* @eventProperty
|
||||
*/
|
||||
cellClicked?: (index: Number, colIndex: Number) => boolean;
|
||||
cellClicked?: (index: number, colIndex: number) => boolean;
|
||||
|
||||
/**
|
||||
* 选中行发生变化时触发的事件
|
||||
* @param index 选中的行索引
|
||||
* @eventProperty
|
||||
*/
|
||||
onSelectedRowChanged?: (index?: Number) => void;
|
||||
onSelectedRowChanged?: (index?: number) => void;
|
||||
/**
|
||||
* 单元格双击时触发的事件,colIndex 为 -1 则表示点击的是行的空白处
|
||||
* @param index 双击的行索引
|
||||
* @param colIndex 双击的列索引
|
||||
* @eventProperty
|
||||
*/
|
||||
onCellDblClicked?: (index: Number, colIndex: Number) => void;
|
||||
onCellDblClicked?: (index: number, colIndex: number) => void;
|
||||
/**
|
||||
* 行双击时触发的事件
|
||||
* @param index 双击的行索引
|
||||
* @eventProperty
|
||||
*/
|
||||
onRowDblClicked?: (index: Number) => void;
|
||||
onRowDblClicked?: (index: number) => void;
|
||||
/**
|
||||
* 列发生变化时触发的事件
|
||||
* @param type 事件类型<br/><br/>
|
||||
@ -168,31 +193,39 @@ export class Grid {
|
||||
* @param value 变化的值
|
||||
* @eventProperty
|
||||
*/
|
||||
onColumnChanged?: (type: GridColumnColumnEvent, colIndex: Number, value: Number | GridColumnDirection) => void;
|
||||
onColumnChanged?: (type: GridColumnEvent, colIndex: number, value: number | GridColumnDirection) => void;
|
||||
/**
|
||||
* 列滚动时触发的事件
|
||||
* @param e 滚动事件对象
|
||||
* @eventProperty
|
||||
*/
|
||||
onBodyScrolled?: (e: Event) => void;
|
||||
/**
|
||||
* 多列排序后触发的事件
|
||||
* @param array 排序列定义数组
|
||||
* @eventProperty
|
||||
*/
|
||||
onSorted?: (array?: Array<GridColumnSortDefinition>) => void;
|
||||
|
||||
/** 获取数据数组 */
|
||||
get source(): GridItem;
|
||||
get source(): Array<GridItem>;
|
||||
/** 设置数据,并刷新列表 */
|
||||
set source(list: Array<GridItem>);
|
||||
/** 获取当前选中的行索引的数组 */
|
||||
get selectedIndexes(): Array<Number>;
|
||||
get selectedIndexes(): Array<number>;
|
||||
/** 设置当前选中的行索引的数组,并刷新列表 */
|
||||
set selectedIndexes(indexes: Array<Number>);
|
||||
set selectedIndexes(indexes: Array<number>);
|
||||
/** 获取 Grid 当前是否处于加载状态 */
|
||||
get loading(): boolean;
|
||||
/** 使 Grid 进入加载状态 */
|
||||
set loading(flag: boolean);
|
||||
/** 获取 Grid 当前滚动的偏移量 */
|
||||
get scrollTop(): Number;
|
||||
get scrollTop(): number;
|
||||
/** 设置 Grid 滚动偏移量 */
|
||||
set scrollTop(top: Number);
|
||||
set scrollTop(top: number);
|
||||
|
||||
/** 获取已过滤后的当前列表中的数据数组 */
|
||||
get sourceFiltered(): Array<GridItem>;
|
||||
/** 获取 Grid 的页面元素 */
|
||||
get element(): HTMLElement;
|
||||
/** 获取当前 Grid 是否已发生改变 */
|
||||
@ -202,7 +235,7 @@ export class Grid {
|
||||
/** 获取当前排序的列关键字,为 null 则当前无排序列 */
|
||||
get sortKey(): string | undefined;
|
||||
/** 获取当前选中行的索引,为 -1 则当前没有选中行 */
|
||||
get selectedIndex(): Number | -1;
|
||||
get selectedIndex(): number | -1;
|
||||
|
||||
/**
|
||||
* 初始化Grid控件
|
||||
@ -219,26 +252,27 @@ export class Grid {
|
||||
* @param index 行索引
|
||||
* @param item 待设置的行数据值
|
||||
*/
|
||||
setItem(index: Number, item: GridItem): void;
|
||||
setItem(index: number, item: GridItem): void;
|
||||
/**
|
||||
* 添加行数据
|
||||
* @param item 待添加的行数据值
|
||||
* @param index 待添加的行索引
|
||||
*/
|
||||
addItem(item: GridItem, index?: Number): void;
|
||||
addItem(item: GridItem, index?: number): void;
|
||||
/**
|
||||
* 删除行数据
|
||||
* @param index 待删除的行索引
|
||||
* @returns 返回已删除的行数据
|
||||
*/
|
||||
removeItem(index: Number): void;
|
||||
removeItem(index: number): GridItem;
|
||||
/**
|
||||
* 滚动到指定行的位置
|
||||
* @param index 待滚动至的行索引
|
||||
*/
|
||||
scrollToIndex(index: Number): void;
|
||||
scrollToIndex(index: number): void;
|
||||
/**
|
||||
* 调整 Grid 元素的大小,一般需要在宽度变化时(如页面大小发生变化时)调用
|
||||
* @param force 是否强制reload,默认只有待渲染的行数发生变化时才会调用reload
|
||||
* @param force 是否强制 {@linkcode reload},默认只有待渲染的行数发生变化时才会调用
|
||||
* @param keep 是否保持当前滚动位置
|
||||
*/
|
||||
resize(force?: boolean, keep?: boolean): void;
|
||||
@ -257,11 +291,20 @@ export class Grid {
|
||||
resetChange(): void;
|
||||
/**
|
||||
* 根据当前排序字段进行列排序
|
||||
* @param reload 为 true 则在列排序后调用 reload 方法
|
||||
* @param reload 为 true 则在列排序后调用 {@linkcode Grid.reload} 方法
|
||||
*/
|
||||
sortColumn(reload?: boolean): void;
|
||||
/**
|
||||
* 根据当前排序列数组进行多列排序
|
||||
* @param reload 为 true 则在多列排序后调用 {@linkcode Grid.reload} 方法
|
||||
*/
|
||||
sort(reload?: boolean): void;
|
||||
/**
|
||||
* 清除列头复选框的选中状态
|
||||
*/
|
||||
clearHeaderCheckbox(): void;
|
||||
/**
|
||||
* 显示多列排序设置面板
|
||||
*/
|
||||
showSortPanel(): void;
|
||||
}
|
@ -6,6 +6,7 @@ import { createElement } from "../../functions";
|
||||
import { createIcon } from "../icon";
|
||||
import { createCheckbox } from "../checkbox";
|
||||
import { setTooltip } from "../tooltip";
|
||||
import { Popup, showAlert } from "../popup";
|
||||
import { convertCssStyle } from "../extension";
|
||||
import { GridColumn, GridInputColumn, GridTextColumn, GridDropdownColumn, GridCheckboxColumn, GridIconColumn, GridDateColumn } from "./column";
|
||||
|
||||
@ -89,6 +90,7 @@ export class Grid {
|
||||
window = global;
|
||||
sortIndex = -1;
|
||||
sortDirection = 1;
|
||||
sortArray = null;
|
||||
|
||||
willSelect;
|
||||
cellClicked;
|
||||
@ -119,7 +121,18 @@ export class Grid {
|
||||
all: r('allItem', '( All )'),
|
||||
ok: r('ok', 'OK'),
|
||||
reset: r('reset', 'Reset'),
|
||||
null: r('null', '( Null )')
|
||||
cancel: r('cancel', 'Cancel'),
|
||||
null: r('null', '( Null )'),
|
||||
addLevel: r('', 'Add level'),
|
||||
deleteLevel: r('', 'Delete level'),
|
||||
copyLevel: r('', 'Copy level'),
|
||||
asc: r('', 'Ascending'),
|
||||
desc: r('', 'Descending'),
|
||||
column: r('', 'Column'),
|
||||
order: r('', 'Order'),
|
||||
sort: r('', 'Sort'),
|
||||
requirePrompt: r('', 'Column required.'),
|
||||
duplicatePrompt: r('', 'Column duplicated: "{column}"')
|
||||
};
|
||||
}
|
||||
|
||||
@ -146,6 +159,8 @@ export class Grid {
|
||||
this._refreshSource(list);
|
||||
}
|
||||
|
||||
get sourceFiltered() { return this._var.currentSource?.map(s => s.values) ?? this.source }
|
||||
|
||||
setItem(index, item) {
|
||||
if (this._var.source == null) {
|
||||
throw new Error('no source');
|
||||
@ -172,8 +187,9 @@ export class Grid {
|
||||
if (this._var.source == null) {
|
||||
throw new Error('no source');
|
||||
}
|
||||
this._var.source.splice(index, 1);
|
||||
const item = this._var.source.splice(index, 1)[0];
|
||||
this.reload();
|
||||
return item;
|
||||
}
|
||||
|
||||
_refreshSource(list) {
|
||||
@ -202,6 +218,8 @@ export class Grid {
|
||||
|
||||
if (this.sortIndex >= 0) {
|
||||
this.sortColumn();
|
||||
} else if (this.sortArray?.length > 0) {
|
||||
this.sort();
|
||||
}
|
||||
this.resize();
|
||||
}
|
||||
@ -351,8 +369,12 @@ export class Grid {
|
||||
this._var.el = grid;
|
||||
|
||||
this._var.rendering = false;
|
||||
if (this._var.source != null && this.sortIndex >= 0) {
|
||||
this.sortColumn();
|
||||
if (this._var.source != null) {
|
||||
if (this.sortIndex >= 0) {
|
||||
this.sortColumn();
|
||||
} else if (this.sortArray?.length > 0) {
|
||||
this.sort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -452,31 +474,12 @@ export class Grid {
|
||||
}
|
||||
}
|
||||
|
||||
sortColumn(reload) {
|
||||
const index = this.sortIndex;
|
||||
const col = this.columns[index];
|
||||
if (col == null) {
|
||||
return;
|
||||
}
|
||||
const direction = this.sortDirection;
|
||||
[...this._var.refs.header.children].forEach((th, i) => {
|
||||
const arrow = th.querySelector('.arrow');
|
||||
if (arrow == null) {
|
||||
return;
|
||||
}
|
||||
if (i === index) {
|
||||
arrow.className = `arrow ${(direction !== 1 ? 'desc' : 'asc')}`;
|
||||
} else if (arrow.className !== 'arrow') {
|
||||
arrow.className = 'arrow';
|
||||
}
|
||||
});
|
||||
let comparer;
|
||||
_getComparer(col, direction) {
|
||||
if (typeof col.sortFilter !== 'function') {
|
||||
let direction = this.sortDirection;
|
||||
if (isNaN(direction)) {
|
||||
direction = 1;
|
||||
}
|
||||
comparer = (a, b) => {
|
||||
return (a, b) => {
|
||||
a = this._getItemProp(a.values, true, col);
|
||||
b = this._getItemProp(b.values, true, col);
|
||||
if (a == null && typeof b === 'number') {
|
||||
@ -491,9 +494,30 @@ export class Grid {
|
||||
}
|
||||
return a === b ? 0 : (a > b ? 1 : -1) * direction;
|
||||
};
|
||||
} else {
|
||||
comparer = (a, b) => col.sortFilter(a.values, b.values) * direction;
|
||||
}
|
||||
return (a, b) => col.sortFilter(a.values, b.values) * direction;
|
||||
}
|
||||
|
||||
sortColumn(reload) {
|
||||
const index = this.sortIndex;
|
||||
const col = this.columns[index];
|
||||
if (col == null) {
|
||||
return;
|
||||
}
|
||||
this.sortArray = null;
|
||||
const direction = this.sortDirection;
|
||||
[...this._var.refs.header.children].forEach((th, i) => {
|
||||
const arrow = th.querySelector('.arrow');
|
||||
if (arrow == null) {
|
||||
return;
|
||||
}
|
||||
if (i === index) {
|
||||
arrow.className = `arrow ${(direction !== 1 ? 'desc' : 'asc')}`;
|
||||
} else if (arrow.className !== 'arrow') {
|
||||
arrow.className = 'arrow';
|
||||
}
|
||||
});
|
||||
const comparer = this._getComparer(col, direction);
|
||||
this._var.source.sort(comparer);
|
||||
if (this._var.colAttrs.__filtered === true) {
|
||||
this._var.currentSource.sort(comparer);
|
||||
@ -508,11 +532,257 @@ export class Grid {
|
||||
}
|
||||
}
|
||||
|
||||
sort(reload) {
|
||||
const sortArray = this.sortArray;
|
||||
if (sortArray == null || sortArray.length === 0) {
|
||||
return;
|
||||
}
|
||||
this.sortIndex = -1;
|
||||
const comparer = (a, b) => {
|
||||
for (let i = 0; i < sortArray.length; ++i) {
|
||||
const s = sortArray[i];
|
||||
const col = this.columns.find(c => c.key === s.column && c.visible !== false);
|
||||
if (col != null) {
|
||||
const result = this._getComparer(col, s.order === 'desc' ? -1 : 1)(a, b);
|
||||
if (result !== 0) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
this._var.source.sort(comparer);
|
||||
if (this._var.colAttrs.__filtered === true) {
|
||||
this._var.currentSource.sort(comparer);
|
||||
}
|
||||
if (this._var.rowCount < 0) {
|
||||
return;
|
||||
}
|
||||
if (reload) {
|
||||
this.reload();
|
||||
} else {
|
||||
this.refresh();
|
||||
}
|
||||
// arrow icon
|
||||
[...this._var.refs.header.children].forEach((th, i) => {
|
||||
const arrow = th.querySelector('.arrow');
|
||||
if (arrow == null) {
|
||||
return;
|
||||
}
|
||||
const col = this.columns[i];
|
||||
const s = sortArray.find(s => s.column === col.key && col.visible !== false);
|
||||
if (s != null) {
|
||||
arrow.className = `arrow ${s.order}`;
|
||||
} else if (arrow.className !== 'arrow') {
|
||||
arrow.className = 'arrow';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
clearHeaderCheckbox() {
|
||||
const boxes = this._var.refs.header.querySelectorAll('.ui-check-wrapper>input');
|
||||
boxes.forEach(box => box.checked = false);
|
||||
}
|
||||
|
||||
showSortPanel() {
|
||||
const content = createElement('div', 'ui-sort-panel-content');
|
||||
const buttonWrapper = createElement('div', 'ui-sort-panel-buttons');
|
||||
const grid = new Grid(null, r);
|
||||
grid.langs = this.langs;
|
||||
const rowChanged = index => {
|
||||
buttonWrapper.querySelector('.ui-button-delete').disabled = index < 0;
|
||||
buttonWrapper.querySelector('.ui-button-copy').disabled = index < 0;
|
||||
buttonWrapper.querySelector('.ui-button-move-up').disabled = index < 1;
|
||||
buttonWrapper.querySelector('.ui-button-move-down').disabled = index >= grid.source.length - 1;
|
||||
};
|
||||
grid.onSelectedRowChanged = rowChanged;
|
||||
const reload = index => {
|
||||
grid.selectedIndexes = [index];
|
||||
grid.scrollTop = index * grid.rowHeight;
|
||||
rowChanged(index);
|
||||
}
|
||||
buttonWrapper.append(
|
||||
createElement('button', null,
|
||||
createIcon('fa-light', 'plus'),
|
||||
createElement('span', span => {
|
||||
span.innerText = this.langs.addLevel;
|
||||
span.addEventListener('click', () => {
|
||||
let index = grid.selectedIndex;
|
||||
const n = { column: '', order: 'asc' };
|
||||
if (index >= 0) {
|
||||
index += 1;
|
||||
grid.addItem(n, index);
|
||||
} else {
|
||||
grid.addItem(n);
|
||||
index = grid.source.length - 1;
|
||||
}
|
||||
reload(index);
|
||||
});
|
||||
})
|
||||
),
|
||||
createElement('button', 'ui-button-delete',
|
||||
createIcon('fa-light', 'times'),
|
||||
createElement('span', span => {
|
||||
span.innerText = this.langs.deleteLevel;
|
||||
span.addEventListener('click', () => {
|
||||
let index = grid.selectedIndex;
|
||||
if (index < 0) {
|
||||
return;
|
||||
}
|
||||
grid.removeItem(index);
|
||||
const length = grid.source.length;
|
||||
if (index >= length) {
|
||||
index = length - 1;
|
||||
}
|
||||
reload(index);
|
||||
});
|
||||
})
|
||||
),
|
||||
createElement('button', 'ui-button-copy',
|
||||
createIcon('fa-light', 'copy'),
|
||||
createElement('span', span => {
|
||||
span.innerText = this.langs.copyLevel;
|
||||
span.addEventListener('click', () => {
|
||||
const index = grid.selectedIndex;
|
||||
if (index < 0) {
|
||||
return;
|
||||
}
|
||||
const item = grid.source[index];
|
||||
if (item == null) {
|
||||
return;
|
||||
}
|
||||
grid.addItem(Object.assign({}, item), index + 1);
|
||||
reload(index + 1);
|
||||
});
|
||||
})
|
||||
),
|
||||
createElement('button', button => {
|
||||
button.className = 'ui-button-move-up';
|
||||
const icon = createIcon('fa-light', 'chevron-up');
|
||||
icon.addEventListener('click', () => {
|
||||
const index = grid.selectedIndex;
|
||||
if (index < 1) {
|
||||
return;
|
||||
}
|
||||
const item = grid.source[index];
|
||||
if (item == null) {
|
||||
return;
|
||||
}
|
||||
const it = grid.removeItem(index);
|
||||
grid.addItem(it, index - 1);
|
||||
reload(index - 1);
|
||||
});
|
||||
button.appendChild(icon);
|
||||
}),
|
||||
createElement('button', button => {
|
||||
button.className = 'ui-button-move-down';
|
||||
const icon = createIcon('fa-light', 'chevron-down');
|
||||
icon.addEventListener('click', () => {
|
||||
const index = grid.selectedIndex;
|
||||
if (index >= grid.source.length - 1) {
|
||||
return;
|
||||
}
|
||||
const item = grid.source[index];
|
||||
if (item == null) {
|
||||
return;
|
||||
}
|
||||
const it = grid.removeItem(index);
|
||||
grid.addItem(it, index + 1);
|
||||
reload(index + 1);
|
||||
});
|
||||
button.appendChild(icon);
|
||||
})
|
||||
);
|
||||
const gridWrapper = createElement('div', 'ui-sort-panel-grid');
|
||||
content.append(buttonWrapper, gridWrapper);
|
||||
const columnSource = this.columns.filter(c => c.sortable !== false && c.visible !== false);
|
||||
grid.columns = [
|
||||
{
|
||||
key: 'column',
|
||||
caption: this.langs.column,
|
||||
width: 270,
|
||||
type: Grid.ColumnTypes.Dropdown,
|
||||
dropOptions: {
|
||||
textKey: 'caption',
|
||||
valueKey: 'key'
|
||||
},
|
||||
source: columnSource,
|
||||
sortable: false,
|
||||
orderable: false
|
||||
},
|
||||
{
|
||||
key: 'order',
|
||||
caption: this.langs.order,
|
||||
width: 150,
|
||||
type: Grid.ColumnTypes.Dropdown,
|
||||
source: [
|
||||
{ value: 'asc', text: this.langs.asc },
|
||||
{ value: 'desc', text: this.langs.desc }
|
||||
],
|
||||
sortable: false,
|
||||
orderable: false
|
||||
}
|
||||
];
|
||||
const pop = new Popup({
|
||||
title: this.langs.sort,
|
||||
content,
|
||||
resizable: true,
|
||||
buttons: [
|
||||
{
|
||||
text: this.langs.ok,
|
||||
trigger: () => {
|
||||
const source = grid.source;
|
||||
if (source == null || source.length === 0) {
|
||||
this.sortArray = null;
|
||||
} else {
|
||||
const dict = {};
|
||||
for (let i = 0; i < source.length; ++i) {
|
||||
const it = source[i];
|
||||
if (it.column == null || it.column === '') {
|
||||
grid.selectedIndexes = [i];
|
||||
grid.refresh();
|
||||
showAlert(this.langs.sort, this.langs.requirePrompt, 'warn');
|
||||
return false;
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(dict, it.column)) {
|
||||
grid.selectedIndexes = [i];
|
||||
grid.refresh();
|
||||
let name = columnSource.find(c => c.key === it.column);
|
||||
if (name == null) {
|
||||
name = it.column;
|
||||
} else {
|
||||
name = name.caption;
|
||||
}
|
||||
showAlert(this.langs.sort, this.langs.duplicatePrompt.replace('{column}', name), 'warn');
|
||||
return false;
|
||||
}
|
||||
dict[it.column] = true;
|
||||
}
|
||||
this.sortArray = source;
|
||||
this.sortDirection = 1;
|
||||
this.sort();
|
||||
}
|
||||
if (typeof this.onSorted === 'function') {
|
||||
this.onSorted(this.sortArray);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
},
|
||||
{ text: this.langs.cancel }
|
||||
],
|
||||
onResizeEnded: () => grid.resize()
|
||||
});
|
||||
const source = this.sortArray || [{ column: '', order: 'asc' }];
|
||||
pop.show(this._var.el).then(() => {
|
||||
pop.container.style.cssText += 'width: 520px; height: 400px';
|
||||
grid.init(gridWrapper);
|
||||
grid.source = source.filter(s => s.column === '' || columnSource.find(c => c.key === s.column) != null);
|
||||
grid.selectedIndexes = [0];
|
||||
grid.refresh();
|
||||
rowChanged(0);
|
||||
});
|
||||
}
|
||||
|
||||
_createHeader(table) {
|
||||
const thead = createElement('thead');
|
||||
if (this.headerVisible === false) {
|
||||
@ -691,7 +961,7 @@ export class Grid {
|
||||
const exists = content.children.length;
|
||||
count -= exists;
|
||||
if (count > 0) {
|
||||
for (let i = 0; i < count; i += 1) {
|
||||
for (let i = 0; i < count; ++i) {
|
||||
const row = createElement('tr', 'ui-grid-row');
|
||||
let left = 0;
|
||||
cols.forEach((col, j) => {
|
||||
@ -940,7 +1210,7 @@ export class Grid {
|
||||
idx ??= 0;
|
||||
} else {
|
||||
const count = children.length;
|
||||
for (let i = index; i < count - 1 && offset >= 0; i += 1) {
|
||||
for (let i = index; i < count - 1 && offset >= 0; ++i) {
|
||||
element = children[i];
|
||||
if (element == null || !element.className || element.classList.contains('sticky')) {
|
||||
idx = i;
|
||||
@ -994,7 +1264,7 @@ export class Grid {
|
||||
if (targetIndex > 1) {
|
||||
targetIndex = orderIndex - 1;
|
||||
// const current = columns[index];
|
||||
// for (let i = index; i < targetIndex; i += 1) {
|
||||
// for (let i = index; i < targetIndex; ++i) {
|
||||
// columns[i] = columns[i + 1];
|
||||
// }
|
||||
// columns[targetIndex] = current;
|
||||
@ -1018,16 +1288,18 @@ export class Grid {
|
||||
row.insertBefore(row.children[index], row.children[targetIndex]);
|
||||
}
|
||||
}
|
||||
// refresh sortIndex
|
||||
[...children].forEach((th, i) => {
|
||||
const arrow = th.querySelector('.arrow');
|
||||
if (arrow == null) {
|
||||
return;
|
||||
}
|
||||
if (arrow.className !== 'arrow') {
|
||||
this.sortIndex = i;
|
||||
}
|
||||
});
|
||||
if (this.sortArray == null || this.sortArray.length === 0) {
|
||||
// refresh sortIndex
|
||||
[...children].forEach((th, i) => {
|
||||
const arrow = th.querySelector('.arrow');
|
||||
if (arrow == null) {
|
||||
return;
|
||||
}
|
||||
if (arrow.className !== 'arrow') {
|
||||
this.sortIndex = i;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof this.onColumnChanged === 'function') {
|
||||
this.onColumnChanged(ColumnChangedType.Reorder, index, targetIndex);
|
||||
@ -1641,7 +1913,7 @@ export class Grid {
|
||||
end = selectedIndex;
|
||||
}
|
||||
selectedIndexes.splice(0);
|
||||
for (let i = start; i <= end; i += 1) {
|
||||
for (let i = start; i <= end; ++i) {
|
||||
selectedIndexes.push(i);
|
||||
}
|
||||
flag = true;
|
||||
@ -1713,7 +1985,7 @@ export class Grid {
|
||||
if (enabled !== false) {
|
||||
const val = item[col.key];
|
||||
let oldValue;
|
||||
if (val != null && Object.prototype.hasOwnProperty.call(val, 'Value') != null) {
|
||||
if (val != null && Object.prototype.hasOwnProperty.call(val, 'Value')) {
|
||||
oldValue = val.Value;
|
||||
val.Value = value;
|
||||
} else {
|
||||
|
Loading…
x
Reference in New Issue
Block a user