@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
JavaScript
;
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'];