UNPKG

@attivio/suit

Version:

Attivio SUIT, the Search UI Toolkit, is a library for creating search clients for searching the Attivio platform.

623 lines (528 loc) 23.8 kB
'use strict'; exports.__esModule = true; exports.default = undefined; var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var _class2, _temp, _initialiseProps; var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _reactBootstrapTable = require('react-bootstrap-table'); require('react-bootstrap-table/css/react-bootstrap-table.css'); var _lodash = require('lodash'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } /** * This defines a single column within the table. */ var TableColumn = function TableColumn(title, render) { var sort = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; var className = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : ''; var style = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {}; _classCallCheck(this, TableColumn); this.className = ''; this.style = {}; this.title = title; this.render = render; this.sort = sort; this.className = className; this.style = style; } /** * The title to show for the column. */ /** * A function to call to render the cell for this column with a * given row object. If not set, then the value will be displayed * as either a locale-formatted number or a plain string, depending * on its type. */ /** * This indicates whether the table is sortable on this column's values. If the parent of the table * wants/needs to make additional calls to a back-end API to get the newly sorted table data, then it * should just pass a boolean value of true for this propety on the column. This will result in the * function passed to the table's onSort property being called with the new sorting informaitopn. If * the data is all in the list to start with, then this can be a sorting function which behaves like * the callback psased to the JavaScript Array class' sort() method, taking two rows' data as parameters, * as well as a third parameter indicating whether the result should be reversed due to sorting in * descending order. */ /** * The class name to use for the html td and th elements representing this column in the table. */ /** * Any css style attributes to apply to the html td and th elements representing this column in the table. */ ; /** * A component to render tablular data. It can allow for rows in the table to be selected (either a * single row or multiple rows). You can define any number of columns for the table that use the * row data to display the contents of the cell at each row/column. For each column, the value can * be the contents of a property in the row's data object or the result of calling a function on that * row, allowing for complex values including nested React components. */ var Table = (_temp = _class2 = function (_React$Component) { _inherits(Table, _React$Component); Table.makeCustomRenderer = function makeCustomRenderer(column) { return function (cell, row) { return column.render(row); }; }; function Table(props) { _classCallCheck(this, Table); var _this = _possibleConstructorReturn(this, _React$Component.call(this, props)); _initialiseProps.call(_this); var defaultIndex = props.noEmptySelection ? 0 : null; _this.state = { sortedRows: props.rows && props.rows.length > 0 ? props.rows.map(function (row, tableRowIndex) { return _extends({}, row, { tableRowIndex: tableRowIndex }); }) : [], shiftKeyDown: false, ctrlKeyDown: false, anchorRowIndex: defaultIndex, activeRowIndex: defaultIndex, selectedIndices: props.noEmptySelection ? new Set([0]) : new Set([]) }; _this.renderColumns = _this.renderColumns.bind(_this); return _this; } Table.prototype.componentDidMount = function componentDidMount() { var _props = this.props, multiSelect = _props.multiSelect, onSelect = _props.onSelect, noEmptySelection = _props.noEmptySelection; var sortedRows = this.state.sortedRows; if (multiSelect) { window.addEventListener('keydown', this.keyDown); window.addEventListener('keyup', this.keyUp); window.addEventListener('blur', this.onWindowBlur); } // If the parent provided an update hook, initialize the parent with selection values. if (onSelect) { var _activeRowIndex = noEmptySelection && sortedRows.length > 0 ? 0 : null; var selectedRow = noEmptySelection && sortedRows.length > 0 ? sortedRows[0] : null; var _selectedRows = noEmptySelection && selectedRow ? [selectedRow] : []; onSelect(_selectedRows, selectedRow); // TODO: Look at reselect pattern as alternative to manipulating data coming from api for component consumption. // eslint-disable-next-line react/no-did-mount-set-state this.setState({ activeRowIndex: _activeRowIndex, anchorRowIndex: _activeRowIndex, selectedIndices: noEmptySelection ? new Set([0]) : new Set([]) }); } }; Table.prototype.componentWillReceiveProps = function componentWillReceiveProps(newProps) { var rows = newProps.rows, noEmptySelection = newProps.noEmptySelection, rowComparator = newProps.rowComparator, onSelect = newProps.onSelect; var prevRows = this.props.rows; var _state = this.state, prevSelectedIndices = _state.selectedIndices, activeRowIndex = _state.activeRowIndex; // If a rowComparator is specified, use it, otherwise, do a deep row comparison and reset selection if anything changed. var shouldUpdateRows = !(0, _lodash.isEqual)(rows, prevRows); var shouldResetSelection = rowComparator && !(0, _lodash.isEqualWith)(rows, prevRows, rowComparator) || !rowComparator && shouldUpdateRows; if (shouldResetSelection || shouldUpdateRows) { var updatedSelectedRows = []; var _sortedRows = rows && rows.length > 0 ? rows.map(function (row, tableRowIndex) { var updatedRow = _extends({}, row, { tableRowIndex: tableRowIndex }); if (prevSelectedIndices.has(tableRowIndex)) { // Derive the full row using the existing selected indices. If index no longer exists, omit the row. updatedSelectedRows.push(updatedRow); } return updatedRow; }) : []; var numPreviousSelections = prevSelectedIndices.size; var numUpdatedSelections = updatedSelectedRows.length; // Indicates one of the previously selected rows can no longer be found. if (numPreviousSelections !== numUpdatedSelections) { shouldResetSelection = true; } var resetIndices = _sortedRows.length > 0 && noEmptySelection ? new Set([0]) : new Set([]); var resetIndex = _sortedRows.length > 0 && noEmptySelection ? 0 : null; var resetSelectedRow = resetIndex !== null ? _sortedRows[resetIndex] : null; var resetSelectedRows = resetSelectedRow !== null ? [resetSelectedRow] : []; if (shouldResetSelection) { // If the parent provided an update hook, update the parent with the changes. if (onSelect) { onSelect(resetSelectedRows, resetSelectedRow); } this.setState({ sortedRows: _sortedRows, selectedIndices: resetIndices, anchorRowIndex: resetIndex, activeRowIndex: resetIndex }); } else { // If the parent provided an update hook, update the parent with the changes. if (onSelect) { var selectedRowIsStillValid = _sortedRows && _sortedRows.length > activeRowIndex; var selectedRow = selectedRowIsStillValid ? _sortedRows[activeRowIndex] : resetSelectedRow; var _selectedRows2 = updatedSelectedRows && updatedSelectedRows.length > 0 ? updatedSelectedRows : resetSelectedRows; onSelect(_selectedRows2, selectedRow); } this.setState({ sortedRows: _sortedRows }); } } }; Table.prototype.componentWillUnmount = function componentWillUnmount() { if (this.props.multiSelect) { window.removeEventListener('keydown', this.keyDown); window.removeEventListener('keyup', this.keyUp); window.removeEventListener('blur', this.onWindowBlur); } }; Table.prototype.renderColumns = function renderColumns() { var columns = this.props.columns; return columns.map(function (column) { var sortable = !!column.sort; var sortFunc = typeof column.sort === 'function' ? column.sort : null; if (typeof column.render === 'function') { // If the column has a custom renderer, then use that var rendererFunction = Table.makeCustomRenderer(column); return _react2.default.createElement( _reactBootstrapTable.TableHeaderColumn, { key: column.title, dataFormat: rendererFunction, dataSort: sortable, sortFunc: sortFunc, tdStyle: column.style, thStyle: column.style, className: column.className, columnClassName: column.className }, column.title ); } // Otherwise it's just the name of a field so use the string value of the field for the row object return _react2.default.createElement( _reactBootstrapTable.TableHeaderColumn, { key: column.title, dataField: column.render, dataSort: sortable, sortFunc: sortFunc, tdStyle: column.style, thStyle: column.style, className: column.className, columnClassName: column.className }, column.title ); }); }; Table.prototype.render = function render() { var _props2 = this.props, activeRowBackgroundColor = _props2.activeRowBackgroundColor, bordered = _props2.bordered, columns = _props2.columns, multiSelect = _props2.multiSelect, onSelect = _props2.onSelect, onSort = _props2.onSort, multiSelectBackgroundColor = _props2.multiSelectBackgroundColor, selectedClassName = _props2.selectedClassName, sortColumn = _props2.sortColumn, tableClassName = _props2.tableClassName; var _state2 = this.state, sortedRows = _state2.sortedRows, selectedIndices = _state2.selectedIndices, activeRowIndex = _state2.activeRowIndex; var selected = []; selectedIndices.forEach(function (index) { selected.push(index); }); // If the selectedIndices function isn't set, then don't let user select rows var selectRow = onSelect ? { mode: multiSelect ? 'checkbox' : 'radio', onSelect: this.rowSelect, clickToSelect: true, className: selectedClassName, bgColor: function bgColor(row, isSelected) { if (activeRowBackgroundColor && row.tableRowIndex === activeRowIndex) { return activeRowBackgroundColor; } if (multiSelect && multiSelectBackgroundColor && isSelected) { return multiSelectBackgroundColor; } return null; }, hideSelectColumn: true, selected: selected } : null; // Fill out the options with sorting-related properties var options = {}; if (sortColumn !== 0) { options.defaultSortOrder = sortColumn > 0 ? 'asc' : 'desc'; options.defaultSortName = columns[Math.abs(sortColumn) - 1].title; } if (onSort) { options.onSortChange = this.handleSort; } return _react2.default.createElement( 'div', null, _react2.default.createElement( _reactBootstrapTable.BootstrapTable, { data: sortedRows, tableHeaderClass: tableClassName, tableBodyClass: tableClassName, selectRow: selectRow, keyField: 'tableRowIndex', options: options, remote: this.remote, bordered: bordered }, this.renderColumns() ) ); }; return Table; }(_react2.default.Component), _class2.defaultProps = { bordered: false, multiSelect: false, noEmptySelection: false, rows: [], selectedClassName: 'attivio-table-row-selected attivio-table-row', sortColumn: 0, tableClassName: 'table table-striped attivio-table attivio-table-sm attivio-table-no-outline' }, _class2.displayName = 'Table', _initialiseProps = function _initialiseProps() { var _this2 = this; this.onWindowBlur = function () { _this2.setState({ shiftKeyDown: false, ctrlKeyDown: false }); }; this.keyDown = function (e) { var shiftKeyDown = e.shiftKey; var ctrlKeyDown = e.ctrlKey || e.metaKey; var isValidKey = shiftKeyDown || ctrlKeyDown; if (isValidKey) { if (shiftKeyDown) { _this2.setState({ shiftKeyDown: shiftKeyDown }); } else if (ctrlKeyDown) { _this2.setState({ ctrlKeyDown: ctrlKeyDown }); } } }; this.keyUp = function (e) { var shiftKeyUp = e.key === 'Shift'; var ctrlKeyUp = e.key === 'Control' || e.key === 'Meta'; if (shiftKeyUp || ctrlKeyUp) { _this2.setState({ shiftKeyDown: false, ctrlKeyDown: false }); } }; this.rowSelect = function (rowData) { var _state3 = _this2.state, activeRowIndex = _state3.activeRowIndex, anchorRowIndex = _state3.anchorRowIndex, ctrlKeyDown = _state3.ctrlKeyDown, shiftKeyDown = _state3.shiftKeyDown, selectedIndices = _state3.selectedIndices, sortedRows = _state3.sortedRows; var shiftKeyPressed = shiftKeyDown; var ctrlKeyPressed = ctrlKeyDown; var _props3 = _this2.props, multiSelect = _props3.multiSelect, noEmptySelection = _props3.noEmptySelection, onSelect = _props3.onSelect; var newSelectedIndices = selectedIndices; // Safety Check - if no row data is passed, do nothing. if (!rowData) { return false; } // If multiselect is enabled, check for modifier keys. if (multiSelect) { // If empty selections are allowed, then clicking a row (with or without a modifier) when there are // no selections will make this row the active row, the anchor row, and the only selectedIndices. if (anchorRowIndex === null) { _this2.setState({ selectedIndices: new Set([rowData.tableRowIndex]), anchorRowIndex: rowData.tableRowIndex, activeRowIndex: rowData.tableRowIndex }); onSelect([rowData], rowData); return true; } if (shiftKeyPressed) { // The shift key was pressed. The anchor row and active rows do not change. // All rows between the anchor row and the selected row are selected and added onto the current selectedIndices. var count = rowData.tableRowIndex - anchorRowIndex; var startIndex = count > 0 ? anchorRowIndex : rowData.tableRowIndex; var endIndex = count > 0 ? rowData.tableRowIndex : anchorRowIndex; var currentIndex = startIndex; // Add the rows in range onto the selectedIndices of row indices. while (currentIndex <= endIndex) { newSelectedIndices.add(currentIndex); currentIndex += 1; } _this2.setState({ selectedIndices: newSelectedIndices }); var _selectedRows3 = []; newSelectedIndices.forEach(function (index) { _selectedRows3.push(sortedRows[index]); }); onSelect(_selectedRows3, sortedRows[activeRowIndex]); return true; } else if (ctrlKeyPressed) { var alreadySelected = selectedIndices.has(rowData.tableRowIndex); var onlySelection = selectedIndices.size <= 1; // If the row is already selected, this is a deselection attempt. if (alreadySelected) { // Is this row the only selectedIndices? if (onlySelection) { // Does this table allow no selections? if (noEmptySelection) { // This row is already selected, it's the only row selected, and the table does not allow // empty selections. Do nothing. return false; } // The row is already selected, it's the only selectedIndices, and the table allows no selections, // clear out selections. _this2.setState({ selectedIndices: new Set([]), anchorRowIndex: null, activeRowIndex: null }); onSelect([], null); return true; } // Is the row we're deselecting the active row? var updateActiveRow = rowData.tableRowIndex === activeRowIndex; // Is the row we're deselecting the anchor row? var updateAnchorRow = rowData.tableRowIndex === anchorRowIndex; // This row is not the only selectedIndices, deselect it. newSelectedIndices.delete(rowData.tableRowIndex); if (updateActiveRow || updateAnchorRow) { // We need a fallback index for the anchor and/or active row. Use the selected row // (excluding the current row) with the least index. var minimiumIndex = Infinity; newSelectedIndices.forEach(function (selectedIndex) { minimiumIndex = Math.min(selectedIndex, minimiumIndex); }); var fallbackIndex = minimiumIndex === Infinity ? null : minimiumIndex; // If the row we're deselecting is the anchor row we need to use a fallback anchor row index. var newActiveRowIndex = updateActiveRow ? fallbackIndex : activeRowIndex; // If the row we're deselecting is the active row, we need to use a fallback active row index. var baseValue = anchorRowIndex === null ? 0 : anchorRowIndex; var newAnchorRowIndex = updateAnchorRow ? fallbackIndex : baseValue; newSelectedIndices.add(newActiveRowIndex); newSelectedIndices.add(newAnchorRowIndex); var _selectedRows6 = []; newSelectedIndices.forEach(function (index) { _selectedRows6.push(sortedRows[index]); }); var _activeRow = sortedRows[newActiveRowIndex]; // The control or meta key was pressed, and this row was previously selected. This row was an anchor or active row. // The anchor and/or active row may have been updated to a fallback index. _this2.setState({ selectedIndices: newSelectedIndices, anchorRowIndex: newAnchorRowIndex, activeRowIndex: newActiveRowIndex }); onSelect(_selectedRows6, _activeRow); return true; } var _selectedRows5 = []; newSelectedIndices.forEach(function (index) { _selectedRows5.push(sortedRows[index]); }); _this2.setState({ selectedIndices: newSelectedIndices }); // The control or meta key was pressed and this row was previously selected. This row is not an anchor or active row. // It may be safely removed. The active row does not change. The anchor row does not change. onSelect(_selectedRows5, sortedRows[activeRowIndex]); return true; } // The control or meta key was pressed, and this row was not previously selected, so select it. // This row becomes the anchor row, but the active row does not change. newSelectedIndices.add(rowData.tableRowIndex); var _selectedRows4 = []; newSelectedIndices.forEach(function (index) { _selectedRows4.push(sortedRows[index]); }); _this2.setState({ selectedIndices: newSelectedIndices, anchorRowIndex: rowData.tableRowIndex }); onSelect(_selectedRows4, sortedRows[activeRowIndex]); return true; } // No modifier keys were pressed, all previously selected indices are cleared and this row becomes the // anchor row, active row, and only selectedIndices. _this2.setState({ anchorRowIndex: rowData.tableRowIndex, activeRowIndex: rowData.tableRowIndex, selectedIndices: new Set([rowData.tableRowIndex]) }); onSelect([rowData], rowData); return true; } // multiSelect is not enabled // Is this row already the currently selected row? if (activeRowIndex === rowData.tableRowIndex) { // Is it ok if no rows are selected? if (noEmptySelection) { // This row is the active row, and we don't allow empty selections, do nothing. return false; } // This row is the active row, and we do allow empty selections, deselect the row. _this2.setState({ selectedIndices: new Set([]), activeRowIndex: null }); onSelect([], null); return true; } // Multiselect is not enabled and this is not the currently selected row, just select the row. _this2.setState({ selectedIndices: [rowData.tableRowIndex], selectedRowIndex: rowData.tableRowIndex }); onSelect([rowData], rowData); return true; }; this.remote = function (remoteObject) { var remoteCopy = _extends({}, remoteObject); if (_this2.props.onSort) { // Caller wants to handle sorting themselves remoteCopy.sort = true; } return remoteCopy; }; this.handleSort = function (colName, order) { var _props4 = _this2.props, rows = _props4.rows, noEmptySelection = _props4.noEmptySelection, onSelect = _props4.onSelect; var colNum = 0; _this2.props.columns.forEach(function (col, index) { if (col.title === colName || typeof col.render === 'string' && col.render === colName) { colNum = index + 1; } }); if (order === 'desc') { colNum = -colNum; } var sortedRows = rows && rows.length > 0 ? rows.map(function (row, tableRowIndex) { return _extends({}, row, { tableRowIndex: tableRowIndex }); }) : []; // Reset table selection. var tableContainsRows = _this2.state.sortedRows.length > 0; var defaultIndex = noEmptySelection && tableContainsRows ? 0 : null; if (onSelect) { var _selectedRows7 = defaultIndex ? [defaultIndex] : []; onSelect(_selectedRows7, defaultIndex); } var selectedIndices = defaultIndex ? new Set([defaultIndex]) : new Set([]); _this2.setState({ activeRowIndex: defaultIndex, anchorRowIndex: defaultIndex, selectedIndices: selectedIndices, sortedRows: sortedRows }); _this2.props.onSort(colNum); }; }, _temp); exports.default = Table; Table.TableColumn = TableColumn; module.exports = exports['default'];