1
0
mirror of https://github.com/devcode-it/openstamanager.git synced 2025-06-05 22:09:38 +02:00

feat: 💥 Conversione a Typescript

- Rimossi Babel e Flow, sostituito con Typescript
- Qualche piccolo fix qua e là
- Aggiornate dipendenze
This commit is contained in:
Maicol Battistini
2022-01-06 15:45:35 +01:00
parent 8621a6aa88
commit 636c7ac68e
71 changed files with 4930 additions and 3817 deletions

View File

@@ -1,333 +0,0 @@
import '@material/mwc-linear-progress';
import '@material/mwc-list/mwc-list-item';
import '../../WebComponents/Select';
import {type Cash} from 'cash-dom/dist/cash';
import {
type Children,
type Vnode
} from 'mithril';
import PropTypes from 'prop-types';
import Component from '../Component.jsx';
import Mdi from '../Mdi.jsx';
import TableColumn from './TableColumn.jsx';
import TableFooter from './TableFooter.jsx';
import TableRow from './TableRow.jsx';
export default class DataTable extends Component {
static propTypes = {
'rows-per-page': PropTypes.number,
'default-rows-per-page': PropTypes.number,
'aria-label': PropTypes.string,
checkable: PropTypes.bool,
paginated: PropTypes.bool
};
rows: Cash[] = [];
columns: Children[];
footer: Children[];
rowsPerPage = {
options: [10, 25, 50, 75, 100],
currentStart: 0,
value: 10,
currentEnd: 10
}
onbeforeupdate(vnode) {
super.onbeforeupdate(vnode);
const children = (vnode.children: Children[]).flat();
this.rows = this.tableRows(children);
this.columns = this.filterElements(children, TableColumn);
this.footer = this.filterElements(children, TableFooter);
const rowsPerPage = this.attrs.get('rows-per-page');
if (rowsPerPage) {
this.rowsPerPage.options = rowsPerPage.split(',')
.map((value: string) => Number.parseInt(value, 10));
}
let defaultRowsPerPage = this.attrs.get('default-rows-per-page', 10);
if (typeof defaultRowsPerPage === 'string' && Number.isInteger(defaultRowsPerPage)) {
defaultRowsPerPage = Number.parseInt(defaultRowsPerPage, 10);
if (!this.rowsPerPage.options.includes(defaultRowsPerPage)) {
[defaultRowsPerPage] = this.rowsPerPage.options;
}
this.rowsPerPage.value = defaultRowsPerPage;
}
if (this.rowsPerPage.currentStart === 0) {
this.rowsPerPage.currentEnd = this.rowsPerPage.value >= this.rows.length ? this.rows.length
: defaultRowsPerPage;
}
}
onupdate(vnode) {
super.onupdate(vnode);
const rows: Cash = $(this.element).find('tbody tr');
rows.hide();
// eslint-disable-next-line no-plusplus
for (let index = this.rowsPerPage.currentStart; index < this.rowsPerPage.currentEnd; index++) {
rows.eq(index).show();
}
if (this.rowsPerPage.currentStart === 0) {
this.paginate('first');
}
$(this.element)
.find('thead th.mdc-data-table__header-cell--with-sort')
.on('click', this.onColumnClicked.bind(this));
$(this.element).find('.mdc-data-table__pagination-rows-per-page-select')
.on('selected', this.onPaginationSelected.bind(this));
$(this.element).find('.mdc-data-table__pagination-button')
.on('click', this.onPaginationButtonClicked.bind(this));
}
view(vnode) {
return <div className="mdc-data-table" {...this.attrs.all()}>
<div className="mdc-data-table__table-container">
<table className="mdc-data-table__table" aria-label={this.attrs.get('aria-label')}>
<thead>
<tr className="mdc-data-table__header-row">
{this.attrs.has('checkable') && <TableColumn type="checkbox"/>}
{this.columns}
</tr>
</thead>
<tbody className="mdc-data-table__content">
{this.rows}
</tbody>
{this.footer}
</table>
{this.attrs.has('paginated') && <div className="mdc-data-table__pagination">
<div className="mdc-data-table__pagination-trailing">
<div className="mdc-data-table__pagination-rows-per-page">
<div className="mdc-data-table__pagination-rows-per-page-label">
{__('Righe per pagina')}
</div>
<material-select
outlined
className="mdc-data-table__pagination-rows-per-page-select"
fixedMenuPosition
style="--mdc-select-width: 112px; --mdc-select-height: 36px; --mdc-menu-item-height: 36px;"
>
{this.rowsPerPage.options.map(
rowsPerPage => (
<mwc-list-item
key={rowsPerPage}
value={rowsPerPage}
selected={this.rowsPerPage.value === rowsPerPage}
>
{rowsPerPage}
</mwc-list-item>
)
)}
</material-select>
</div>
<div className="mdc-data-table__pagination-navigation">
<div className="mdc-data-table__pagination-total">
{__(':start-:chunk di :total', {
start: this.rowsPerPage.currentStart + 1,
chunk: this.rowsPerPage.currentEnd,
total: this.rows.length
})}
</div>
<mwc-icon-button className="mdc-data-table__pagination-button" data-page="first"
disabled>
<Mdi icon="page-first"/>
</mwc-icon-button>
<mwc-icon-button className="mdc-data-table__pagination-button" data-page="previous"
disabled>
<Mdi icon="chevron-left"/>
</mwc-icon-button>
<mwc-icon-button className="mdc-data-table__pagination-button" data-page="next">
<Mdi icon="chevron-right"/>
</mwc-icon-button>
<mwc-icon-button className="mdc-data-table__pagination-button" data-page="last">
<Mdi icon="page-last"/>
</mwc-icon-button>
</div>
</div>
</div>}
<div className="mdc-data-table__progress-indicator">
<div className="mdc-data-table__scrim"/>
<mwc-linear-progress className="mdc-data-table__linear-progress" indeterminate/>
</div>
</div>
</div>;
}
tableRows(children: Children[]): Children[] {
let rows = this.filterElements(children, TableRow);
if (this.attrs.has('checkable')) {
rows = rows.map((row: Vnode) => (
<TableRow key={row.attrs.key} checkable {...row.attrs}>
{row.children}
</TableRow>
));
}
return rows;
}
filterElements(elements: Children[], tag: Component | string): Children[] {
const filtered = [];
for (const element: Vnode of elements) {
if (element.tag === tag) {
filtered.push(element);
}
}
return filtered;
}
oncreate(vnode) {
super.oncreate(vnode);
}
showProgress() {
$(this.element)
.addClass('mdc-data-table--in-progress')
.find('.mdc-data-table__progress-indicator mwc-linear-progress')
.get(0)
.open();
}
hideProgress() {
$(this.element)
.removeClass('mdc-data-table--in-progress')
.find('.mdc-data-table__progress-indicator mwc-linear-progress')
.get(0)
.open();
}
onColumnClicked(event: Event) {
this.showProgress();
const column: Cash = $(event.target)
.closest('th');
const ascendingClass = 'mdc-data-table__header-cell--sorted';
// Clean previously sorted info and arrows
const columns = $(this.element)
.find('thead th');
columns.removeClass(ascendingClass);
columns.off('click').on('click', this.onColumnClicked.bind(this));
// Add ony one header to sort
column.addClass(ascendingClass);
// Do sorting
this.sortTable(column.attr('id'), false);
// Set/remove callbacks
column.off('click');
column.find('mwc-icon-button-toggle').on('click', () => {
this.sortTable(column.attr('id'));
});
}
sortTable(columnId: number, toggleClass = true) {
const column: Cash = $(`#${columnId}`);
const cells = $(this.element).find(`tr td:nth-child(${column.index() + 1})`).get();
// Handle button class
if (toggleClass) {
column.toggleClass('mdc-data-table__header-cell--sorted-descending');
}
const isNumeric = column.attr('type') === 'numeric';
const isDescending = column.hasClass('mdc-data-table__header-cell--sorted-descending');
cells.sort((a: HTMLElement, b: HTMLElement) => {
let aValue = a.textContent;
let bValue = b.textContent;
if (isNumeric) {
aValue = Number.parseFloat(aValue);
bValue = Number.parseFloat(bValue);
}
if (!isDescending) {
const temporary = aValue;
aValue = bValue;
bValue = temporary;
}
if (typeof aValue === 'string') {
return aValue.localeCompare(bValue);
}
return aValue < bValue ? -1 : (aValue > bValue ? 1 : 0);
});
for (const cell of cells) {
const row = $(cell)
.parent();
row.appendTo(row.parent());
}
this.hideProgress();
}
onPaginationSelected(event: Event) {
this.rowsPerPage.value = $(event.target).find('mwc-list-item').eq(event.detail.index).val();
this.rowsPerPage.currentStart = 0;
this.rowsPerPage.currentEnd = this.rowsPerPage.value;
m.redraw();
}
onPaginationButtonClicked(event: Event) {
const button: Cash = $(event.target);
this.paginate(button.data('page'));
m.redraw();
}
paginate(action: 'first' | 'next' | 'previous' | 'last') {
const increments = {
first: -this.rowsPerPage.currentStart,
next: this.rowsPerPage.value,
previous: -this.rowsPerPage.value,
last: this.rows.length - this.rowsPerPage.currentStart
};
const increment = increments[action];
if (action !== 'first') {
this.rowsPerPage.currentStart += increment;
}
if (action !== 'last') {
this.rowsPerPage.currentEnd += increment;
}
const paginationButtons: Cash = $(this.element).find('.mdc-data-table__pagination-button');
const disabled = {
first: this.rowsPerPage.currentStart === 0,
previous: this.rowsPerPage.currentStart === 0,
next: this.rowsPerPage.currentEnd >= this.rows.length,
last: this.rowsPerPage.currentEnd >= this.rows.length
};
for (const button of paginationButtons) {
const buttonElement = $(button);
const buttonAction = buttonElement.data('page');
buttonElement.prop('disabled', disabled[buttonAction]);
}
}
}

