import '@maicol07/material-web-additions/data-table/data-table-cell.js'; import '@maicol07/material-web-additions/data-table/data-table-column.js'; import '@maicol07/material-web-additions/data-table/data-table-footer.js'; import '@maicol07/material-web-additions/data-table/data-table-row.js'; import { DataTable as MdDataTable, PaginateDetail, RowSelectionChangedDetail } from '@maicol07/material-web-additions/data-table/internal/data-table'; import {DataTableCell} from '@maicol07/material-web-additions/data-table/internal/data-table-cell'; import {mdiDeleteOutline} from '@mdi/js'; import DataTable, {DataTableAttributes} from '@osm/Components/DataTable/DataTable'; import DataTableColumn, {DataTableColumnAttributes} from '@osm/Components/DataTable/DataTableColumn'; import RecordsTableColumn from '@osm/Components/DataTable/RecordsTableColumn'; import MdIcon from '@osm/Components/MdIcon'; import Model from '@osm/Models/Record'; import {isVnode} from '@osm/utils/misc'; import collect, {Collection} from 'collect.js'; import { Children, Vnode, VnodeDOM } from 'mithril'; import {Class} from 'type-fest'; export interface RecordsTableColumnAttributes extends DataTableColumnAttributes { label?: string; } export interface RecordsTableAttributes extends DataTableAttributes { cols: Collection | Collection | Collection; records: Map; readonly?: boolean; selectable?: boolean; onTableRowClick?(recordId: string, event: MouseEvent): void; onDeleteRecordButtonClick?(recordId: string, event: MouseEvent): void; onDeleteSelectedRecordsButtonClick?(recordsIds: string[], event: MouseEvent): void; onRowSelectionChanged?(selectedRecordsIds: string[], event: CustomEventInit): void; onPageChange?(event: CustomEventInit): void; valueModifier?(value: any, attribute: string, record: M): any; } export default class RecordsTable = RecordsTableAttributes> extends DataTable { element!: MdDataTable; selectedRecordsIds: string[] = []; oninit(vnode: Vnode) { super.oninit(vnode); this.setDefaultAttributes(vnode); } onbeforeupdate(vnode: VnodeDOM) { super.onbeforeupdate(vnode); this.setDefaultAttributes(vnode); } onupdate(vnode: VnodeDOM) { super.onupdate(vnode); // Clear selected rows if new records are not included for (const id of this.selectedRecordsIds) { if (!vnode.attrs.records.has(id)) { this.selectedRecordsIds.splice(this.selectedRecordsIds.indexOf(id), 1); } } } setDefaultAttributes(vnode: Vnode) { vnode.attrs.paginated ??= true; vnode.attrs['current-page-size'] ??= 10; // @ts-ignore - False positive vnode.attrs['custom-pagination'] ??= true; } contents(vnode: Vnode) { return [ this.tableColumns(vnode).values().all(), vnode.attrs.records.size === 0 ? this.noRecordsContent(vnode) : this.tableRows(vnode), this.tableFooter(vnode) ]; } protected tableColumns(vnode: Vnode) { let columns: Collection = collect({}); if (vnode.attrs.selectable) { columns.put('checkbox', ); } // noinspection NestedFunctionJS function isDataTableColumn(column: Vnode>): boolean { return (typeof column.tag !== 'string' && (column.tag as Class).prototype instanceof DataTableColumn); } columns = columns.merge(vnode.attrs.cols.map((column: Children | RecordsTableColumnAttributes, attribute: string) => { // If the column is a vnode, and it is a DataTableColumn or a string that matches the tag name of a DataTableColumn, then use it as is. if (isVnode>(column) && (column.tag === 'md-data-table-column' || isDataTableColumn(column))) { column.key ??= attribute; column.attrs['data-model-attribute'] ??= attribute; return column; } let attributes: DataTableColumnAttributes = {}; let children: Children | RecordsTableColumnAttributes = column; if (RecordsTable.isRecordTableColumnAttributes(column)) { children = column.label ?? attribute; attributes = column; } attributes['data-model-attribute'] ??= attribute; // Otherwise, wrap it in a DataTableColumn return {children}; }).all()); if (!vnode.attrs.readonly) { columns.put('actions', this.tableActionsColumn()); } return columns; } protected tableActionsColumn(): Children { return ( {__('Azioni')} ); } noRecordsContent(vnode: Vnode): Children { const colspan = vnode.attrs.cols.count() + (vnode.attrs.selectable ? 1 : 0) + (vnode.attrs.readonly ? 0 : 1); // noinspection JSXDomNesting return ( {__('Nessun record trovato')} ); } protected tableRows(vnode: Vnode) { return [...this.tableRowsData(vnode)].map(([recordId, row]): Children => ( {vnode.attrs.selectable && } {row.map((cell, attribute: string) => {cell}).values().all()} )); } protected tableRowsData(vnode: Vnode) { const rows = new Map>(); for (const record of vnode.attrs.records.values()) { const cells = collect({}); for (const attribute of vnode.attrs.cols.keys()) { const value = this.getModelValue(record, attribute, vnode); cells.put(attribute, value); } if (!vnode.attrs.readonly) { cells.put('actions', this.tableRowActions(vnode, record).values().all()); } rows.set(record.id!, cells); } return rows; } protected tableRowActions(vnode: Vnode, record: M) { return collect({ delete: ( ) }); } protected tableFooter(vnode: Vnode) { return ( {__('Seleziona un\'azione per la selezione multipla:')} {this.tableFooterActions(vnode).values().all()} ); } protected tableFooterActions(vnode: Vnode) { return collect({ delete: ( {__('Elimina selezionati')} ) }); } oncreate(vnode: VnodeDOM) { super.oncreate(vnode); this.element.addEventListener('row-selection-changed', this.onRowSelectionChanged.bind(this, vnode)); this.element.addEventListener('paginate', this.onTablePaginate.bind(this, vnode)); } protected onRowSelectionChanged(vnode: Vnode, event: CustomEventInit) { const recordId = event.detail!.row.dataset.modelId!; if (event.detail!.selected && !this.selectedRecordsIds.includes(recordId)) { this.selectedRecordsIds.push(recordId); } else if (!event.detail!.selected && this.selectedRecordsIds.includes(recordId)) { this.selectedRecordsIds.splice(this.selectedRecordsIds.indexOf(recordId), 1); } vnode.attrs.onRowSelectionChanged?.(this.selectedRecordsIds, event); } protected onTableRowClick(vnode: Vnode, recordId: string, event: MouseEvent) { if (event.target instanceof DataTableCell && event.target.type === 'checkbox') { event.target.dispatchEvent(new CustomEvent('checked', { detail: { checked: !event.target?.checkbox?.checked } })); return; } vnode.attrs.onTableRowClick?.(recordId, event); } protected onTablePaginate(vnode: Vnode, event: CustomEventInit) { vnode.attrs.onPageChange?.(event); } protected onDeleteRecordButtonClicked(vnode: Vnode, record: M, event: MouseEvent) { event.stopPropagation(); vnode.attrs.onDeleteRecordButtonClick?.(record.id!, event); } protected onDeleteSelectedRecordsButtonClicked(vnode: Vnode, event: MouseEvent) { vnode.attrs.onDeleteSelectedRecordsButtonClick?.(this.selectedRecordsIds, event); } protected getModelValue(record: M, attribute: string, vnode: Vnode): unknown { // Check if is a relation // @ts-expect-error let value: unknown = record[attribute]; if (attribute === 'id') { value = record.id; } return vnode.attrs.valueModifier?.(value, attribute, record) ?? value; } private static isRecordTableColumnAttributes(column: Children | RecordsTableColumnAttributes): column is RecordsTableColumnAttributes { return typeof column === 'object' && 'label' in (column ?? {}); } }