From c27d44872b92611803330364ba3ce5169f874769 Mon Sep 17 00:00:00 2001 From: Tsanie Lily Date: Thu, 6 Apr 2023 00:08:03 +0800 Subject: [PATCH] optimized --- css/grid.scss | 6 +- lib/ui/checkbox.html | 23 +++---- lib/ui/dropdown.d.ts | 4 +- lib/ui/dropdown.html | 32 +++++----- lib/ui/dropdown.js | 7 +-- lib/ui/grid.d.ts | 81 ++++++++++++++++++++---- lib/ui/grid.html | 133 ++++++++++++++++++++++++++++++++++++++- lib/ui/grid.js | 75 +++++++++++++--------- lib/utility/lgres.html | 4 +- lib/utility/request.html | 12 ++-- style.css | 28 ++++++--- 11 files changed, 314 insertions(+), 91 deletions(-) diff --git a/css/grid.scss b/css/grid.scss index d4e29c1..e235ea6 100644 --- a/css/grid.scss +++ b/css/grid.scss @@ -105,7 +105,11 @@ align-items: center; padding: var(--header-padding); box-sizing: border-box; - overflow-x: hidden; + // overflow-x: hidden; + + >span { + overflow: hidden; + } } >.arrow { diff --git a/lib/ui/checkbox.html b/lib/ui/checkbox.html index 985200d..377eb89 100644 --- a/lib/ui/checkbox.html +++ b/lib/ui/checkbox.html @@ -22,43 +22,43 @@ onchange?: (this: HTMLInputElement, ev: Event) => any; }

-

type?: string

+

type?: string

复选框图标的样式,可选值目前有 fa-regularfa-lightfa-solid

-

label?: string | HTMLElement

+

label?: string | HTMLElement

复选框的标签文本,或者想要呈现的元素

-

checked?: boolean

+

checked?: boolean

初始是否选中

-

isImage?: boolean

+

isImage?: boolean

是否为图片复选框

-

imageHeight?: Number

+

imageHeight?: Number

为图片复选框时的图片限制高度

-

checkedNode?: HTMLElement

+

checkedNode?: HTMLElement

为图片复选框时的选中时显示的元素

-

uncheckedNode?: HTMLElement

+

uncheckedNode?: HTMLElement

为图片复选框时的未选中时显示的元素

-

customerAttributes?: { [key: string]: string }

+

customerAttributes?: { [key: string]: string }

自定义属性,例如 -

