diff --git a/lib/app/communications/lib.js b/lib/app/communications/lib.js
index bfdef7e..64bb325 100644
--- a/lib/app/communications/lib.js
+++ b/lib/app/communications/lib.js
@@ -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) {
diff --git a/lib/ui/css/grid.scss b/lib/ui/css/grid.scss
index 9cc4962..01c381b 100644
--- a/lib/ui/css/grid.scss
+++ b/lib/ui/css/grid.scss
@@ -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) {
diff --git a/lib/ui/dropdown.d.ts b/lib/ui/dropdown.d.ts
index 5e93352..14da60f 100644
--- a/lib/ui/dropdown.d.ts
+++ b/lib/ui/dropdown.d.ts
@@ -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;
/** 是否固定为向下展开 */
diff --git a/lib/ui/grid/column.d.ts b/lib/ui/grid/column.d.ts
index 6da6c40..dc38b7a 100644
--- a/lib/ui/grid/column.d.ts
+++ b/lib/ui/grid/column.d.ts
@@ -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
+ * 设置单元格值时调用的方法
+ * 支持以下几种数据类型
+ * `"2024-01-26"`
+ * `"1/26/2024"`
+ * `"638418240000000000"`
+ * `new Date('2024-01-26')`
+ * @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
diff --git a/lib/ui/grid/column.js b/lib/ui/grid/column.js
index df07cf5..132929c 100644
--- a/lib/ui/grid/column.js
+++ b/lib/ui/grid/column.js
@@ -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;
diff --git a/lib/ui/grid/grid.d.ts b/lib/ui/grid/grid.d.ts
index b44ee7e..a7c5bef 100644
--- a/lib/ui/grid/grid.d.ts
+++ b/lib/ui/grid/grid.d.ts
@@ -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;
/** 多语言资源对象 */
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;
/**
* 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 事件类型
@@ -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) => void;
/** 获取数据数组 */
- get source(): GridItem;
+ get source(): Array;
/** 设置数据,并刷新列表 */
set source(list: Array);
/** 获取当前选中的行索引的数组 */
- get selectedIndexes(): Array;
+ get selectedIndexes(): Array;
/** 设置当前选中的行索引的数组,并刷新列表 */
- set selectedIndexes(indexes: Array);
+ set selectedIndexes(indexes: Array);
/** 获取 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;
/** 获取 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;
}
\ No newline at end of file
diff --git a/lib/ui/grid/grid.js b/lib/ui/grid/grid.js
index 02472cb..df27d69 100644
--- a/lib/ui/grid/grid.js
+++ b/lib/ui/grid/grid.js
@@ -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 {