diff --git a/CHANGELOG.md b/CHANGELOG.md
index b57b0a71564d..314129a1ca9a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,12 +1,15 @@
## [`master`](https://github.com/elastic/eui/tree/master)
-- `EuiBasicTable` and `EuiInMemoryTable` now let you define a `__props__` object on each row item,
-which lets you apply custom props to each row component ([#869](https://github.com/elastic/eui/pull/869))
+- `EuiBasicTable` and `EuiInMemoryTable` now accept `rowProps` and `cellProps` callbacks,
+which let you apply custom props to rows and props ([#869](https://github.com/elastic/eui/pull/869))
**Breaking changes**
- `EuiSearchBar` no longer has an `onParse` callback, and now passes an object to `onChange` with the shape `{ query, queryText, error }` ([#863](https://github.com/elastic/eui/pull/863))
- `EuiInMemoryTable`'s `search.onChange` callback now passes an object with `{ query, queryText, error }` instead of only the query ([#863](https://github.com/elastic/eui/pull/863))
+- `EuiBasicTable` and `EuiInMemoryTable` pass-through cell props (defined by the `columns` prop
+and the `cellProps` prop) used to be applied to the `div` inside of the `td` element. They're
+now applied directly to the `td` element. ([#869](https://github.com/elastic/eui/pull/869))
**Bug fixes**
diff --git a/src-docs/src/views/tables/basic/basic.js b/src-docs/src/views/tables/basic/basic.js
index ba76e3399e6d..ca6f08f0c8dc 100644
--- a/src-docs/src/views/tables/basic/basic.js
+++ b/src-docs/src/views/tables/basic/basic.js
@@ -80,22 +80,31 @@ export const Table = () => {
}
}];
- const items = store.users.filter((user, index) => index < 10).map(user => {
- const { id } = user;
+ const items = store.users.filter((user, index) => index < 10);
+
+ const getRowProps = (item) => {
+ const { id } = item;
+ return {
+ 'data-test-subj': `row-${id}`,
+ className: 'customRowClass',
+ onClick: () => console.log(`Clicked row ${id}`),
+ };
+ };
+
+ const getCellProps = (item, column, columnIndex) => {
+ const { id } = item;
return {
- ...user,
- __props__: {
- 'data-test-subj': `row-${id}`,
- className: 'customClass',
- onClick: () => console.log(`Clicked row ${id}`),
- },
+ className: 'customCellClass',
+ 'data-test-subj': `cell-${id}-${columnIndex}`,
};
- });
+ };
return (
);
};
diff --git a/src-docs/src/views/tables/basic/basic_section.js b/src-docs/src/views/tables/basic/basic_section.js
index 419cbfa3ec0b..4221067205b1 100644
--- a/src-docs/src/views/tables/basic/basic_section.js
+++ b/src-docs/src/views/tables/basic/basic_section.js
@@ -31,10 +31,11 @@ export const section = {
-
items are an array of objects that should be displayed in the table;
- one item per row. You can define a __props__ property on each item
- object to define props to pass to the corresponding row component. The exact item data
- that will be rendered in each cell in these rows is determined by
- the columns property.
+ one item per row. The exact item data that will be rendered in each cell in these rows is
+ determined by the columns property.
+ You can define rowProps and cellProps props
+ which can either be objects and functions that return objects. The returned object’s
+ will be applied as props to the rendered rows and row cells, respectively.
-
columns defines what columns the table has and how to extract item data
diff --git a/src/components/basic_table/__snapshots__/basic_table.test.js.snap b/src/components/basic_table/__snapshots__/basic_table.test.js.snap
index 82553b8be8b3..e3c98dd8fe49 100644
--- a/src/components/basic_table/__snapshots__/basic_table.test.js.snap
+++ b/src/components/basic_table/__snapshots__/basic_table.test.js.snap
@@ -1,5 +1,169 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`EuiBasicTable cellProps renders cells with custom props from a callback 1`] = `
+
+
+
+
+
+
+ Name
+
+
+
+
+
+
+ name1
+
+
+
+
+
+
+ name2
+
+
+
+
+
+
+ name3
+
+
+
+
+
+
+
+`;
+
+exports[`EuiBasicTable cellProps renders rows with custom props from an object 1`] = `
+
+
+
+
+
+
+ Name
+
+
+
+
+
+
+ name1
+
+
+
+
+
+
+ name2
+
+
+
+
+
+
+ name3
+
+
+
+
+
+
+
+`;
+
exports[`EuiBasicTable empty is rendered 1`] = `
`;
-exports[`EuiBasicTable items are rendered with custom props 1`] = `
+exports[`EuiBasicTable rowProps renders rows with custom props from a callback 1`] = `
+
+
+
+
+
+
+ Name
+
+
+
+
+
+
+ name1
+
+
+
+
+
+
+ name2
+
+
+
+
+
+
+ name3
+
+
+
+
+
+
+
+`;
+
+exports[`EuiBasicTable rowProps renders rows with custom props from an object 1`] = `
@@ -245,7 +491,10 @@ exports[`EuiBasicTable items are rendered with custom props 1`] = `
key="row_1"
>
void;,
+ onSelectionChange: PropTypes.func, // (selection: item[]) => void;,
selectable: PropTypes.func, // (item) => boolean;
selectableMessage: PropTypes.func // (selectable, item) => boolean;
});
@@ -144,11 +144,12 @@ const BasicTablePropTypes = {
responsive: PropTypes.bool,
isSelectable: PropTypes.bool,
isExpandable: PropTypes.bool,
- hasActions: PropTypes.bool
+ hasActions: PropTypes.bool,
+ rowProps: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
+ cellProps: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
};
-export function getItemId(item, props) {
- const { itemId } = props;
+export function getItemId(item, itemId) {
if (itemId) {
if (isFunction(itemId)) {
return itemId(item);
@@ -157,8 +158,29 @@ export function getItemId(item, props) {
}
}
-export class EuiBasicTable extends Component {
+function getRowProps(item, rowProps) {
+ if (rowProps) {
+ if (isFunction(rowProps)) {
+ return rowProps(item);
+ }
+ return rowProps;
+ }
+ return {};
+}
+
+function getCellProps(item, column, columnIndex, cellProps) {
+ if (cellProps) {
+ if (isFunction(cellProps)) {
+ return cellProps(item, column, columnIndex);
+ }
+ return cellProps;
+ }
+
+ return {};
+}
+
+export class EuiBasicTable extends Component {
static propTypes = BasicTablePropTypes;
static defaultProps = {
responsive: true,
@@ -171,8 +193,9 @@ export class EuiBasicTable extends Component {
return { selection: [] };
}
+ const { itemId } = nextProps;
const selection = prevState.selection.filter(selectedItem => (
- nextProps.items.findIndex(item => getItemId(item, nextProps) === getItemId(selectedItem, nextProps)) !== -1
+ nextProps.items.findIndex(item => getItemId(item, itemId) === getItemId(selectedItem, itemId)) !== -1
));
return { selection };
@@ -280,6 +303,8 @@ export class EuiBasicTable extends Component {
isSelectable, // eslint-disable-line no-unused-vars
isExpandable, // eslint-disable-line no-unused-vars
hasActions, // eslint-disable-line no-unused-vars
+ rowProps, // eslint-disable-line no-unused-vars
+ cellProps, // eslint-disable-line no-unused-vars
...rest
} = this.props;
@@ -495,9 +520,10 @@ export class EuiBasicTable extends Component {
const cells = [];
- const itemId = getItemId(item, this.props) || rowIndex;
- const selected = !selection ? false : this.state.selection && !!this.state.selection.find(selectedRecord => (
- getItemId(selectedRecord, this.props) === itemId
+ const { itemId: itemIdCallback } = this.props;
+ const itemId = getItemId(item, itemIdCallback) || rowIndex;
+ const selected = !selection ? false : this.state.selection && !!this.state.selection.find(selectedItem => (
+ getItemId(selectedItem, itemIdCallback) === itemId
));
if (selection) {
@@ -534,7 +560,8 @@ export class EuiBasicTable extends Component {
) : undefined;
- const { __props__: customRowProps } = item;
+ const { rowProps: rowPropsCallback } = this.props;
+ const rowProps = getRowProps(item, rowPropsCallback);
return (
@@ -544,7 +571,7 @@ export class EuiBasicTable extends Component {
isSelected={selected}
hasActions={hasActions}
isExpandable={isExpandable}
- {...customRowProps}
+ {...rowProps}
>
{cells}
@@ -563,8 +590,9 @@ export class EuiBasicTable extends Component {
if (event.target.checked) {
this.changeSelection([...this.state.selection, item]);
} else {
+ const { itemId: itemIdCallback } = this.props;
this.changeSelection(this.state.selection.reduce((selection, selectedItem) => {
- if (getItemId(selectedItem, this.props) !== itemId) {
+ if (getItemId(selectedItem, itemIdCallback) !== itemId) {
selection.push(selectedItem);
}
return selection;
@@ -651,6 +679,10 @@ export class EuiBasicTable extends Component {
const value = get(item, field);
const contentRenderer = this.resolveContentRenderer(column);
const content = contentRenderer(value, item);
+
+ const { cellProps: cellPropsCallback } = this.props;
+ const cellProps = getCellProps(item, column, columnIndex, cellPropsCallback);
+
return (
{content}
diff --git a/src/components/basic_table/basic_table.test.js b/src/components/basic_table/basic_table.test.js
index cfd754b45ede..c89f0bb86fee 100644
--- a/src/components/basic_table/basic_table.test.js
+++ b/src/components/basic_table/basic_table.test.js
@@ -6,19 +6,19 @@ import { EuiBasicTable, getItemId } from './basic_table';
describe('getItemId', () => {
it('returns undefined if no itemId prop is given', () => {
- expect(getItemId({ id: 5 }, {})).toBeUndefined();
- expect(getItemId({ itemId: 5 }, {})).toBeUndefined();
- expect(getItemId({ _itemId: 5 }, {})).toBeUndefined();
+ expect(getItemId({ id: 5 })).toBeUndefined();
+ expect(getItemId({ itemId: 5 })).toBeUndefined();
+ expect(getItemId({ _itemId: 5 })).toBeUndefined();
});
it('returns the correct id when a string itemId is given', () => {
- expect(getItemId({ id: 5 }, { itemId: 'id' })).toBe(5);
- expect(getItemId({ thing: '5' }, { itemId: 'thing' })).toBe('5');
+ expect(getItemId({ id: 5 }, 'id')).toBe(5);
+ expect(getItemId({ thing: '5' }, 'thing')).toBe('5');
});
it('returns the correct id when a function itemId is given', () => {
- expect(getItemId({ id: 5 }, { itemId: () => 6 })).toBe(6);
- expect(getItemId({ x: 2, y: 4 }, { itemId: ({ x, y }) => x * y })).toBe(8);
+ expect(getItemId({ id: 5 }, () => 6)).toBe(6);
+ expect(getItemId({ x: 2, y: 4 }, ({ x, y }) => x * y)).toBe(8);
});
});
@@ -82,34 +82,123 @@ describe('EuiBasicTable', () => {
});
});
- test('items are rendered with custom props', () => {
- const props = {
- items: [
- {
- id: '1',
- name: 'name1',
- __props__: {
- 'data-test-subj': `row`,
- className: 'customClass',
+ describe('rowProps', () => {
+ test('renders rows with custom props from a callback', () => {
+ const props = {
+ items: [
+ { id: '1', name: 'name1' },
+ { id: '2', name: 'name2' },
+ { id: '3', name: 'name3' }
+ ],
+ columns: [
+ {
+ field: 'name',
+ name: 'Name',
+ description: 'description'
+ }
+ ],
+ rowProps: (item) => {
+ const { id } = item;
+ return {
+ 'data-test-subj': `row-${id}`,
+ className: 'customRowClass',
onClick: () => {},
- },
+ };
},
- { id: '2', name: 'name2' },
- { id: '3', name: 'name3' }
- ],
- columns: [
- {
- field: 'name',
- name: 'Name',
- description: 'description'
- }
- ]
- };
- const component = shallow(
-
- );
+ };
+ const component = shallow(
+
+ );
- expect(component).toMatchSnapshot();
+ expect(component).toMatchSnapshot();
+ });
+
+ test('renders rows with custom props from an object', () => {
+ const props = {
+ items: [
+ { id: '1', name: 'name1' },
+ { id: '2', name: 'name2' },
+ { id: '3', name: 'name3' }
+ ],
+ columns: [
+ {
+ field: 'name',
+ name: 'Name',
+ description: 'description'
+ }
+ ],
+ rowProps: {
+ 'data-test-subj': `row`,
+ className: 'customClass',
+ onClick: () => {},
+ },
+ };
+ const component = shallow(
+
+ );
+
+ expect(component).toMatchSnapshot();
+ });
+ });
+
+ describe('cellProps', () => {
+ test('renders cells with custom props from a callback', () => {
+ const props = {
+ items: [
+ { id: '1', name: 'name1' },
+ { id: '2', name: 'name2' },
+ { id: '3', name: 'name3' }
+ ],
+ columns: [
+ {
+ field: 'name',
+ name: 'Name',
+ description: 'description'
+ }
+ ],
+ cellProps: (item, column, columnIndex) => {
+ const { id } = item;
+ const { field } = column;
+ return {
+ 'data-test-subj': `cell-${id}-${columnIndex}-${field}`,
+ className: 'customRowClass',
+ onClick: () => {},
+ };
+ },
+ };
+ const component = shallow(
+
+ );
+
+ expect(component).toMatchSnapshot();
+ });
+
+ test('renders rows with custom props from an object', () => {
+ const props = {
+ items: [
+ { id: '1', name: 'name1' },
+ { id: '2', name: 'name2' },
+ { id: '3', name: 'name3' }
+ ],
+ columns: [
+ {
+ field: 'name',
+ name: 'Name',
+ description: 'description'
+ }
+ ],
+ cellProps: {
+ 'data-test-subj': `cell`,
+ className: 'customClass',
+ onClick: () => {},
+ },
+ };
+ const component = shallow(
+
+ );
+
+ expect(component).toMatchSnapshot();
+ });
});
test('itemIdToExpandedRowMap renders an expanded row', () => {
diff --git a/src/components/table/__snapshots__/table_row_cell.test.js.snap b/src/components/table/__snapshots__/table_row_cell.test.js.snap
index d77cae1ddfb2..e7b5428c554c 100644
--- a/src/components/table/__snapshots__/table_row_cell.test.js.snap
+++ b/src/components/table/__snapshots__/table_row_cell.test.js.snap
@@ -58,12 +58,12 @@ exports[`children's className merges new classnames into existing ones 1`] = `
exports[`renders EuiTableRowCell 1`] = `
|