{
+  
{
     'data-id': 'xxxxxx',
     'disabled': ''
 }

-

onchange?: (this: HTMLInputElement, ev: Event) => any

+

onchange?: (this: HTMLInputElement, ev: Event) => any

复选框改变时触发的事件

@@ -66,7 +66,8 @@ function resolveCheckbox(container?: HTMLElement, legacy?: boolean): HTMLElement

container?: HTMLElement

- 将把此 HTML 元素,为 null 则把 document.body 下的所有 label[data-checkbox] 元素解析为复选框,[data-id] 为复选框元素的 id,包含 + 将把此 HTML 元素,为 null 则把 document.body 下的所有 label[data-checkbox] 元素解析为复选框,[data-id] 为复选框元素的 + id,包含 [data-checked] 时复选框默认选中。

当该元素无子元素时,[data-type] 同上述参数中的 type?: string[data-label] 同上述参数中的 diff --git a/lib/ui/dropdown.d.ts b/lib/ui/dropdown.d.ts index cd2b3b6..8c7263b 100644 --- a/lib/ui/dropdown.d.ts +++ b/lib/ui/dropdown.d.ts @@ -4,7 +4,7 @@ interface DropdownItem { html?: HTMLElement } -interface DropdownOptions { +export interface DropdownOptions { textkey?: string; valuekey?: string; htmlkey?: string; @@ -32,7 +32,7 @@ interface Dropdown { get disabled(): boolean; set disabled(flag: boolean); get source(): Array; - set source(list: Array): void; + set source(list: Array); readonly multiselect: boolean; readonly selected: DropdownItem | any; readonly selectedlist: Array; diff --git a/lib/ui/dropdown.html b/lib/ui/dropdown.html index 68d899a..902fcd9 100644 --- a/lib/ui/dropdown.html +++ b/lib/ui/dropdown.html @@ -28,67 +28,67 @@ parent?: HTMLElement; }

-

textkey?: string

+

textkey?: string

数据源中用以显示的属性,默认为 text

-

valuekey?: string

+

valuekey?: string

数据源中作为值的属性,默认为 value

-

htmlkey?: string

+

htmlkey?: string

数据源中用来以 html 方式呈现的属性,默认为 html

-

maxlength?: Number

+

maxlength?: Number

作为输入类型时的最大允许字符数,默认为 500

-

multiselect?: boolean

+

multiselect?: boolean

是否允许多选,仅在选择类型下有效

-

selected?: string

+

selected?: string

默认选中的项目的值

-

selectedlist?: Array<DropdownItem | any>

+

selectedlist?: Array<DropdownItem | any>

多选时默认选中的项目的值的列表

-

disabled?: boolean

+

disabled?: boolean

初始化时下拉框是否禁用

-

input?: boolean

+

input?: boolean

是否为输入类型

-

search?: boolean

+

search?: boolean

是否允许搜索

-

searchkeys?: Array<string>

+

searchkeys?: Array<string>

搜索时搜索的数据源属性的列表,默认为 [valuekey]

-

searchplaceholder?: string

+

searchplaceholder?: string

搜索输入框的占位字符串

-

tabindex?: Number

+

tabindex?: Number

下拉框的焦点顺序

-

placeholder?: string

+

placeholder?: string

作为输入类型时,输入框的占位字符串

-

slidefixed?: boolean

+

slidefixed?: boolean

下拉方向是否固定为下

-

parent?: HTMLElement

+

parent?: HTMLElement

下拉列表呈现的父容器,默认为 document.body

diff --git a/lib/ui/dropdown.js b/lib/ui/dropdown.js index 3aa37a6..ecec701 100644 --- a/lib/ui/dropdown.js +++ b/lib/ui/dropdown.js @@ -231,8 +231,7 @@ class Dropdown { let item = this.source.find(it => it[valuekey] === selected); if (this.#options.input) { if (item == null) { - item = {}; - item[valuekey] = selected; + item = { [valuekey]: selected }; } this.#label.value = selected; } else { @@ -266,9 +265,7 @@ class Dropdown { const itemlist = selectedlist.map(v => { let item = source.find(it => it[valuekey] === v); if (item == null) { - item = {}; - item[valuekey] = v; - item[textkey] = v; + item = { [valuekey]: v, [textkey]: v }; } return item; }); diff --git a/lib/ui/grid.d.ts b/lib/ui/grid.d.ts index 3dddd85..db26508 100644 --- a/lib/ui/grid.d.ts +++ b/lib/ui/grid.d.ts @@ -1,16 +1,59 @@ -interface GridColumn { - static create(): HTMLElement; - static setValue(element: HTMLElement, val: any): void; - static setStyle(element: HTMLElement, style: { [key: string]: string }): void; +import { DropdownOptions } from "./dropdown"; + +interface GridItem { + value: any; + displayValue: string; } -interface GridColumn { +declare var GridColumn: { + create(): HTMLElement; + createEdit(trigger: (e: any) => void, col: GridColumnDefinition, body: HTMLElement): HTMLElement; + setValue(element: HTMLElement, val: any): void; + getValue(e: any): any; + setStyle(element: HTMLElement, style: { [key: string]: string }): void; + setEnabled(element: HTMLElement, enabled?: boolean): void; +} + +interface GridColumnType { + 0: "Common"; + 1: "Input"; + 2: "Dropdown"; + 3: "Checkbox"; + 4: "Icon"; +} + +interface GridColumnDefinition { key?: string; + type?: keyof GridColumnType | typeof GridColumn; + caption?: string; + width?: Number; + align?: "left" | "center" | "right"; + enabled?: boolean | string; + css?: { [key: string]: string }; + styleFilter?: (item: GridItem | any) => { [key: string]: string }; + textStyle?: { [key: string]: string }; + visible?: boolean; + resizable?: boolean; + sortable?: boolean; + orderable?: boolean; + allcheck?: boolean; + events?: { [event: string]: any }; + attrs?: { [key: string]: string } | ((item: GridItem | any) => { [key: string]: string }); + // TODO: allowFilter?: boolean; + filter?: (item: GridItem | any) => any; + sortFilter?: (a: GridItem | any, b: GridItem | any) => -1 | 0 | 1; + bgFilter?: (item: GridItem | any) => string; + dropOptions?: DropdownOptions; + source?: Array | ((item: GridItem | any) => Array); + iconType?: string; + text?: string; + tooltip?: string; + onallchecked?: (this: Grid, col: GridColumnDefinition, flag: boolean) => void; + onchanged?: (this: Grid, item: GridItem | any, value: boolean | any) => void; } interface GridColumnDirection { [-1]: Number, - 0: Number, 1: Number } @@ -21,7 +64,7 @@ interface GridColumnColumnEventMap { } interface Grid { - columns: Array; + columns: Array; langs?: { all: string, ok: string, reset: string }; virtualCount?: Number; rowHeight?: Number; @@ -43,10 +86,26 @@ interface Grid { rowDblClicked?: (index: Number) => void; columnChanged?: (type: K, index: Number, value: Number | keyof GridColumnDirection) => void; - source(): Array; - source(list: Array): void; + get source(): Array; + set source(list: Array); + get selectedIndexes(): Array; + set selectedIndexes(indexes: Array); + get loading(): boolean; + set loading(flag: boolean); + get scrollTop(): Number; + set scrollTop(top: Number); - + readonly virtual: boolean; + readonly sortKey: string | undefined; + readonly selectedIndex: Number | -1; + + init(container?: HTMLElement): void; + scrollToIndex(index: Number): void; + resize(force?: boolean): void; + reload(): void; + refresh(): void; + resetChange(): void; + sortColumn(reload?: boolean): void; } declare var Grid: { @@ -58,7 +117,7 @@ declare var Grid: { Icon: 4, isCheckbox(type: Number): boolean; }; - GridColumn: GridColumn; + GridColumn: typeof GridColumn; new(container: HTMLElement): Grid; } diff --git a/lib/ui/grid.html b/lib/ui/grid.html index 671b877..2f4072f 100644 --- a/lib/ui/grid.html +++ b/lib/ui/grid.html @@ -4,6 +4,137 @@

创建一个统一样式的滚动列表元素。

+

constructor

+ new(container: HTMLElement): Grid +

container: HTMLElement

+

+ 父容器元素,Grid 将创建在此元素之内 +

+

init

+ init(container?: HTMLElement): void +

container?: HTMLElement

+

+ 父容器元素,默认使用构造函数中传递的值 +

+

scrollToIndex

+ scrollToIndex(index: Number): void +

index: Number

+

+ 将滚动至此行 +

+

resize

+ resize(force?: boolean): void +

force?: boolean

+

+ 重新计算大小,参数表示是否强制重载 +

+

reload

+ reload(): void +

+ 重载表格元素 +

+

refresh

+ refresh(): void +

+ 刷新表格单元格值 +

+

resetChange

+ resetChange(): void +

+ 还原表格修改状态,置为未修改 +

+

sortColumn

+ sortColumn(reload?: boolean): void +

reload?: boolean

+

+ 根据当前设定的排序列排序,参数表示是否重载表格 +

+
+

sortIndex

+ sortIndex?: Number +

+ 排序的列序号 +

+

sortDirection

+ sortDirection?: keyof GridColumnDirection +

+ 排序的方向,可选值为 -1: desc1: asc +

+

columns

+ columns: Array<GridColumnDefinition> +

+ 表格列的定义,结构为 +

interface GridColumnDefinition {
+      key?: string;
+      type?: keyof GridColumnType | typeof GridColumn;
+      caption?: string;
+      width?: Number;
+      align?: "left" | "center" | "right";
+      enabled?: boolean | string;
+      css?: { [key: string]: string };
+      styleFilter?: (item: GridItem | any) => { [key: string]: string };
+      textStyle?: { [key: string]: string };
+      visible?: boolean;
+      resizable?: boolean;
+      sortable?: boolean;
+      orderable?: boolean;
+      allcheck?: boolean;
+      events?: { [event: string]: any };
+      attrs?: { [key: string]: string } | ((item: GridItem | any) => { [key: string]: string });
+      filter?: (item: GridItem | any) => any;
+      sortFilter?: (a: GridItem | any, b: GridItem | any) => -1 | 0 | 1;
+      bgFilter?: (item: GridItem | any) => string;
+      dropOptions?: DropdownOptions;
+      source?: Array<any> | ((item: GridItem | any) => Array<any>);
+      iconType?: string;
+      text?: string;
+      tooltip?: string;
+      onallchecked?: (this: Grid, col: GridColumnDefinition, flag: boolean) => void;
+      onchanged?: (this: Grid, item: GridItem | any, value: boolean | any) => void;
+  }
+

+

key

+ key?: string +

关键字

+

type

+ type?: keyof GridColumnType | typeof GridColumn +

列类型,可以为 Grid.ColumnTypes 枚举值表示内置的通用、输入、下拉、复选框、图标等类型 +

declare var ColumnTypes: {
+      Common: 0,
+      Input: 1,
+      Dropdown: 2,
+      Checkbox: 3,
+      Icon: 4,
+      isCheckbox(type: Number): boolean;
+  }
也可以为符合 GridColumn 接口的类或对象表示自定义类型,接口定义如下 +
interface GridColumn {
+    static create(): HTMLElement;
+    static createEdit(trigger: (e: any) => void, col: GridColumnDefinition, body: HTMLElement): HTMLElement;
+    static setValue(element: HTMLElement, val: any): void;
+    static getValue(e: any): any;
+    static setStyle(element: HTMLElement, style: { [key: string]: string }): void;
+    static setEnabled(element: HTMLElement, enabled?: boolean): void;
+}
+

+

create

+ static create(): HTMLElement +

创建只读状态的单元格元素

+

createEdit

+ static createEdit(trigger: (e: any) => void, col: GridColumnDefinition, body: HTMLElement): HTMLElement +

创建编辑状态的单元格元素

+ trigger: (e: any) => void +

用以触发 Grid 的列修改事件 columnChanged 的函数代理

+ col: GridColumnDefinition +

当前列的定义对象

+ body: HTMLElement +

Grid 正文表格元素

+

setValue

+ static setValue(element: HTMLElement, val: any): void +

设置单元格值时触发的函数

+ element: HTMLElement +

单元格元素

+ val: any +

单元格的值


示例


@@ -38,7 +169,7 @@
       grid.allowHtml = true;
       grid.height = 0;
       grid.columns = [
-        { key: 'c1', caption: 'column 122222222311111111111111111' },
+        { key: 'c1', caption: 'column 122222222311111111111111111', shrink: true },
         { key: 'c2', caption: '选择', allcheck: true, type: Grid.ColumnTypes.Checkbox, enabled: 'enabled' },
         {
           key: 'c2a',
diff --git a/lib/ui/grid.js b/lib/ui/grid.js
index c2ac56c..97a0e6b 100644
--- a/lib/ui/grid.js
+++ b/lib/ui/grid.js
@@ -78,8 +78,8 @@ class GridInputColumn extends GridColumn {
 const SymbolDropdown = Symbol.for('ui-dropdown');
 
 class GridDropdownColumn extends GridColumn {
-    static createEdit(trigger, parent) {
-        const drop = new Dropdown({ parent });
+    static createEdit(trigger, col, parent) {
+        const drop = new Dropdown({ ...col.dropOptions, parent });
         drop.onselected = trigger;
         return drop.create();
     }
@@ -459,7 +459,7 @@ class Grid {
         if (this.#needResize && widths.flag) {
             this.#needResize = false;
             this.columns.forEach((col, i) => {
-                if (!col.autoResize) {
+                if (!this.#get(col.key, 'autoResize')) {
                     return;
                 }
                 let width = widths[i];
@@ -535,7 +535,7 @@ class Grid {
                 return a === b ? 0 : (a > b ? 1 : -1) * direction;
             };
         } else {
-            comparer = (a, b) => col.sortFilter(a, b) * direction;
+            comparer = (a, b) => col.sortFilter(a.values, b.values) * direction;
         }
         this.#source.sort(comparer);
         // TODO: filter to currentSource;
@@ -566,10 +566,10 @@ class Grid {
             }
             // style
             const isCheckbox = Grid.ColumnTypes.isCheckbox(col.type);
-            if (col.width > 0 || col.shrink) {
-                col.autoResize = false;
+            if (col.width > 0) {
+                // col.autoResize = false;
             } else {
-                col.autoResize = true;
+                this.#set(col.key, 'autoResize', true);
                 this.#needResize = true;
                 sizer.innerText = col.caption ?? '';
                 let width = sizer.offsetWidth + 22;
@@ -585,22 +585,19 @@ class Grid {
             if (col.sortable !== false) {
                 col.sortable = true;
             }
-            if (col.shrink) {
-                col.style = { 'text-align': col.align };
-            } else {
-                const w = `${col.width}px`;
-                col.style = {
-                    'width': w,
-                    'max-width': w,
-                    'min-width': w,
-                    'text-align': col.align
-                };
-            }
+            const w = `${col.width}px`;
+            const style = {
+                'width': w,
+                'max-width': w,
+                'min-width': w,
+                'text-align': col.align
+            };
+            this.#set(col.key, 'style', style);
             // element
             const th = document.createElement('th');
             th.className = 'column';
             th.dataset.key = col.key;
-            for (let css of Object.entries(col.style)) {
+            for (let css of Object.entries(style)) {
                 th.style.setProperty(css[0], css[1]);
             }
             if (col.sortable) {
@@ -741,8 +738,9 @@ class Grid {
                     const cell = document.createElement('td');
                     if (col.visible !== false) {
                         cell.keyid = ((exists + i) << MaxColumnBit) | j;
-                        if (col.style != null) {
-                            for (let css of Object.entries(col.style)) {
+                        const style = this.#get(col.key, 'style');
+                        if (style != null) {
+                            for (let css of Object.entries(style)) {
                                 cell.style.setProperty(css[0], css[1]);
                             }
                         }
@@ -836,7 +834,7 @@ class Grid {
                 let element;
                 if (!isCheckbox && selectChanged && typeof type.createEdit === 'function') {
                     element = selected ?
-                        type.createEdit(e => this.#onRowChanged(e, startIndex + i, col, type.getValue(e)), this.#refs.bodyContent) :
+                        type.createEdit(e => this.#onRowChanged(e, startIndex + i, col, type.getValue(e)), col, this.#refs.bodyContent) :
                         type.create(col);
                     cell.replaceChildren(element);
                 } else {
@@ -856,7 +854,7 @@ class Grid {
                     type.setEnabled(element, enabled);
                 }
                 // auto resize
-                if (this.#needResize && col.autoResize) {
+                if (this.#needResize && this.#get(col.key, 'autoResize')) {
                     const width = element.scrollWidth + 12;
                     if (width > 0 && widths != null && (isNaN(widths[j]) || widths[j] < width)) {
                         widths[j] = width;
@@ -893,9 +891,10 @@ class Grid {
         // const oldwidth = col.width;
         const w = `${width}px`;
         col.width = width;
-        col.style.width = w;
-        col.style['max-width'] = w;
-        col.style['min-width'] = w;
+        const style = this.#get(col.key, 'style');
+        style.width = w;
+        style['max-width'] = w;
+        style['min-width'] = w;
         let element = this.#refs.header.children[index];
         element.style.width = w;
         element.style.maxWidth = w;
@@ -1060,6 +1059,23 @@ class Grid {
         return top;
     }
 
+    #get(key, name) {
+        const attr = this.#colAttrs[key];
+        if (attr == null) {
+            return null;
+        }
+        return attr[name];
+    }
+
+    #set(key, name, value) {
+        const attr = this.#colAttrs[key];
+        if (attr == null) {
+            this.#colAttrs[key] = { [name]: value };
+        } else {
+            attr[name] = value;
+        }
+    }
+
     #getRowTarget(target) {
         let parent;
         while ((parent = target.parentElement) != null && !parent.classList.contains('grid-row')) {
@@ -1073,8 +1089,7 @@ class Grid {
     }
 
     #onHeaderClicked(e, col, force) {
-        const attr = this.#colAttrs[col.key];
-        if (!force && attr != null && (attr.resizing || attr.dragging)) {
+        if (!force && (this.#get(col.key, 'resizing') || this.#get(col.key, 'dragging'))) {
             return;
         }
         if (!this.#notHeader(e.target.tagName)) {
@@ -1185,7 +1200,7 @@ class Grid {
                 setTimeout(() => delete attr.resizing);
                 if (attr.sizing) {
                     delete attr.sizing;
-                    col.autoResize = false;
+                    delete attr.autoResize;
                     this.#changeColumnWidth(index, width);
                     if (typeof this.columnChanged === 'function') {
                         this.columnChanged(ColumnChangedType.Resize, index, width);
@@ -1379,6 +1394,7 @@ class Grid {
             }
         }
     }
+
     #onRowDblClicked(e) {
         if (e.target.tagName === 'INPUT') {
             return;
@@ -1394,6 +1410,7 @@ class Grid {
             }
         }
     }
+
     #onRowChanged(_e, index, col, value) {
         if (this.#currentSource == null) {
             return;
diff --git a/lib/utility/lgres.html b/lib/utility/lgres.html
index a4d5a00..308c7e9 100644
--- a/lib/utility/lgres.html
+++ b/lib/utility/lgres.html
@@ -42,11 +42,11 @@
     callback?: (result: any) => void
 }
     

-

template?: string

+

template?: string

语言资源文件的后缀,资源文件 url 为 `language/${lgid}${template}`

-

callback?: (result: any) => void

+

callback?: (result: any) => void

资源初始化后的回调函数,可能在 DOM 加载完成之前触发。

diff --git a/lib/utility/request.html b/lib/utility/request.html index 5e90e7c..6f1c833 100644 --- a/lib/utility/request.html +++ b/lib/utility/request.html @@ -22,19 +22,19 @@ progress?: (this: XMLHttpRequestUpload, ev: ProgressEvent<XMLHttpRequestEventTarget>) => any }

-

method?: string

+

method?: string

请求类型,默认为 GET 或 POST

-

accept?: string

+

accept?: string

Accept 请求头的值

-

contentType?: string

+

contentType?: string

Content-Type 请求头的值

-

customerHeaders?: { [key: string]: string }

+

customerHeaders?: { [key: string]: string }

自定义请求头,例如

{
@@ -42,11 +42,11 @@
     'X-Auth-Token': 'xxxxxx'
 }

-

signal?: AbortSignal | null

+

signal?: AbortSignal | null

终止器的信号,用来控制终止请求任务

-

progress?: (this: XMLHttpRequestUpload, ev: ProgressEvent<XMLHttpRequestEventTarget>) => any

+

progress?: (this: XMLHttpRequestUpload, ev: ProgressEvent<XMLHttpRequestEventTarget>) => any

调用 upload 方法时在上传过程中触发的事件,返回进度参数

diff --git a/style.css b/style.css index cff88c4..68c45cc 100644 --- a/style.css +++ b/style.css @@ -24,7 +24,10 @@ input { font-family: var(--serif-font-family); } -code, kbd, pre, samp { +code, +kbd, +pre, +samp { font-family: var(--mono-font-family); background-color: var(--hover-color); padding: 0 10px; @@ -34,32 +37,43 @@ code { display: inline-block; } -pre { +pre, +samp { font-size: .875em; } -h2 + code { +h2+code { margin-left: 70px; position: relative; } -h2 + code::before { +h2+code::before { content: '签名:'; position: absolute; margin-left: -70px; } -h3 { +h3, +h4 { font-family: var(--mono-font-family); font-size: 1em; margin-left: 10px; /* font-weight: bold; */ } -h3 ~ p { +h3~p { margin-left: 10px; } +h4 { + font-size: .9em; + margin-block-end: .4em; +} + +h4+code { + font-size: .9rem; +} + a { font-weight: 500; color: #646cff; @@ -120,7 +134,7 @@ h1 { user-select: none; } -#directory>ul>li.title { +#directory>ul>li.title { margin: 20px 0 6px; font-weight: bold; font-size: 1.25em;