import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { getCellData, renderCell } from './Utils';
const propTypes = {
column: PropTypes.object.isRequired,
columnGroup: PropTypes.object,
columnSummary: PropTypes.object,
columns: PropTypes.array,
highlightedColumn: PropTypes.bool,
highlightedRow: PropTypes.bool,
isBottomData: PropTypes.bool,
onHighlight: PropTypes.func,
plugins: PropTypes.array,
rowData: PropTypes.object.isRequired,
rowNumber: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
tableData: PropTypes.array,
};
const defaultProps = {
};
/**
* React component for rendering table cells, uses `<td>`.
*
* @prop {Object} column The column definition
* @prop {Object} columnGroup Column group definition
* `{ header:String, columns:[colId1, colId2, ...], className:String} `
* @prop {Object} columnSummary summary information for the column
* @prop {Object[]} columns The column definitions
* @prop {Boolean} highlightedColumn Whether this column is highlighted or not
* @prop {Boolean} highlightedRow Whether this row is highlighted or not
* @prop {Boolean} isBottomData Whether this row is in the bottom data area or not
* @prop {Function} onHighlight callback for when a column is highlighted / unhighlighted
* @prop {Object[]} plugins Collection of plugins to run to compute cell style,
* cell class name, column summaries
* @prop {Object} rowData The data to render in this row
* @prop {Number} rowNumber The row number in the table (bottom-${i} for bottom data)
* @prop {Object[]} tableData The table data
* @extends React.Component
*/
class TacoTableCell extends React.PureComponent {
/**
* @param {Object} props React props
*/
constructor(props) {
super(props);
this.handleMouseEnter = this.handleMouseEnter.bind(this);
this.handleMouseLeave = this.handleMouseLeave.bind(this);
}
/**
* Handler for when the mouse enters the `<td>`. Calls `onHighlight(column.id)`.
* @private
*/
handleMouseEnter() {
const { onHighlight, column } = this.props;
onHighlight(column.id);
}
/**
* Handler for when the mouse leaves the `<td>`. Calls `onHighlight(null)`.
* @private
*/
handleMouseLeave() {
const { onHighlight } = this.props;
onHighlight(null);
}
/**
* Computes the value of a property such as tdClassName or tdStyle
* by also considering plugins
* @private
*/
computeWithPlugins(property, cellData) {
const { column, plugins } = this.props;
let result;
/** evaluates `maybeFunction` as a function if it is one, otherwise returns it as a value */
const getValue = (maybeFunction) => {
if (typeof maybeFunction === 'function') {
return maybeFunction(cellData, this.props);
}
return maybeFunction;
};
// interpret plugins
// run the td class name from each plugin
if (plugins) {
plugins.forEach(plugin => {
// if the plugin has property and this column matches the column test (if provided)
if (plugin[property] && (!plugin.columnTest || plugin.columnTest(column))) {
const pluginResult = getValue(plugin[property]);
if (pluginResult) {
if (!result) {
result = [pluginResult];
} else {
result.push(pluginResult);
}
}
}
});
}
// compute the column result
const columnResult = getValue(column[property]);
// combine column result and plugin results
if (!result) {
result = columnResult;
} else if (columnResult) {
result.push(columnResult);
}
return result;
}
/**
* Computes the tdClassName value based on the prop and plugins.
* @private
*/
computeTdClassName(cellData) {
return this.computeWithPlugins('tdClassName', cellData);
}
/**
* Computes the tdStyle value based on the prop and plugins.
* @private
*/
computeTdStyle(cellData) {
const tdStyle = this.computeWithPlugins('tdStyle', cellData);
// combine the array into a single object if it is an array
if (Array.isArray(tdStyle)) {
return tdStyle.reduce((merged, style) => {
Object.assign(merged, style);
return merged;
}, {});
}
return tdStyle;
}
/**
* Main render method
* @return {React.Component}
*/
render() {
const { column, rowData, rowNumber, tableData, columns,
onHighlight, highlightedColumn, columnGroup, isBottomData, columnSummary } = this.props;
const { className, type } = column;
const cellData = getCellData(column, rowData, rowNumber, tableData, columns, isBottomData);
const rendered = renderCell(cellData, column, rowData, rowNumber, tableData, columns, isBottomData, columnSummary);
// attach mouse listeners for highlighting
let onMouseEnter;
let onMouseLeave;
if (onHighlight) {
onMouseEnter = this.handleMouseEnter;
onMouseLeave = this.handleMouseLeave;
}
// compute class name and style
const computedTdClassName = this.computeTdClassName(cellData);
const computedTdStyle = this.computeTdStyle(cellData);
// get classes based on column group
let columnGroupClass;
if (columnGroup) {
columnGroupClass = [columnGroup.className];
const columnGroupIndex = columnGroup.columns.indexOf(column.id);
// first column in group
if (columnGroupIndex === 0) {
columnGroupClass.push('group-first');
}
// last column in group
if (columnGroupIndex === columnGroup.columns.length - 1) {
columnGroupClass.push('group-last');
}
}
return (
<td
className={classNames(className, columnGroupClass, computedTdClassName,
`data-type-${type}`, { 'column-highlight': highlightedColumn })}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
style={computedTdStyle}
>
{rendered}
</td>
);
}
}
TacoTableCell.propTypes = propTypes;
TacoTableCell.defaultProps = defaultProps;
export default TacoTableCell;