View File

@@ -0,0 +1,376 @@
import '@material/mwc-linear-progress';
import '@material/mwc-list/mwc-list-item';
import '../../WebComponents/Select';
import type {LinearProgress as MWCLinearProgress} from '@material/mwc-linear-progress';
import type {Cash} from 'cash-dom/dist/cash';
import type {
Children,
Vnode,
VnodeDOM
} from 'mithril';
import Component from '../Component';
import Mdi from '../Mdi';
import TableColumn from './TableColumn';
import TableFooter from './TableFooter';
import TableRow, {type TableRowAttributes} from './TableRow';
declare global {
namespace JSX {
interface IntrinsicElements {
DataTable: DataTable
}
}
}
type PaginationAction = 'first' | 'next' | 'previous' | 'last';
export type Attributes = {
'rows-per-page'?: number,
'default-rows-per-page'?: number,
'aria-label'?: string,
checkable?: boolean,
paginated?: boolean
};
export default class DataTable extends Component<Attributes> {
rows: Children[] = [];
columns: Children[];
footer: Children[];
rowsPerPage = {
options: [10, 25, 50, 75, 100],
currentStart: 0,
value: 10,
currentEnd: 10
};
onbeforeupdate(vnode: VnodeDOM<Attributes, this>) {
super.onbeforeupdate(vnode);
const children = (vnode.children as Children[]).flat();
this.rows = this.tableRows(children);
this.columns = this.filterElements(children, TableColumn);
this.footer = this.filterElements(children, TableFooter);
const rowsPerPage = this.attrs.get('rows-per-page');
if (rowsPerPage) {
this.rowsPerPage.options = rowsPerPage
.split(',')
.map((value: string) => Number.parseInt(value, 10));
}
let defaultRowsPerPage: number = Number.parseInt(this.attrs.get('default-rows-per-page', '10') as string, 10);
if (Number.isInteger(defaultRowsPerPage)) {
if (!this.rowsPerPage.options.includes(defaultRowsPerPage)) {
[defaultRowsPerPage] = this.rowsPerPage.options;
}
this.rowsPerPage.value = defaultRowsPerPage;
}
if (this.rowsPerPage.currentStart === 0) {
this.rowsPerPage.currentEnd = this.rowsPerPage.value >= this.rows.length
? this.rows.length
: defaultRowsPerPage;
}
}
onupdate(vnode: VnodeDOM<Attributes, this>) {
super.onupdate(vnode);
const rows: Cash = $(this.element).find('tbody tr');
rows.hide();
// eslint-disable-next-line no-plusplus
for (
let index = this.rowsPerPage.currentStart;
index < this.rowsPerPage.currentEnd;
index += 1
) {
rows.eq(index).show();
}
if (this.rowsPerPage.currentStart === 0) {
this.paginate('first');
}
$(this.element)
.find('thead th.mdc-data-table__header-cell--with-sort')
.on('click', this.onColumnClicked.bind(this));
$(this.element)
.find('.mdc-data-table__pagination-rows-per-page-select')
.on('selected', this.onPaginationSelected.bind(this));
$(this.element)
.find('.mdc-data-table__pagination-button')
.on('click', this.onPaginationButtonClicked.bind(this));
}
view() {
return (
<div className="mdc-data-table" {...this.attrs.all()}>
<div className="mdc-data-table__table-container">
<table
className="mdc-data-table__table"
aria-label={this.attrs.get('aria-label')}
>
<thead>
<tr className="mdc-data-table__header-row">
{this.attrs.has('checkable') && <TableColumn type="checkbox" />}
{this.columns}
</tr>
</thead>
<tbody className="mdc-data-table__content">{this.rows}</tbody>
{this.footer}
</table>
{this.attrs.has('paginated') && (
<div className="mdc-data-table__pagination">
<div className="mdc-data-table__pagination-trailing">
<div className="mdc-data-table__pagination-rows-per-page">
<div className="mdc-data-table__pagination-rows-per-page-label">
{__('Righe per pagina')}
</div>
<material-select
outlined
className="mdc-data-table__pagination-rows-per-page-select"
fixedMenuPosition
// @ts-ignore
style="--mdc-select-width: 112px; --mdc-select-height: 36px; --mdc-menu-item-height: 36px;"
>
{this.rowsPerPage.options.map((rowsPerPage) => (
<mwc-list-item
key={rowsPerPage}
value={String(rowsPerPage)}
selected={this.rowsPerPage.value === rowsPerPage}
>
{rowsPerPage}
</mwc-list-item>
))}
</material-select>
</div>
<div className="mdc-data-table__pagination-navigation">
<div className="mdc-data-table__pagination-total">
{__(':start-:chunk di :total', {
start: this.rowsPerPage.currentStart + 1,
chunk: this.rowsPerPage.currentEnd,
total: this.rows.length
})}
</div>
<mwc-icon-button
className="mdc-data-table__pagination-button"
data-page="first"
disabled
>
<Mdi icon="page-first" />
</mwc-icon-button>
<mwc-icon-button
className="mdc-data-table__pagination-button"
data-page="previous"
disabled
>
<Mdi icon="chevron-left" />
</mwc-icon-button>
<mwc-icon-button
className="mdc-data-table__pagination-button"
data-page="next"
>
<Mdi icon="chevron-right" />
</mwc-icon-button>
<mwc-icon-button
className="mdc-data-table__pagination-button"
data-page="last"
>
<Mdi icon="page-last" />
</mwc-icon-button>
</div>
</div>
</div>
)}
<div className="mdc-data-table__progress-indicator">
<div className="mdc-data-table__scrim" />
<mwc-linear-progress
className="mdc-data-table__linear-progress"
indeterminate
/>
</div>
</div>
</div>
);
}
tableRows(children: Children[]): Children[] {
let rows = this.filterElements(children, TableRow);
if (this.attrs.has('checkable')) {
rows = rows.map<Children>((row: Children) => {
if (!row) {
return '';
}
const rowNode = row as Vnode<TableRowAttributes>;
return (
<TableRow key={rowNode.key} checkable {...rowNode.attrs}>
{rowNode.children}
</TableRow>
);
});
}
return rows;
}
filterElements(
elements: Children[],
tag: typeof TableRow | typeof TableColumn | typeof TableFooter | string
): Children[] {
const filtered = [];
for (const element of elements) {
if ((element as Vnode).tag === tag) {
filtered.push(element);
}
}
return filtered;
}
getProgress(): Element & Partial<MWCLinearProgress> | null {
return this.element.querySelector('.mdc-data-table__progress-indicator mwc-linear-progress');
}
showProgress() {
this.manageProgress(true);
}
hideProgress() {
this.manageProgress(false);
}
onColumnClicked(event: Event) {
this.showProgress();
const column: Cash = $(event.target as Element).closest('th');
const ascendingClass = 'mdc-data-table__header-cell--sorted';
// Clean previously sorted info and arrows
const columns = $(this.element).find('thead th');
columns.removeClass(ascendingClass);
columns.off('click').on('click', this.onColumnClicked.bind(this));
// Add ony one header to sort
column.addClass(ascendingClass);
// Do sorting
this.sortTable(column, false);
// Set/remove callbacks
column.off('click');
column.find('mwc-icon-button-toggle').on('click', () => {
this.sortTable(column);
});
}
sortTable(column: Cash, toggleClass = true) {
const cells = $(this.element)
.find(`tr td:nth-child(${column.index() + 1})`)
.get();
// Handle button class
if (toggleClass) {
column.toggleClass('mdc-data-table__header-cell--sorted-descending');
}
const isNumeric = column.attr('type') === 'numeric';
const isDescending = column.hasClass(
'mdc-data-table__header-cell--sorted-descending'
);
cells.sort((a: HTMLElement, b: HTMLElement) => {
let aValue: string | number = a.textContent as string;
let bValue: string | number = b.textContent as string;
if (isNumeric) {
aValue = Number.parseFloat(aValue);
bValue = Number.parseFloat(bValue);
}
if (!isDescending) {
const temporary = aValue;
aValue = bValue;
bValue = temporary;
}
if (typeof aValue === 'string' && typeof bValue === 'string') {
return aValue.localeCompare(bValue);
}
return aValue < bValue ? -1 : (aValue > bValue ? 1 : 0);
});
for (const cell of cells) {
const row = $(cell).parent();
row.appendTo(row.parent());
}
this.hideProgress();
}
onPaginationSelected(event: Event & {detail: {index: number}}) {
this.rowsPerPage.value = Number.parseInt(
$(event.target as Element)
.find('mwc-list-item')
.eq(event.detail.index)
.val() as string,
10
);
this.rowsPerPage.currentStart = 0;
this.rowsPerPage.currentEnd = this.rowsPerPage.value;
m.redraw();
}
onPaginationButtonClicked(event: Event) {
const button: Cash = $(event.target as Element);
this.paginate(button.data('page') as PaginationAction);
m.redraw();
}
paginate(action: PaginationAction) {
const increments = {
first: -this.rowsPerPage.currentStart,
next: this.rowsPerPage.value,
previous: -this.rowsPerPage.value,
last: this.rows.length - this.rowsPerPage.currentStart
};
const increment = increments[action];
if (action !== 'first') {
this.rowsPerPage.currentStart += increment;
}
if (action !== 'last') {
this.rowsPerPage.currentEnd += increment;
}
const paginationButtons = this.element.querySelectorAll(
'.mdc-data-table__pagination-button'
);
const disabled = {
first: this.rowsPerPage.currentStart === 0,
previous: this.rowsPerPage.currentStart === 0,
next: this.rowsPerPage.currentEnd >= this.rows.length,
last: this.rowsPerPage.currentEnd >= this.rows.length
};
for (const button of paginationButtons) {
const buttonElement = $(button);
const buttonAction = buttonElement.data('page') as PaginationAction;
buttonElement.prop('disabled', disabled[buttonAction]);
}
}
private manageProgress(show: boolean) {
$(this.element).toggleClass('mdc-data-table--in-progress');
const progress = this.getProgress();
if (progress) {
(progress as MWCLinearProgress)[show ? 'open' : 'close']();
}
}
}

