import {define} from '/node_modules/vl-ui-core/dist/vl-core.js';
import '/node_modules/vl-ui-data-table/dist/vl-data-table.js';
import {VlRichData} from '/node_modules/vl-ui-rich-data/dist/vl-rich-data.js';
import {VlRichDataField} from '/src/vl-rich-data-field.js';
import {VlRichDataSorter} from '/src/vl-rich-data-sorter.js';
/**
* VlRichDataTable
* @class
* @classdesc Een tabel op basis van een dynamische lijst van data die uitgebreid kan worden met functionaliteiten die het consumeren van de data door een gebruiker kunnen verbeteren.
*
* @extends VlRichData
*
* @property {string} data-vl-data - De data die door de tabel getoond moet worden in JSON formaat.
* @property {boolean} data-vl-collapsed-m - Vanaf een medium schermgrootte zullen de cellen van elke rij onder elkaar ipv naast elkaar getoond worden.
* @property {boolean} data-vl-collapsed-s - Vanaf een small schermgrootte zullen de cellen van elke rij onder elkaar ipv naast elkaar getoond worden.
* @property {boolean} data-vl-collapsed-xs - Vanaf een extra small schermgrootte zullen de cellen van elke rij onder elkaar ipv naast elkaar getoond worden.
* @property {boolean} data-vl-multisort - Laat de gebruiker sorteren op meer dan 1 kolom.
*
* @see {@link https://www.github.com/milieuinfo/webcomponent-vl-ui-rich-data-table/releases/latest|Release notes}
* @see {@link https://www.github.com/milieuinfo/webcomponent-vl-ui-rich-data-table/issues|Issues}
* @see {@link https://webcomponenten.omgeving.vlaanderen.be/demo/vl-rich-data-table.html|Demo}
*/
export class VlRichDataTable extends VlRichData {
static get _observedAttributes() {
return super._observedAttributes.concat(['data', 'collapsed-m', 'collapsed-s', 'collapsed-xs']);
}
static get _tableAttributes() {
return ['data-vl-collapsed-m', 'data-vl-collapsed-s', 'data-vl-collapsed-xs'];
}
static get is() {
return 'vl-rich-data-table';
}
constructor() {
super(`
<style>
@import "/node_modules/vl-ui-data-table/dist/style.css";
</style>`, `
<table is="vl-data-table" slot="content">
<thead>
<tr></tr>
</thead>
<tbody></tbody>
</table>
`);
this.__observeSorters();
}
connectedCallback() {
super.connectedCallback();
this._render();
this.__observeFields();
}
attributeChangedCallback(attr, oldValue, newValue) {
super.attributeChangedCallback(attr, oldValue, newValue);
if (VlRichDataTable._tableAttributes.includes(attr)) {
const withoutDataVlPrefix = attr.substring('data-vl-'.length);
this.__table.toggleAttribute(withoutDataVlPrefix);
}
}
/**
* Stelt in welke data de tabel moet tonen.
* @param {Object[]} object - Een Array van objecten die de data voorstellen.
*/
set data(object) {
const previousData = this.data ? this.data.data : undefined;
super.data = object;
const hasNewData = previousData !== this.data.data;
if (hasNewData) {
try {
this._validate(this.data.data);
this._renderBody();
} catch (error) {
this._data.data = [];
throw error;
}
}
}
/**
* Geeft de data terug die in de tabel wordt getoond.
* @return {Object[]}
*/
get data() {
return super.data;
}
get __activeSorters() {
return Array.from(this.__sorters)
.filter((sorter) => sorter.direction !== undefined)
.sort(VlRichDataSorter.PRIORITY_COMPARATOR);
}
get __contentColumn() {
return this.shadowRoot.querySelector('#content');
}
get __fields() {
return this.querySelectorAll(VlRichDataField.is);
}
get __richDataFields() {
return [...this.__fields].filter((field) => field.constructor === VlRichDataField);
}
get __sorters() {
return this.__tableHeaderRow.querySelectorAll(VlRichDataSorter.is);
}
get __sortingState() {
if (this.__activeSorters && this.__activeSorters.length > 0) {
return this.__activeSorters.map((criteria) => {
return {
name: criteria.for,
priority: criteria.priority,
direction: criteria.direction,
};
});
}
}
get __table() {
return this.shadowRoot.querySelector('table');
}
get __tableHeader() {
return this.__table.querySelector('thead');
}
get __tableHeaderRow() {
const header = this.__tableHeader;
if (header) {
return header.querySelector('tr');
} else {
return undefined;
}
}
get __tableBody() {
return this.__table.querySelector('tbody');
}
__getState({paging}) {
const state = super.__getState({paging});
state.sorting = this.__sortingState;
return state;
}
get _isMultisortingEnabled() {
return this.dataset.vlMultisort !== undefined;
}
_validate(data) {
if (data) {
if (!Array.isArray(data)) {
throw new Error('vl-rich-data-table verwacht een Array als data');
}
}
}
set _sorting(sorting) {
if (sorting) {
this.__sorters.forEach((sorter) => {
const matchedSorter = sorting.find((sort) => sort.name === sorter.for);
sorter.direction = matchedSorter ? matchedSorter.direction : undefined;
sorter.priority = matchedSorter ? matchedSorter.priority : undefined;
});
}
}
get _hasResults() {
return this._data;
}
_render() {
this._renderHeaders();
this._renderBody();
}
_renderHeaders() {
this.__tableHeaderRow.innerHTML = '';
const headerColumns = this.__richDataFields.map((field) => field.headerTemplate());
const atLeastOneHeaderColumnHasContent = headerColumns.some((header) => !!header.textContent);
if (atLeastOneHeaderColumnHasContent) {
headerColumns.forEach(this.__addHeaderColumn.bind(this));
this.__showHeader();
} else {
this.__hideHeader();
}
}
__addHeaderColumn(header) {
this.__initializeSortingOnHeaderColumn(header);
this.__tableHeaderRow.appendChild(header);
}
__hideHeader() {
this.__tableHeader.setAttribute('hidden', '');
}
__showHeader() {
this.__tableHeader.removeAttribute('hidden');
}
__initializeSortingOnHeaderColumn(header) {
const sorterButton = header.querySelector('th[data-vl-sortable] > a');
if (sorterButton) {
sorterButton.addEventListener('click', (e) => {
sorterButton.querySelector('vl-rich-data-sorter').nextDirection();
});
}
}
_renderBody() {
if (this.data && this.data.data) {
this.__tableBody.innerHTML = '';
this.data.data.forEach((rowData) => {
const rowTemplate = this._template(`<tr></tr>`).firstElementChild;
this.__richDataFields.map((field) => {
rowTemplate.appendChild(field.valueTemplate(rowData));
});
this.__tableBody.appendChild(rowTemplate);
});
}
}
_dataChangedCallback(oldValue, newValue) {
this.data = JSON.parse(newValue);
}
__listenToFieldChanges(field) {
field.addEventListener('change', this.__fieldChanged.bind(this));
}
__stopListeningToFieldChanges(field) {
field.removeEventListener('change', this.__fieldChanged.bind(this));
}
__listenToSortChanges(sorter) {
sorter.addEventListener('change', this.__sortingChanged.bind(this));
}
__stopListeningToSortChanges(sorter) {
sorter.removeEventListener('change', this.__sortingChanged.bind(this));
}
__fieldChanged(event) {
const propertiesChanged = event.detail.properties;
if (propertiesChanged) {
if (propertiesChanged.some((property) => VlRichDataField.headerAttributes.includes(property))) {
this._renderHeaders();
}
if (propertiesChanged.some((property) => VlRichDataField.bodyAttributes.includes(property))) {
this._renderBody();
}
}
}
__sortingChanged(event) {
if (this._isMultisortingEnabled) {
this.__activeSorters.forEach((sorter, index) => sorter.priority = index + 1);
} else {
this.__activeSorters.filter((sorter) => sorter !== event.target).forEach((sorter) => sorter.direction = undefined);
}
this.__onStateChange(event);
}
__createObserver(doWhenNodeIsAdded, doWhenNodeIsRemoved, render) {
return new MutationObserver((mutationsList) => {
let shouldRender = false;
mutationsList.forEach((mutation) => {
if (mutation.addedNodes || mutation.removedNodes) {
shouldRender = true;
if (mutation.addedNodes) {
mutation.addedNodes.forEach(doWhenNodeIsAdded);
}
if (mutation.removedNodes) {
mutation.removedNodes.forEach(doWhenNodeIsRemoved);
}
}
});
if (render && shouldRender) {
this._render();
}
});
}
__observeFields() {
this.__fields.forEach(this.__listenToFieldChanges.bind(this));
const observer = this.__createObserver(this.__listenToFieldChanges.bind(this), this.__stopListeningToFieldChanges.bind(this), true);
observer.observe(this, {childList: true});
}
__observeSorters() {
const nodeToSorter = (doWithSorter) => {
return (node) => {
const sorter = node.querySelector(VlRichDataSorter.is);
if (sorter) {
doWithSorter(sorter);
}
};
};
this.__createObserver(
nodeToSorter((sorter) => this.__listenToSortChanges(sorter)),
nodeToSorter((sorter) => this.__stopListeningToSortChanges(sorter)),
).observe(this.__tableHeaderRow, {childList: true});
}
}
Promise.all([customElements.whenDefined(VlRichDataField.is), customElements.whenDefined(VlRichDataSorter.is)])
.then(() => define(VlRichDataTable.is, VlRichDataTable));