import Bind from "@web-atoms/core/dist/core/Bind"; import { BindableProperty } from "@web-atoms/core/dist/core/BindableProperty"; import { CancelToken } from "@web-atoms/core/dist/core/types"; import XNode from "@web-atoms/core/dist/core/XNode"; import AtomRepeater, { getParentRepeaterItem, SelectorCheckBox } from "./AtomRepeater"; import { EditableInput, getPropertyInfo, IPropertyInfo } from "./Editable"; import TableRepeater from "./TableRepeater"; import "./styles/data-grid.global.css"; const cellEventName = Symbol("cell-event-name"); const headerEventName = Symbol("header-event-name"); const footerEventName = Symbol("footer-event-name"); const fromHyphenToCamel = (input: string) => input.replace(/-([a-z])/ig, (g) => g[1].toUpperCase()); const toEventName = (name: string) => { const r = fromHyphenToCamel(name.replace(/\s+/, "-")); return r[0].toLowerCase() + r.substring(1); }; const getCellEventName = (d: IDataGridColumn) => { let name = d[cellEventName]; if (name !== void 0) { return name; } name = toEventName(d.cellClickEvent ?? `cell-${d.header}-click`); d[cellEventName] = name; return name; }; const getHeaderEventName = (d: IDataGridColumn) => { let name = d[headerEventName]; if (name !== void 0) { return name; } name = toEventName(d.headerClickEvent ?? `header-${d.header}-click`); d[headerEventName] = name; return name; }; const getFooterEventName = (d: IDataGridColumn) => { let name = d[footerEventName]; if (name !== void 0) { return name; } name = toEventName(d.footerClickEvent ?? `footer-${d.header}-click`); d[footerEventName] = name; return name; }; export interface IDataGridColumnBase { header: string; headerSort?: any; headerSortDescending?: any; headerSortDefault?: any; headerClickEvent?: string; cellClickEvent?: string; footerClickEvent?: string; headerRenderer?: (item) => XNode; footerRenderer?: (item) => XNode; headerClickHandler?: (e: CustomEvent) => void; cellClickHandler?: (e: CustomEvent) => void; footerClickHandler?: (e: CustomEvent) => void; /** * Default is true */ ellipsis?: boolean; width?: string; maxWidth?: string; minWidth?: string; } export interface IDataGridColumnWithLabel extends IDataGridColumnBase { label: string; labelPath?: never; cellRenderer?: never; } export interface IDataGridColumnWithLabelPath extends IDataGridColumnBase { labelPath: (item) => string; label?: never; cellRenderer?: never; } export interface IDataGridColumnWithCellRenderer extends IDataGridColumnBase { cellRenderer: (item) => XNode; label?: never; labelPath?: never; } export type IDataGridColumn = IDataGridColumnWithLabel | IDataGridColumnWithLabelPath | IDataGridColumnWithCellRenderer; // export interface IDataGridColumn { // header: string; // headerClickEvent?: string; // cellClickEvent?: string; // footerClickEvent?: string; // cellRenderer: (item) => XNode; // headerRenderer?: (item) => XNode; // footerRenderer?: (item) => XNode; // headerClickHandler?: (e: CustomEvent) => void; // cellClickHandler?: (e: CustomEvent) => void; // footerClickHandler?: (e: CustomEvent) => void; // } export const SelectAllColumn: IDataGridColumn = { header: "Select All", headerRenderer: () => x.allSelected)}/> , headerClickHandler: (e) => { const s = e.detail.repeater as AtomRepeater; const items = s.items; if (!(items)) { return; } const selectedItems = s.selectedItems ??= []; if (s.allSelected) { selectedItems.clear(); return; } selectedItems.length = 0; selectedItems.push(... items); selectedItems.refresh(); }, cellRenderer: () => }; export interface IEditorColumn { header: string; changeEvents?: string[]; editorValuePath?: (editor) => any; propertyPath: string[] | string | IPropertyInfo ; } export function InputColumn({ type = "text", header, propertyPath }: IEditorColumn & { type?: string}): IDataGridColumn { const property = getPropertyInfo(propertyPath); return ({ header, cellRenderer: (item) => , }); } export default class DataGrid extends TableRepeater { @BindableProperty public columns: IDataGridColumn[]; @BindableProperty public orderBy: any; private orderBySet: boolean; constructor(app, e = document.createElement("table")) { super(app, e); } public onPropertyChanged(name: keyof DataGrid): void { super.onPropertyChanged(name as any); if (name === "columns") { super.onPropertyChanged("header"); super.onPropertyChanged("footer"); super.onPropertyChanged("items"); } if (name === "orderBy") { if (!this.orderBySet) { this.onPropertyChanged("header"); } this.orderBySet = true; } } protected preCreate(): void { super.preCreate(); this.header = true; this.footer = null; this.element.dataset.dataGrid = "data-grid"; this.headerRenderer = (item) => { ... this.columns?.map?.((x) => { if (x.headerRenderer === void 0) { x.headerRenderer = (_) => { let order = this.orderBy; if (order !== void 0) { if (order === x.headerSort) { order = true; } else if (order === x.headerSortDescending) { order = false; } } return { typeof order === "boolean" && (order ? : ) } ; }; } const node = x.headerRenderer(item); const na = node.attributes ??= {}; na["data-click-event"] ??= getHeaderEventName(x); if (x.width !== void 0) { na["style-width"] ??= x.width; } else { if (x.maxWidth !== void 0) { na["style-max-width"] ??= x.maxWidth; } if (x.minWidth !== void 0) { na["style-min-width"] ??= x.minWidth; } } return node; }) ?? []} ; this.itemRenderer = (item) => { ... this.columns?.map?.((x) => { x.ellipsis ??= true; if (x.cellRenderer === void 0) { x.labelPath ??= (i) => i[x.label]; if (x.ellipsis) { x.cellRenderer = (i) => ; } else { x.cellRenderer = (i) => ; } } const node = x.cellRenderer?.(item); const na = node.attributes ??= {}; if (x.ellipsis) { na["data-ellipsis"] = "true"; } na["data-click-event"] ??= getCellEventName(x); if (x.width !== void 0) { na["style-width"] ??= x.width; } else { if (x.maxWidth !== void 0) { na["style-max-width"] ??= x.maxWidth; } if (x.minWidth !== void 0) { na["style-min-width"] ??= x.minWidth; } } return node; }) ?? []} ; this.footerRenderer = (item) => { ... this.columns?.map?.((x) => { const node = x.footerRenderer?.(item); if (node === void 0) { return node; } const na = node.attributes ??= {}; if (na["data-click-event"] === void 0) { na["data-click-event"] = getFooterEventName(x); } return node; }) ?? [] } ; } protected dispatchHeaderFooterEvent(eventName: any, type: any, recreate: boolean, originalTarget: any): void { let detail = this[type]; const column = this.columns.find((x) => getHeaderEventName(x) === eventName || getFooterEventName(x) === eventName); let order = this.orderBy; const originalOrder = this.orderBy; const isHeader = type === "header"; const setOrderBy = isHeader && column.headerSort !== void 0; if (isHeader) { if (setOrderBy) { if (order === column.headerSortDescending) { order = column.headerSort; } else if (order === column.headerSort) { order = column.headerSortDescending; } else { order = column.headerSortDefault ?? column.headerSort; } } detail = { repeater: this, detail, type, order }; } const ce = new CustomEvent(eventName ?? `${type}Click`, { detail, bubbles: this.bubbleEvents, cancelable: true }); originalTarget.dispatchEvent(ce); if (ce.defaultPrevented) { return; } this.invokeHandler(isHeader ? column.headerClickHandler : column.footerClickHandler, ce); if (ce.defaultPrevented) { return; } if (setOrderBy && this.orderBy === originalOrder) { this.orderBy = order; (ce as any).executed = true; } if ((ce as any).executed) { this.onPropertyChanged(type); } } protected dispatchItemEvent(eventName: any, item: any, recreate: any, originalTarget: any): void { const ce = new CustomEvent(eventName ?? "itemClick", { detail: item, bubbles: this.bubbleEvents, cancelable: true }); originalTarget.dispatchEvent(ce); if (ce.defaultPrevented) { return; } for (const iterator of this.columns) { if (getCellEventName(iterator) === eventName) { this.invokeHandler(iterator.cellClickHandler, ce); break; } } if (ce.defaultPrevented) { return; } if (eventName === "itemSelect" || eventName === "itemDeselect") { const si = this.selectedItems ??= []; if (si) { const index = si.indexOf(item); if (index === -1) { if (this.allowMultipleSelection) { si.add(item); } else { si.set(0, item); } } else { si.removeAt(index); } } } if (recreate && (ce as any).executed) { this.refreshItem(item, (ce as any).promise); } } private invokeHandler(h, e) { if (!h) { return; } const p = h(e); e.executed = true; if (p && p.then) { if (e.promise) { e.promise = Promise.all([e.promise, p]); } else { e.promise = p; } p?.catch((error) => { if (CancelToken.isCancelled(error)) { return; } // tslint:disable-next-line: no-console console.error(error); }); } } }