View File

@@ -1,27 +1,41 @@
import {type Cash} from 'cash-dom/dist/cash';
import {inRange} from 'lodash-es';
import PropTypes from 'prop-types';
import type {Cash} from 'cash-dom/dist/cash';
import {inRange} from 'lodash';
import type {
Vnode,
VnodeDOM
} from 'mithril';
import Component from '../Component.jsx';
import Component from '../Component';
export default class TableCell extends Component {
static propTypes = {
type: PropTypes.string
};
declare global {
namespace JSX {
interface IntrinsicElements {
TableCell: TableCell;
}
}
}
view(vnode) {
type Attributes = {type?: string};
export default class TableCell extends Component<Attributes> {
view(vnode: Vnode) {
this.attrs.addClassNames('mdc-data-table__cell', {
[`mdc-data-table__cell--${this.attrs.get('type')}`]: this.attrs.has('type')
[`mdc-data-table__cell--${this.attrs.get('type') as string}`]: this.attrs.has(
'type'
)
});
if ((!vnode.children || vnode.children.length === 0) && this.attrs.get('type') === 'checkbox') {
vnode.children = <mwc-checkbox className="mdc-data-table__row-checkbox"/>;
if (
(!Array.isArray(vnode.children) || vnode.children.length === 0)
&& this.attrs.get('type') === 'checkbox'
) {
vnode.children = [<mwc-checkbox key="checkbox" className="mdc-data-table__row-checkbox" />];
}
return <td {...this.attrs.all()}>{vnode.children}</td>;
}
oncreate(vnode) {
oncreate(vnode: VnodeDOM<Attributes>) {
super.oncreate(vnode);
const checkboxes = (): Cash => $(this.element)
@@ -32,8 +46,9 @@ export default class TableCell extends Component {
cell.children('mwc-checkbox').on('change', () => {
const row = cell.parent();
row.toggleClass('mdc-data-table__row--selected');
const headerCheckbox = cell.closest('.mdc-data-table').find('thead th mwc-checkbox');
const headerCheckbox = cell
.closest('.mdc-data-table')
.find('thead th mwc-checkbox');
const checks = checkboxes();
const checked = checks.filter('[checked]');

View File

@@ -1,125 +0,0 @@
import '@material/mwc-icon-button-toggle';
import {type Cash} from 'cash-dom';
import PropTypes from 'prop-types';
import Component from '../Component.jsx';
import Mdi from '../Mdi.jsx';
export default class TableColumn extends Component {
static propTypes = {
type: PropTypes.oneOf(['numeric', 'checkbox']),
id: PropTypes.string,
sortable: PropTypes.bool,
filterable: PropTypes.bool
};
view(vnode) {
this.attrs.addClassNames('mdc-data-table__header-cell', {
[`mdc-data-table__header-cell--${this.attrs.get('type')}`]: this.attrs.has('type')
});
if (this.attrs.has('sortable')) {
this.attrs.addClassNames('mdc-data-table__header-cell--with-sort');
this.attrs.put('aria-sort', 'none').put('data-column-id', this.attrs.get('id'));
vnode.children = (
<div className="mdc-data-table__header-cell-wrapper">
<mwc-icon-button-toggle style="--mdc-icon-button-size: 28px; display: none;">
<Mdi icon="arrow-down-thin" slot="onIcon"/>
<Mdi icon="arrow-up-thin" slot="offIcon" />
</mwc-icon-button-toggle>
&nbsp;
<div className="mdc-data-table__header-cell-label">
{vnode.children}
</div>
</div>
);
}
if ((!vnode.children || vnode.children.length === 0) && this.attrs.get('type') === 'checkbox') {
vnode.children = <mwc-checkbox className="mdc-data-table__header-row-checkbox" />;
}
if (this.attrs.get('type') !== 'checkbox' && this.attrs.has('filterable')) {
vnode.children = (
<>
{vnode.children}
<div style="margin-top: 8px;">
<text-field outlined className="mdc-data-table__filter-textfield" label={__('Filtro')} compact/>
</div>
</>
);
}
return <th {...this.attrs.all()} role="columnheader" scope="col">{vnode.children}</th>;
}
oncreate(vnode) {
super.oncreate(vnode);
if (this.attrs.get('type') === 'checkbox') {
const checkbox = $(this.element)
.children('.mdc-data-table__header-row-checkbox');
checkbox.on('change', this.onCheckboxClicked.bind(this));
}
// Handle click on column (add arrows)
const observer = new MutationObserver(this.onClassChanged.bind(this));
observer.observe(this.element, {
attributes: true,
attributeFilter: ['class']
});
$(this.element).find('.mdc-data-table__filter-textfield').on('input', this.onFilterInput.bind(this));
}
onCheckboxClicked(event: Event) {
const row: Cash = $(this.element)
.closest('table')
.find('tbody tr[checkable]');
const selectedClass = 'mdc-data-table__row--selected';
if (event.target.checked) {
row.addClass(selectedClass);
} else {
row.removeClass(selectedClass);
}
row.find('mwc-checkbox').prop('checked', event.target.checked);
}
onClassChanged(mutations: MutationRecord[]) {
for (const mutation of mutations) {
const {classList} = mutation.target;
const ascendingClass = 'mdc-data-table__header-cell--sorted';
const descendingClass = 'mdc-data-table__header-cell--sorted-descending';
const onValue = classList.contains(descendingClass);
const button: Cash = $(this.element).find('mwc-icon-button-toggle');
button.prop('on', onValue);
if (classList.contains(ascendingClass) || classList.contains(descendingClass)) {
$(this.element).css('cursor', 'auto');
button.show();
} else if (!classList.contains(ascendingClass) && !classList.contains(descendingClass)) {
$(this.element).css('cursor', 'pointer');
button.hide();
}
}
}
onFilterInput(event: InputEvent) {
const index = $(this.element).index();
const rows: Cash = $(this.element).closest('table').find('tbody tr');
rows.hide();
rows.filter((index_, element) => (
$(element)
.find(`td:nth-child(${index + 1})`)
.text()
.search(event.target.value) !== -1
)).show();
}
}

View File

@@ -0,0 +1,160 @@
import '@material/mwc-icon-button-toggle';
import type {Cash} from 'cash-dom';
import type {
Children,
Vnode,
VnodeDOM
} from 'mithril';
import Component from '../Component';
import Mdi from '../Mdi';
declare global {
namespace JSX {
interface IntrinsicElements {
TableColumn: TableColumn;
}
}
}
type Attributes = {
type?: 'numeric' | 'checkbox',
id?: string,
sortable?: boolean,
filterable?: boolean,
};
export default class TableColumn extends Component<Attributes> {
view(vnode: Vnode) {
this.attrs.addClassNames('mdc-data-table__header-cell', {
[`mdc-data-table__header-cell--${this.attrs.get(
'type'
) as string}`]: this.attrs.has('type')
});
let {children}: {children?: Children} = vnode;
if (this.attrs.has('sortable')) {
this.attrs.addClassNames('mdc-data-table__header-cell--with-sort');
this.attrs
.put('aria-sort', 'none')
.put('data-column-id', this.attrs.get('id'));
children = (
<div className="mdc-data-table__header-cell-wrapper">
<mwc-icon-button-toggle style="--mdc-icon-button-size: 28px; display: none;">
<Mdi icon="arrow-down-thin" slot="onIcon" />
<Mdi icon="arrow-up-thin" slot="offIcon" />
</mwc-icon-button-toggle>
&nbsp;
<div className="mdc-data-table__header-cell-label">
{children}
</div>
</div>
);
}
if ((
(!children || (Array.isArray(children) && children.length === 0))
&& this.attrs.get('type') === 'checkbox'
)) {
children = <mwc-checkbox className="mdc-data-table__header-row-checkbox" />;
}
if (this.attrs.get('type') !== 'checkbox' && this.attrs.has('filterable')) {
children = (
<>
{children}
<div style="margin-top: 8px;">
<text-field
outlined
className="mdc-data-table__filter-textfield"
label={__('Filtro') as string}
compact
/>
</div>
</>
);
}
return (
<th {...this.attrs.all()} role="columnheader" scope="col">
{children}
</th>
);
}
oncreate(vnode: VnodeDOM<Attributes>) {
super.oncreate(vnode);
if (this.attrs.get('type') === 'checkbox') {
const checkbox = $(this.element).children(
'.mdc-data-table__header-row-checkbox'
);
checkbox.on('change', this.onCheckboxClicked.bind(this));
}
// Handle click on a column (add arrows)
const observer = new MutationObserver(this.onClassChanged.bind(this));
observer.observe(this.element, {
attributes: true,
attributeFilter: ['class']
});
$(this.element)
.find('.mdc-data-table__filter-textfield')
.on('input', this.onFilterInput.bind(this));
}
onCheckboxClicked(event: Event) {
const row: Cash = $(this.element)
.closest('table')
.find('tbody tr[checkable]');
const selectedClass = 'mdc-data-table__row--selected';
const checkbox = event.target as HTMLInputElement;
row.toggleClass(selectedClass, checkbox.checked);
row.find('mwc-checkbox').prop('checked', checkbox.checked);
}
onClassChanged(mutations: MutationRecord[]) {
for (const mutation of mutations) {
const {classList} = mutation.target as HTMLElement;
const ascendingClass = 'mdc-data-table__header-cell--sorted';
const descendingClass = 'mdc-data-table__header-cell--sorted-descending';
const onValue = classList.contains(descendingClass);
const button: Cash = $(this.element).find('mwc-icon-button-toggle');
button.prop('on', onValue);
if (
classList.contains(ascendingClass)
|| classList.contains(descendingClass)
) {
$(this.element).css('cursor', 'auto');
button.show();
} else if (
!classList.contains(ascendingClass)
&& !classList.contains(descendingClass)
) {
$(this.element).css('cursor', 'pointer');
button.hide();
}
}
}
onFilterInput(event: InputEvent) {
const index = $(this.element).index();
const rows: Cash = $(this.element).closest('table').find('tbody tr');
const {value} = event.target as HTMLInputElement;
rows.hide();
rows
.filter(
(index_, element) => $(element)
.find(`td:nth-child(${index + 1})`)
.text()
.search(value) !== -1
)
.show();
}
}

View File

@@ -1,7 +0,0 @@
import Component from '../Component.jsx';
export default class TableFooter extends Component {
view(vnode) {
return <tfoot {...this.attrs.all()}>{vnode.children}</tfoot>;
}
}

View File

@@ -0,0 +1,9 @@
import type {Vnode} from 'mithril';
import Component from '../Component';
export default class TableFooter extends Component<{}> {
view(vnode: Vnode) {
return <tfoot {...this.attrs.all()}>{vnode.children}</tfoot>;
}
}

View File

@@ -1,43 +0,0 @@
import '@material/mwc-checkbox';
import {collect} from 'collect.js';
import {
type Children,
type Vnode
} from 'mithril';
import PropTypes from 'prop-types';
import Component from '../Component.jsx';
import TableCell from './TableCell.jsx';
export default class TableRow extends Component {
static propTypes = {
checkable: PropTypes.bool
};
view(vnode) {
this.attrs.addClassNames('mdc-data-table__row');
return (
<tr {...this.attrs.all()}>
{this.checkbox(vnode.children)}
{vnode.children}
</tr>
);
}
checkbox(children: Children[]): Children {
if (!this.attrs.has('checkable')) {
return <></>;
}
for (const child: Vnode of children) {
const attributes = collect(child.attrs)
if (attributes.get('type') === 'checkbox') {
break;
}
}
return <TableCell type="checkbox"/>;
}
}

View File

@@ -0,0 +1,45 @@
import '@material/mwc-checkbox';
import {collect} from 'collect.js';
import type {Children, Vnode} from 'mithril';
import Component from '../Component';
import TableCell from './TableCell';
declare global {
namespace JSX {
interface IntrinsicElements {
'TableRow': TableRow;
}
}
}
export type TableRowAttributes = {checkable?: boolean};
export default class TableRow extends Component<TableRowAttributes> {
view(vnode: Vnode<TableRowAttributes>) {
this.attrs.addClassNames('mdc-data-table__row');
return (
<tr {...this.attrs.all()}>
{this.checkbox(vnode.children as Children[])}
{vnode.children}
</tr>
);
}
checkbox(children: Children[]): Children {
if (!this.attrs.has('checkable')) {
return <></>;
}
for (const child of children) {
const attributes = collect((child as Vnode).attrs);
if (attributes.get('type') === 'checkbox') {
break;
}
}
return <TableCell type="checkbox" />;
}
}

View File

@@ -1,6 +0,0 @@
export { default as DataTable } from './DataTable.jsx';
export { default as TableCell } from './TableCell.jsx';
export { default as TableColumn } from './TableColumn.jsx';
export { default as TableFooter } from './TableFooter.jsx';
export { default as TableRow } from './TableRow.jsx';

View File

@@ -0,0 +1,7 @@
// noinspection JSUnusedGlobalSymbols
export {default as DataTable} from './DataTable';
export {default as TableCell} from './TableCell';
export {default as TableColumn} from './TableColumn';
export {default as TableFooter} from './TableFooter';
export * from './TableRow';