@geogirafe/lib-geoportal
Version:
GeoGirafe is a flexible application to build online geoportals.
290 lines (289 loc) • 11.6 kB
JavaScript
import FeatureToGridDataById from '../../../tools/featuretogriddatabyid.js';
import { TabulatorFull as Tabulator } from 'tabulator-tables';
import { getUid } from 'ol/util.js';
import ColumnAliasHelper from '../../../tools/utils/aliases.js';
import { linkify } from '../../../tools/utils/utils.js';
import { noop } from '../../../tools/utils/async.js';
import tippy from 'tippy.js';
import { sanitize } from '../../../tools/utils/sanitize.js';
const geometryColumns = new Set(['geom', 'the_geom', 'geometry']);
export default class SelectionTabulatorManager {
featureToGridData;
idTab = {};
tabHeaders = [];
table = null;
element = '';
data = {};
context;
columnAliasHelper;
minimalColumnWidth = 64;
columnPositionsBeforeFreezing = {};
popupLastImageSrc;
popupLastInstance;
get tableColumns() {
return this.table?.getColumns(false) ?? [];
}
constructor(context) {
this.context = context;
this.columnAliasHelper = new ColumnAliasHelper(context.stateManager);
this.featureToGridData = new FeatureToGridDataById(this.context, { keepGeomProperty: true });
}
/**
* Set the HTML element to be used by the grid.
*/
setElement(htmlElement) {
this.element = htmlElement;
}
/**
* Replace the data of the current grid with the data of the specified tab.
*/
replaceData(id) {
const columns = this.columnsToGridColumns(id, this.data[id].columns);
this.table?.setColumns(columns);
this.table?.replaceData(this.data[id].notOlProperties);
}
/**
* Activates a tab matching the given id.
*/
activateTab(id) {
for (const tabHeader of this.tabHeaders) {
tabHeader.active = false;
}
const visibleTabHeader = this.tabHeaders?.find((tabHeader) => tabHeader.id === id);
if (!visibleTabHeader) {
return;
}
visibleTabHeader.active = true;
this.context.stateManager.state.selection.selectedFeaturesType = id;
}
/**
* Creates (replace) the Tabulator grid based on the provided tab id and related data and features.
*/
displayGrid(id) {
if (!this.element) {
return;
}
this.table = new Tabulator(this.element, {
data: this.data[id].notOlProperties,
columns: this.columnsToGridColumns(id, this.data[id].columns),
selectableRows: true,
layout: 'fitDataStretch',
movableColumns: true,
locale: true,
maxHeight: '750',
resizableRows: true,
headerSortElement: function (_, dir) {
switch (dir) {
case 'asc':
return '<img alt="sort-up-icon" src="icons/sort-up.svg" class="sort-icon" />';
case 'desc':
return '<img alt="sort-down-icon" src="icons/sort-down.svg" class="sort-icon" />';
default:
return '<img alt="sort-icon" src="icons/sort.svg" class="sort-icon default" />';
}
},
popupContainer: this.element
});
this.table?.on('rowSelectionChanged', (selection) => {
// True if at least one row is selected.
this.context.stateManager.state.selection.gridSelected = selection.length > 0;
// Create a Map of features by their UIDs
const featureMap = new Map();
for (const feature of this.data[id].features) {
featureMap.set(getUid(feature.getGeometry()), feature);
}
// Highlight selected features on the map.
this.context.stateManager.state.selection.highlightedFeatures = selection
.map((row) => featureMap.get(getUid(row.geom ?? row.the_geom ?? row.geometry)))
.filter((feature) => feature !== undefined);
if (selection.length == 1) {
this.context.stateManager.state.selection.getDirectionsFeature =
this.context.stateManager.state.selection.highlightedFeatures[0];
}
else {
this.context.stateManager.state.selection.getDirectionsFeature = undefined;
}
});
this.table?.on('dataProcessed', () => this.redraw());
}
/**
* Transforms the given features into grid data and sets tab headers.
* The grid data are completely replaced.
*/
featuresToGridData(features) {
this.idTab = {};
const gridDataById = this.featureToGridData.toGridDataById(features);
// Create tabs and collect data.
for (const id of Object.keys(gridDataById)) {
this.gridDataToGridTab(id, gridDataById[id]);
}
// Create tabs headers
this.tabHeaders = Object.keys(this.idTab).map((key) => {
return {
id: key,
text: this.context.i18nManager.getTranslation(key),
active: false
};
});
this.data = this.filterEmptyColumnsOnData(gridDataById);
}
/**
* Converts grid data to grid tab format and adds it to the idTab object.
* @private
*/
gridDataToGridTab(id, gridData) {
if (id in this.idTab) {
// Already in ? Don't add a new one.
return;
}
this.idTab[id] = {
columns: gridData.columns.map((column) => this.createGridColumn(id, column)),
data: gridData.data,
features: gridData.features
};
}
columnsToGridColumns(idTable, columns) {
const columnDefinition = [];
for (const column of columns) {
if (geometryColumns.has(column)) {
continue;
}
const columnAlias = this.columnAliasHelper.getColumnAlias(idTable, column);
columnDefinition.push({
title: this.context.i18nManager.getTranslation(columnAlias),
field: column,
formatter: 'html',
headerTooltip: true,
minWidth: this.minimalColumnWidth,
headerContextMenu: [
{
label: (column) => this.context.i18nManager.getTranslation(column.getDefinition().frozen ? 'unfreeze-column' : 'freeze-column'),
action: (_e, column) => {
const columnDefinition = column.getDefinition();
const freezeOrNot = !columnDefinition.frozen;
const [toColumn, after] = this.getLookupColumnForFreezing();
if (freezeOrNot) {
this.columnPositionsBeforeFreezing[column.getField()] = this.tableColumns.findIndex((colComp) => colComp.getField() === column.getField());
this.table?.moveColumn(column, toColumn, after);
}
else {
const colPos = this.columnPositionsBeforeFreezing[column.getField()];
if (colPos) {
const previousColumn = this.tableColumns.at(colPos);
if (previousColumn) {
this.table?.moveColumn(column, previousColumn, true);
}
}
}
column
.updateDefinition({
...columnDefinition,
frozen: !columnDefinition.frozen
})
.then(noop);
}
}
],
contextMenu: [
{
label: this.context.i18nManager.getTranslation('copy-cell-content'),
action: (_e, cell) => {
navigator.clipboard?.writeText(cell.getElement().textContent);
}
}
],
cellMouseEnter: (_e, cell) => {
const imageElement = cell.getElement().querySelector('img');
if (imageElement && this.popupLastImageSrc !== imageElement.src) {
if (this.popupLastInstance) {
this.popupLastInstance.destroy();
}
this.popupLastImageSrc = imageElement.src;
this.popupLastInstance = tippy(imageElement, {
arrow: true,
theme: 'image-popup',
content: (_ref) => {
const popupImageElement = document.createElement('img');
popupImageElement.src = imageElement.src;
return popupImageElement;
}
});
this.popupLastInstance.show();
}
}
});
}
return columnDefinition;
}
getLookupColumnForFreezing() {
const firstFrozenColumnDefinition = this.tableColumns.find((colComp) => colComp.getDefinition().frozen);
const firstColumnDefinition = this.tableColumns[0];
return firstFrozenColumnDefinition ? [firstFrozenColumnDefinition, true] : [firstColumnDefinition, false];
}
filterEmptyColumnsOnData(data) {
for (const [_, entry] of Object.entries(data)) {
const columns = entry.columns;
const notOlProperties = entry.notOlProperties;
for (const [_, row] of Object.entries(notOlProperties)) {
for (const [key, value] of Object.entries(row)) {
if (!columns.includes(key)) {
delete row[key];
}
else if (typeof value === 'string') {
row[key] = sanitize(linkify(value), this.context.configManager.Config);
}
}
}
}
return data;
}
/**
* @returns A newly created grid column object.
* @private
*/
createGridColumn(idTable, idColumn) {
const columnAlias = this.columnAliasHelper.getColumnAlias(idTable, idColumn);
return {
id: idColumn,
name: this.context.i18nManager.getTranslation(columnAlias)
};
}
blockRedraw() {
this.table?.blockRedraw();
}
restoreRedraw() {
this.table?.restoreRedraw();
}
redraw(force) {
this.table?.redraw(force);
}
selectAll() {
this.table?.selectRow();
}
selectNone() {
this.table?.deselectRow();
}
invertSelection() {
for (const row of this.table.getRows()) {
row.toggleSelect();
}
}
getSelectedRows() {
return this.table?.getRows('selected') ?? [];
}
getSelectedData() {
return this.table?.getSelectedData() ?? [];
}
getColumnFields() {
return this.table?.getColumns(false).map((columnComponent) => columnComponent.getField()) ?? [];
}
getTabHeaders() {
return this.tabHeaders;
}
clearTabHeaders() {
this.tabHeaders = [];
}
getTabIds() {
return Object.keys(this.idTab);
}
}