Compare commits

...

2 Commits

Author SHA1 Message Date
0ff48a0ac4 change: replace date controller of Scheduler.
feature: `ui.formatDate` supports formatter.
fix: issue about boolean sort.
2024-03-28 16:37:22 +08:00
03e3b4a70f add: column.filterAsValue
fix: case-sensitive order of Array value
2024-03-27 13:36:31 +08:00
7 changed files with 234 additions and 1712 deletions

View File

@ -1,18 +1,8 @@
import { Grid, GridColumn, createElement, setTooltip, createIcon, createCheckbox, createRadiobox, showAlert, showConfirm, Popup, Dropdown, validation } from "../ui";
import { r as lang, nullOrEmpty, formatUrl, escapeEmoji, isEmail, isPhone } from "../utility";
import { createElement, createCheckbox, createRadiobox, Dropdown, validation, toDateValue } from "../ui";
import { r as lang } from "../utility";
let r = lang;
function datepicker(element) {
if (typeof $?.fn?.datepicker === 'function') {
$(element).datepicker({
autoHide: true,
format: 'm/dd/yyyy'
});
}
return element;
}
export default class ScheduleItem {
_var = {};
@ -79,7 +69,8 @@ export default class ScheduleItem {
getDateString(s) {
const d = this.getDateTime(s);
return String(d.getMonth() + 1).padStart(2, '0') + '/' + String(d.getDate()).padStart(2, '0') + '/' + String(d.getFullYear());
// return String(d.getMonth() + 1).padStart(2, '0') + '/' + String(d.getDate()).padStart(2, '0') + '/' + String(d.getFullYear());
return toDateValue(d, true);
}
setParameters(p) {
@ -101,15 +92,8 @@ export default class ScheduleItem {
this._var.container.querySelector('.schedule-id-6>input').checked = schedule.Saturday;
this._var.container.querySelector('.schedule-id-7>input').checked = schedule.Sunday;
this._var.container.querySelector('.schedule-id-dayofmonth').value = String(schedule.DayOfMonth);
const start = this.getDateString(schedule.StartDate);
const end = this.getDateString(schedule.EndDate);
if (typeof $?.fn?.datepicker === 'function') {
$(this._var.container.querySelector('.schedule-id-duration-start')).datepicker('setDate', new Date(start));
$(this._var.container.querySelector('.schedule-id-duration-end')).datepicker('setDate', new Date(end));
} else {
this._var.container.querySelector('.schedule-id-duration-start').value = start;
this._var.container.querySelector('.schedule-id-duration-end').value = end;
}
this._var.container.querySelector('.schedule-id-duration-start').value = this.getDateString(schedule.StartDate);
this._var.container.querySelector('.schedule-id-duration-end').value = this.getDateString(schedule.EndDate);
}
create() {
@ -222,20 +206,10 @@ export default class ScheduleItem {
createElement('legend', legend => legend.innerText = 'Duration'),
createElement('div', 'schedule-item-line schedule-item-line-duration',
createElement('span', span => span.innerText = 'Start date'),
datepicker(
validation(
createElement('input', i => { i.type = 'text', i.className = 'ui-input schedule-id-duration-start', i.maxLength = 10 }),
/^([0]?[1-9]|[1][0-2])\/([0]?[1-9]|[12][0-9]|[3][01])\/[0-9]{4}$/
)
),
createElement('input', i => { i.type = 'date', i.className = 'ui-input schedule-id-duration-start', i.maxLength = 10 }),
createElement('div', 'schedule-item-placeholder'),
createElement('span', span => span.innerText = 'End date'),
datepicker(
validation(
createElement('input', i => { i.type = 'text', i.className = 'ui-input schedule-id-duration-end', i.maxLength = 10 }),
/^([0]?[1-9]|[1][0-2])\/([0]?[1-9]|[12][0-9]|[3][01])\/[0-9]{4}$/
)
)
createElement('input', i => { i.type = 'date', i.className = 'ui-input schedule-id-duration-end', i.maxLength = 10 })
),
createElement('div', 'schedule-item-line',
createCheckbox({ className: 'schedule-id-enabled', checked: true, label: 'Enabled' })

5
lib/ui/date.d.ts vendored
View File

@ -10,8 +10,9 @@ export function createDateInput(min?: string, max?: string, element?: HTMLInputE
/**
* yyyy-MM-dd
* @param dt
* @param local
*/
export function toDateValue(dt: Date): string;
export function toDateValue(dt: Date, local?: boolean): string;
/**
*
@ -23,7 +24,7 @@ export function toDateValue(dt: Date): string;
* `new Date('2024-01-26')`<br/>
* @returns M/d/yyyy
*/
export function formatDate(date: Date | number | string): string;
export function formatDate(date: Date | number | string, formatter?: string): string;
/**
*

View File

@ -45,6 +45,129 @@ export function toDateValue(dt, local) {
return `${year}-${month}-${date}`;
}
/**
* @private
* @param {Date} date
* @param {boolean} [utc=true]
* @returns {string}
*/
function getFormatter(date, utc) {
const prefix = utc !== false ? 'getUTC' : 'get';
const r = {
/**
* @private
* @returns {number} 返回日数字
*/
j: () => date[`${prefix}Date`](),
/**
* @private
* @returns {string} 返回格式化成两位的日字符串
*/
d: () => String(r.j()).padStart(2, '0'),
/**
* @private
* @returns {number} 返回月数字
*/
n: () => date[`${prefix}Month`]() + 1,
/**
* @private
* @returns {string} 返回格式化成两位的月字符串
*/
m: () => String(r.n()).padStart(2, '0'),
/**
* @private
* @returns {number} 返回年数字
*/
Y: () => date[`${prefix}FullYear`](),
/**
* @private
* @returns {string} 返回年后两位
*/
y: () => String(r.Y()).slice(-2),
/**
* @private
* @returns {number} 返回日期当月总天数
*/
t: () => new Date(r.Y(), r.n(), 0).getDate(),
/**
* @private
* @returns {('1' | '0')} 返回是否为闰年
*/
L() {
const y = r.Y();
return t % 4 === 0 && t % 100 !== 0 || t % 400 === 0 ? 1 : 0;
},
/**
* @private
* @returns {number} 返回小时数字
*/
G: () => date[`${prefix}Hours`](),
/**
* @private
* @returns {number} 返回 12 小时制的数字
*/
g: () => r.G() % 12 || 12,
/**
* @private
* @returns {string} 返回格式化成两位的小时字符串
*/
H: () => String(r.G()).padStart(2, '0'),
/**
* @private
* @returns {string} 返回格式化成两位的 12 小时制的字符串
*/
h: () => String(r.g()).padStart(2, '0'),
/**
* @private
* @returns {string} 返回上下午
*/
A: () => ['AM', 'PM'][r.G() < 12 ? 0 : 1],
/**
* @private
* @returns {string} 返回小写的上下午
*/
a: () => r.A().toLowerCase(),
/**
* @private
* @returns {string} 返回格式化成两位的分钟字符串
*/
i: () => String(date[`${prefix}Minutes`]()).padStart(2, '0'),
/**
* @private
* @returns {string} 返回格式化成两位的秒字符串
*/
s: () => String(date[`${prefix}Seconds`]()).padStart(2, '0'),
/**
* @private
* @returns {string} 返回格式化成六位的毫秒字符串
*/
u: () => String(1e3 * date[`${prefix}Milliseconds`]()).padStart(6, '0'),
/**
* @private
* @returns {string} 返回时区描述字符串
*/
e: () => /\((.*)\)/.exec(String(date))[1] || 'Coordinated Universal Time',
/**
* @private
* @returns {string} 返回时区偏移字符串
*/
O() {
const t = date.getTimezoneOffset();
const r = Math.abs(t);
return (t > 0 ? '-' : '+') + String(100 * Math.floor(r / 60) + r % 60).padStart(4, '0');
},
/**
* @private
* @returns {string} 返回 +08:00 这种格式的时区偏移字符串
*/
P() {
const t = r.O();
return t.substring(0, 3) + ':' + t.substring(3, 5);
}
};
return r;
}
/**
* 格式化日期为 M/d/yyyy 格式的字符串
* @param {Date | number | string} date - 需要格式化的日期值支持的格式如下
@ -54,16 +177,41 @@ export function toDateValue(dt, local) {
* * `"1/26/2024"`
* * `"638418240000000000"`
* * `new Date('2024-01-26')`
* @param {string} [formatter] - 格式化格式默认为 `"m/d/Y"`
*
* * Y - 例如 `2024`
* * y - 年的后两位例如 `"24"`
* * n - 例如 `2`
* * m - 格式化成两位的月例如 `"02"`
* * j - 例如 `4`
* * d - 格式化成两位的日例如 `"04"`
* * t - 日期当月总天数例如 `29`
* * L - 是否为闰年例如 `1`
* * G - 小时例如 `15`
* * g - 12 小时制的小时例如 `3`
* * H - 格式化成两位的小时例如 `"15"`
* * h - 格式化成两位的 12 小时制的小时例如 `"03"`
* * A - 上下午例如 `"PM"`
* * a - 小写的上下午例如 `"pm"`
* * i - 格式化成两位的分钟例如 `"39"`
* * s - 格式化成两位的秒例如 `"20"`
* * u - 格式化成六位的毫秒例如 `"023040"`
* * e - 时区描述字符串例如 `"China Standard Time"`
* * O - 时区偏移字符串例如 `"+0800"`
* * P - `"+08:00"` 这种格式的时区偏移字符串
* @returns {string} 返回格式化后的日期字符串
*/
export function formatDate(date) {
export function formatDate(date, formatter) {
formatter ??= 'm/d/Y';
if (date instanceof Date) {
return `${date.getMonth() + 1}/${date.getDate()}/${date.getFullYear()}`;
const f = getFormatter(date, false);
return formatter.replace(/\\?(.?)/gi, (k, v) => f[k] ? f[k]() : v);
}
const ticks = Number(date);
if (!isNaN(ticks) && ticks > 0) {
date = new Date((ticks - 621355968e9) / 1e4);
return `${date.getUTCMonth() + 1}/${date.getUTCDate()}/${date.getUTCFullYear()}`;
const f = getFormatter(date);
return formatter.replace(/\\?(.?)/gi, (k, v) => f[k] ? f[k]() : v);
}
return date;
}

View File

@ -805,10 +805,32 @@ export class GridDateColumn extends GridColumn {
* * `"1/26/2024"`
* * `"638418240000000000"`
* * `new Date('2024-01-26')`
* @param {string} [formatter] - 格式化格式默认为 `"m/d/Y"`
*
* * Y - 例如 `2024`
* * y - 年的后两位例如 `"24"`
* * n - 例如 `2`
* * m - 格式化成两位的月例如 `"02"`
* * j - 例如 `4`
* * d - 格式化成两位的日例如 `"04"`
* * t - 日期当月总天数例如 `29`
* * L - 是否为闰年例如 `1`
* * G - 小时例如 `15`
* * g - 12 小时制的小时例如 `3`
* * H - 格式化成两位的小时例如 `"15"`
* * h - 格式化成两位的 12 小时制的小时例如 `"03"`
* * A - 上下午例如 `"PM"`
* * a - 小写的上下午例如 `"pm"`
* * i - 格式化成两位的分钟例如 `"39"`
* * s - 格式化成两位的秒例如 `"20"`
* * u - 格式化成六位的毫秒例如 `"023040"`
* * e - 时区描述字符串例如 `"China Standard Time"`
* * O - 时区偏移字符串例如 `"+0800"`
* * P - `"+08:00"` 这种格式的时区偏移字符串
* @returns {string} 格式化为 M/d/yyyy 的日期字符串
*/
static formatDate(date) {
return formatDate(date);
static formatDate(date, formatter) {
return formatDate(date, formatter);
}
/**

View File

@ -255,6 +255,7 @@ let r = lang;
* @property {any[]} [filterValues] - 过滤值的数组
* @property {boolean} [filterAllowNull=false] - 是否区分 `null` 与空字符串
* @property {(ValueItem[] | GridColumnFilterSourceCallback)} [filterSource] - 自定义列过滤器的数据源支持调用函数返回数据源
* @property {boolean} [filterAsValue=false] - 列头过滤强制使用 `Value` 字段
* @property {GridItemSortCallback} [sortFilter] - 自定义列排序函数
* @property {DropdownOptions} [dropOptions] - 列为下拉列表类型时以该值设置下拉框的参数
* @property {(GridSourceItem[] | Promise<GridSourceItem[]> | GridDropdownSourceCallback)} [source] - 列为下拉列表类型时以该值设置下拉列表数据源支持返回异步对象也支持调用函数返回
@ -2259,15 +2260,29 @@ export class Grid {
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;
} else if (typeof a === 'string' && typeof b === 'string') {
a = a.toLowerCase();
b = b.toLowerCase();
} 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 a === b ? 0 : (a > b ? 1 : -1) * direction;
};
@ -3489,25 +3504,32 @@ export class Grid {
};
}
}
const filterAsValue = col.filterAsValue;
const type = this._var.colTypes[col.key];
const isDateColumn = type === GridDateColumn || type instanceof GridDateColumn;
array = Object.values(dict)
.sort((a, b) => {
// if (a == null && b == null) {
// return 0;
// }
// if (a == null && b != null) {
// return -1;
// }
// if (a != null && b == null) {
// return 1;
// }
// if (Object.prototype.hasOwnProperty.call(a, 'Value')) {
// a = a.Value;
// }
// if (Object.prototype.hasOwnProperty.call(b, 'Value')) {
// b = b.Value;
// }
// return a > b ? 1 : a < b ? -1 : 0;
return a.Value > b.Value ? 1 : a.Value < b.Value ? -1 : 0;
.sort((itemA, itemB) => {
let a = itemA.Value;
let b = itemB.Value;
if (a instanceof Date || b instanceof Date) {
if (a == null) {
a = 0;
} else if (b == null) {
b = 0;
}
} else if (a != null && b == null) {
return 1;
} else {
if (!filterAsValue && !isDateColumn) {
a = itemA.DisplayValue;
b = itemB.DisplayValue;
}
if (typeof a === 'string' && typeof b === 'string') {
a = a.toLowerCase();
b = b.toLowerCase();
}
}
return a > b ? 1 : (a < b ? -1 : 0);
});
}
array = array.map(i => {
@ -3640,7 +3662,7 @@ export class Grid {
const display = Object.prototype.hasOwnProperty.call(item, 'DisplayValue') ? item.DisplayValue : item;
div.appendChild(createCheckbox({
checked: item.__checked,
label: display && String(display).replace(/(\r\n|\n|<br[ \t]*\/?>)/g, '\u00a0'),
label: display && String(display).replace(/( |\r\n|\n|<br[ \t]*\/?>)/g, '\u00a0'),
title: display,
onchange: e => {
item.__checked = e.target.checked;

File diff suppressed because one or more lines are too long

6
package-lock.json generated
View File

@ -2041,9 +2041,9 @@
}
},
"node_modules/electron-to-chromium": {
"version": "1.4.715",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.715.tgz",
"integrity": "sha512-XzWNH4ZSa9BwVUQSDorPWAUQ5WGuYz7zJUNpNif40zFCiCl20t8zgylmreNmn26h5kiyw2lg7RfTmeMBsDklqg==",
"version": "1.4.717",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.717.tgz",
"integrity": "sha512-6Fmg8QkkumNOwuZ/5mIbMU9WI3H2fmn5ajcVya64I5Yr5CcNmO7vcLt0Y7c96DCiMO5/9G+4sI2r6eEvdg1F7A==",
"dev": true
},
"node_modules/entities": {