feature: tiered sort

This commit is contained in:
2024-05-22 09:27:22 +08:00
parent a946012a33
commit 190e43c814
14 changed files with 254 additions and 147 deletions

View File

@ -50,12 +50,12 @@ export function toDateValue(dt, local) {
}
/**
* @private
* @param {Date} date
* @param {boolean} [utc=true]
* @returns {string}
* 获取日期格式器
* @param {Date} date - 待格式的日期
* @param {boolean} [utc=true] - 是否按 UTC 时间格式化
* @returns {any} 返回格式化工具对象
*/
function getFormatter(date, utc) {
export function getFormatter(date, utc) {
const prefix = utc !== false ? 'getUTC' : 'get';
const r = {
/**

View File

@ -1,4 +1,3 @@
// import { r, global, contains, isPositive, nullOrEmpty } from "../utility";
import './css/dropdown.scss';
import { r } from "../utility/lgres";
import { contains, nullOrEmpty } from "../utility/strings";
@ -205,6 +204,7 @@ export class Dropdown {
if (options.input) {
label = createElement('input', 'ui-drop-text');
label.type = 'text';
label.autocomplete = 'off';
label.draggable = false;
options.placeholder && label.setAttribute('placeholder', options.placeholder);
isPositive(options.maxLength) && label.setAttribute('maxlength', options.maxLength);

View File

@ -1,19 +1,19 @@
import '../css/grid.scss';
import { global, isPositive, isMobile, throttle, truncate, debounce } from "../../utility";
import { global, isPositive, isMobile, throttle, debounce, truncate } from "../../utility";
import { r as lang } from "../../utility/lgres";
import { nullOrEmpty } from "../../utility/strings";
import { createElement } from "../../functions";
import { createIcon } from "../icon";
import { createCheckbox } from "../checkbox";
import { setTooltip } from "../tooltip";
import { Popup, showAlert } from "../popup";
import { Popup, showAlert, showConfirm } from "../popup";
import { convertCssStyle } from "../extension";
import { GridColumn, GridInputColumn, GridTextColumn, GridDropdownColumn, GridCheckboxColumn, GridRadioboxColumn, GridIconColumn, GridDateColumn } from "./column";
/**
* @author Tsanie Lily <tsorgy@gmail.com>
* @license MIT
* @version 1.0.2
* @version 1.0.3
*/
const ScriptPath = (self.document == null ? self.location.href : self.document.currentScript?.src ?? '').replace(/ui\.min\.js\?.+$/, '');
@ -258,6 +258,7 @@ let r = lang;
* @property {(ValueItem[] | GridColumnFilterSourceCallback)} [filterSource] - 自定义列过滤器的数据源,支持调用函数返回数据源
* @property {boolean} [filterAsValue=false] - 列头过滤强制使用 `Value` 字段
* @property {GridItemSortCallback} [sortFilter] - 自定义列排序函数
* @property {boolean} [sortAsText=false] - 按照 `DisplayValue` 排序
* @property {DropdownOptions} [dropOptions] - 列为下拉列表类型时以该值设置下拉框的参数
* @property {boolean} [dropRestrictCase=false] - 下拉列表是否区分大小写
* @property {(GridSourceItem[] | Promise<GridSourceItem[]> | GridDropdownSourceCallback)} [source] - 列为下拉列表类型时以该值设置下拉列表数据源,支持返回异步对象,也支持调用函数返回
@ -450,6 +451,7 @@ const GridColumnDirection = {
* @typedef GridLanguages
* @property {string} [all] - ( All )
* @property {string} [ok] - OK
* @property {string} [yes] - Yes
* @property {string} [reset] - Reset
* @property {string} [cancel] - Cancel
* @property {string} [null] - ( Null )
@ -464,6 +466,7 @@ const GridColumnDirection = {
* @property {string} [column] - Column
* @property {string} [order] - Order
* @property {string} [sort] - Sort
* @property {string} [sortArrayExists] - This will remove the current tiered sort. Do you wish to continue?
* @property {string} [requirePrompt] - All sort criteria must have a column specified. Check the selected sort criteria and try again.
* @property {string} [duplicatePrompt] - {column} is being sorted more than once. Delete the duplicate sort criteria and try again.
* @interface
@ -1348,6 +1351,7 @@ export class Grid {
this.langs = {
all: r('allItem', '( All )'),
ok: r('ok', 'OK'),
yes: r('yes', 'Yes'),
reset: r('reset', 'Reset'),
cancel: r('cancel', 'Cancel'),
null: r('null', '( Null )'),
@ -1362,6 +1366,7 @@ export class Grid {
column: r('column', 'Column'),
order: r('order', 'Order'),
sort: r('sort', 'Sort'),
sortArrayExists: r('sortArrayExists', 'This will remove the current tiered sort. Do you wish to continue?'),
requirePrompt: r('requirePrompt', 'All sort criteria must have a column specified. Check the selected sort criteria and try again.'),
duplicatePrompt: r('duplicatePrompt', '{column} is being sorted more than once. Delete the duplicate sort criteria and try again.')
};
@ -1405,10 +1410,9 @@ export class Grid {
if (e.target === this._var.el) {
// cancel selections
const selectedIndexes = this._var.selectedIndexes;
if (selectedIndexes == null || selectedIndexes.length === 0) {
return;
if (selectedIndexes?.length > 0) {
selectedIndexes.splice(0);
}
selectedIndexes.splice(0);
if (this.readonly) {
this._tableRows.forEach(row => {
row.classList.remove('selected');
@ -1426,6 +1430,10 @@ export class Grid {
if (parent == null) {
return;
}
if (this._getParentElement(parent) !== this._var.el) {
// sub ui-grid
return;
}
const rowIndex = parent.classList.contains('ui-grid-total-row') ? -1 : this._tableRows.indexOf(parent);
let colIndex = indexOfParent(target) - (this.expandable ? 1 : 0);
if (colIndex >= this.columns.length) {
@ -1799,58 +1807,64 @@ export class Grid {
grid.onRowChanged();
}
buttonWrapper.append(
createElement('span', 'button',
createElement('span', button => {
button.className = 'button';
button.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);
});
},
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('span', 'button ui-button-delete',
createElement('span', button => {
button.className = 'button ui-button-delete';
button.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);
});
},
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('span', 'button ui-button-copy',
createElement('span', button => {
button.className = 'button ui-button-copy';
button.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);
});
},
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);
});
})
),
/*
@ -2444,22 +2458,39 @@ export class Grid {
if (isNaN(direction)) {
direction = 1;
}
const editing = col.sortAsText !== true;
return (a, b) => {
a = this._getItemProp(a.values, true, col);
b = this._getItemProp(b.values, true, col);
if (typeof a === 'boolean') {
a = a ? 2 : 1;
}
if (typeof b === 'boolean') {
b = b ? 2 : 1;
}
if (a == null && typeof b === 'number') {
a = 0;
} else if (typeof a === 'number' && b == null) {
b = 0;
} else if (a != null && b == null) {
return direction;
a = this._getItemSortProp(a.values, editing, col);
b = this._getItemSortProp(b.values, editing, col);
if (editing) {
if (typeof a === 'boolean') {
a = a ? 2 : 1;
}
if (typeof b === 'boolean') {
b = b ? 2 : 1;
}
if (a == null && typeof b === 'number') {
a = 0;
} else if (typeof a === 'number' && b == null) {
b = 0;
} 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();
}
}
} else {
if (a != null && b == null) {
return direction;
}
if (Array.isArray(a)) {
a = a.join(', ');
}
@ -3546,6 +3577,21 @@ export class Grid {
return value;
}
/**
* @private
* @param {GridRowItem} item
* @param {boolean} editing
* @param {GridColumnDefinition} col
* @returns {any}
*/
_getItemSortProp(item, editing, col) {
const value = item[col.key];
if (value != null && Object.prototype.hasOwnProperty.call(value, 'SortValue')) {
return value.SortValue;
}
return this._getItemProp(item, editing, col);
}
/**
* @private
* @param {HTMLElement} target
@ -3591,22 +3637,46 @@ export class Grid {
return;
}
if (!this._notHeader(e.target.tagName)) {
const index = this.columns.indexOf(col);
if (index < 0) {
return;
}
if (this.sortIndex === index) {
this.sortDirection = this.sortDirection === 1 ? -1 : 1;
if (Array.isArray(this.sortArray) && this.sortArray.length > 0) {
showConfirm(this.langs.sort, this.langs.sortArrayExists, [
{
key: 'yes',
text: this.langs.yes
},
{
text: this.langs.cancel
}
]).then(result => {
if (result?.key === 'yes') {
this._onDoHeaderSort(col);
}
});
} else {
this.sortIndex = index;
}
this.sortColumn();
if (typeof this.onColumnChanged === 'function') {
this.onColumnChanged(ColumnChangedType.Sort, index, this.sortDirection);
this._onDoHeaderSort(col);
}
}
}
/**
* @private
* @param {GridColumnDefinition} col
*/
_onDoHeaderSort(col) {
const index = this.columns.indexOf(col);
if (index < 0) {
return;
}
if (this.sortIndex === index) {
this.sortDirection = this.sortDirection === 1 ? -1 : 1;
} else {
this.sortIndex = index;
}
this.sortColumn();
if (typeof this.onColumnChanged === 'function') {
this.onColumnChanged(ColumnChangedType.Sort, index, this.sortDirection);
}
}
/**
* @private
* @param {MouseEvent} [e]

View File

@ -1,7 +1,7 @@
import "./css/media.scss";
import { createElement } from "../functions";
import { createIcon } from "./icon";
import { get } from "../utility";
import { get } from "../utility/request";
export function createPicture(url) {
return createElement('a', a => {

3
lib/ui/popup.d.ts vendored
View File

@ -69,7 +69,8 @@ export class Popup {
}
interface PopupButton {
tabIndex: number;
className?: string;
tabIndex?: number;
key: string;
text: string;
trigger: (this: Popup) => boolean | Promise<boolean>;

View File

@ -271,6 +271,9 @@ export class Popup {
container.appendChild(
createElement('div', 'ui-popup-footer', ...option.buttons.map((b, i) => {
const button = createElement('button', 'ui-popup-button');
if (b.className != null) {
button.classList.add(b.className);
}
if (b.tabIndex > 0) {
button.tabIndex = b.tabIndex;
} else {

View File

@ -1,6 +1,5 @@
import './css/tooltip.scss';
import { createElement } from "../functions";
// import { global } from "../utility";
const pointerHeight = 12;