'use strict'; import React, {Component} from 'react'; import ReactDOMServer from 'react-dom/server'; import PropTypes from 'prop-types'; import {withTranslation} from './i18n'; import jQuery from 'jquery'; import 'datatables.net'; import 'datatables.net-bs4'; import 'datatables.net-bs4/css/dataTables.bootstrap4.css'; import axios from './axios'; import {withPageHelpers} from './page' import { withAsyncErrorHandler, withErrorHandling } from './error-handling'; import styles from "./styles.scss"; import {getUrl} from "./urls"; import {withComponentMixins} from "./decorator-helpers"; //dtFactory(); //dtSelectFactory(); const TableSelectMode = { NONE: 0, SINGLE: 1, MULTI: 2 }; @withComponentMixins([ withTranslation, withErrorHandling, withPageHelpers ], ['refresh']) class Table extends Component { constructor(props) { super(props); this.selectionMap = this.getSelectionMap(props); } static propTypes = { dataUrl: PropTypes.string, data: PropTypes.array, columns: PropTypes.array, selectMode: PropTypes.number, selection: PropTypes.oneOfType([PropTypes.array, PropTypes.string, PropTypes.number]), selectionKeyIndex: PropTypes.number, selectionAsArray: PropTypes.bool, onSelectionChangedAsync: PropTypes.func, onSelectionDataAsync: PropTypes.func, withHeader: PropTypes.bool, refreshInterval: PropTypes.number, pageLength: PropTypes.number } static defaultProps = { selectMode: TableSelectMode.NONE, selectionKeyIndex: 0, pageLength: 50 } refresh() { if (this.table) { this.table.rows().draw('page'); } } getSelectionMap(props) { let selArray = []; if (props.selectMode === TableSelectMode.SINGLE && !this.props.selectionAsArray) { if (props.selection !== null && props.selection !== undefined) { selArray = [props.selection]; } else { selArray = []; } } else if ((props.selectMode === TableSelectMode.SINGLE && this.props.selectionAsArray) || props.selectMode === TableSelectMode.MULTI) { selArray = props.selection || []; } const selMap = new Map(); for (const elem of selArray) { selMap.set(elem, undefined); } if (props.data) { for (const rowData of props.data) { const key = rowData[props.selectionKeyIndex]; if (selMap.has(key)) { selMap.set(key, rowData); } } } else if (this.table) { this.table.rows().every(function() { const rowData = this.data(); const key = rowData[props.selectionKeyIndex]; if (selMap.has(key)) { selMap.set(key, rowData); } }); } return selMap; } updateSelectInfo() { if (!this.jqSelectInfo) { return; // If the table is updated very quickly after mounting, the datatable may not be initialized yet. } const t = this.props.t; const count = this.selectionMap.size; if (this.selectionMap.size > 0) { const jqInfo = jQuery('' + t('countEntriesSelected', { count }) + ' '); const jqDeselectLink = jQuery('Deselect all.').on('click', ::this.deselectAll); this.jqSelectInfo.empty().append(jqInfo).append(jqDeselectLink); } else { this.jqSelectInfo.empty(); } } @withAsyncErrorHandler async fetchData(data, callback) { // This custom ajax fetch function allows us to properly handle the case when the user is not authenticated. const response = await axios.post(getUrl(this.props.dataUrl), data); callback(response.data); } @withAsyncErrorHandler async fetchAndNotifySelectionData() { if (this.props.onSelectionDataAsync) { if (!this.props.data) { const keysToFetch = []; for (const pair of this.selectionMap.entries()) { if (!pair[1]) { keysToFetch.push(pair[0]); } } if (keysToFetch.length > 0) { const response = await axios.post(getUrl(this.props.dataUrl), { operation: 'getBy', column: this.props.selectionKeyIndex, values: keysToFetch }); for (const row of response.data) { const key = row[this.props.selectionKeyIndex]; if (this.selectionMap.has(key)) { this.selectionMap.set(key, row); } } } } // noinspection JSIgnoredPromiseFromCall this.notifySelection(this.props.onSelectionDataAsync, this.selectionMap); } } shouldComponentUpdate(nextProps, nextState) { const nextSelectionMap = this.getSelectionMap(nextProps); let updateDueToSelectionChange = false; if (nextSelectionMap.size !== this.selectionMap.size) { updateDueToSelectionChange = true; } else { for (const key of this.selectionMap.keys()) { if (!nextSelectionMap.has(key)) { updateDueToSelectionChange = true; break; } } } this.selectionMap = nextSelectionMap; return updateDueToSelectionChange || this.props.data !== nextProps.data || this.props.dataUrl !== nextProps.dataUrl; } componentDidMount() { const columns = this.props.columns.slice(); // XSS protection and actions rendering for (const column of columns) { if (column.actions) { const createdCellFn = (td, data, rowData) => { const linksContainer = jQuery(``); let actions = column.actions(rowData); let options = {}; if (!Array.isArray(actions)) { options = actions; actions = actions.actions; } for (const action of actions) { if (action.action) { const html = ReactDOMServer.renderToStaticMarkup({action.label}); const elem = jQuery(html); elem.click((evt) => { evt.preventDefault(); action.action(this) }); linksContainer.append(elem); } else if (action.link) { const html = ReactDOMServer.renderToStaticMarkup({action.label}); const elem = jQuery(html); elem.click((evt) => { evt.preventDefault(); this.navigateTo(action.link) }); linksContainer.append(elem); } else if (action.href) { const html = ReactDOMServer.renderToStaticMarkup({action.label}); const elem = jQuery(html); linksContainer.append(elem); } else { const html = ReactDOMServer.renderToStaticMarkup({action.label}); const elem = jQuery(html); linksContainer.append(elem); } } if (options.refreshTimeout) { const currentMS = Date.now(); if (!this.refreshTimeoutAt || this.refreshTimeoutAt > currentMS + options.refreshTimeout) { clearTimeout(this.refreshTimeoutId); this.refreshTimeoutAt = currentMS + options.refreshTimeout; this.refreshTimeoutId = setTimeout(() => { this.refreshTimeoutAt = 0; this.refresh(); }, options.refreshTimeout); } } jQuery(td).html(linksContainer); }; column.type = 'html'; column.createdCell = createdCellFn; if (!('data' in column)) { column.data = null; column.orderable = false; column.searchable = false; } } else { const originalRender = column.render; column.render = (data, ...rest) => { if (originalRender) { const markup = originalRender(data, ...rest); return ReactDOMServer.renderToStaticMarkup(