Skip to content

Commit

Permalink
Custom column sort values for EuiInMemoryTable (#929)
Browse files Browse the repository at this point in the history
* Added custom column sort values to EuiInMemoryTable

* changelog

* small code style tweak
  • Loading branch information
chandlerprall authored Jun 15, 2018
1 parent ffeea17 commit 97df3ce
Show file tree
Hide file tree
Showing 9 changed files with 177 additions and 7 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
- `EuiComboBox` is now decorated with `data-test-subj` selectors for the search input (`comboxBoxSearchInput`), toggle button (`comboBoxToggleListButton`), and clear button (`comboBoxClearButton`) ([#918](https://github.com/elastic/eui/pull/918))
- `EuiComboBox` now gives focus to the search input when the user clicks the clear button, to prevent focus from defaulting to the body ([#918](https://github.com/elastic/eui/pull/918))
- Fixed visual size of inputs by setting the box-shadow border to `inset` ([#928](https://github.com/elastic/eui/pull/928))
- Per-column custom sort values added to `EuiInMemoryTable` ([#929](https://github.com/elastic/eui/pull/929))

**Non-breaking major changes**

Expand Down
41 changes: 41 additions & 0 deletions src-docs/src/views/tables/in_memory/in_memory_custom_sorting.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react';
import {
EuiInMemoryTable
} from '../../../../../src/components';

const data = [
{ animal: 'snail', weight: 25, humanFriendlyWeight: '25g' },
{ animal: 'peregrine falcon', weight: 900, humanFriendlyWeight: '0.9kg' },
{ animal: 'small dog', weight: 4500, humanFriendlyWeight: '4.5kg' },
{ animal: 'brown bear', weight: 180000, humanFriendlyWeight: '180kg' },
{ animal: 'elephant', weight: 5440000, humanFriendlyWeight: '5440kg' },
{ animal: 'giraffe', weight: 1180000, humanFriendlyWeight: '1180kg' }
];

export const Table = () => {
const columns = [{
field: 'animal',
name: 'Animal',
sortable: true
}, {
field: 'humanFriendlyWeight',
name: 'Weight',
sortable: ({ weight }) => weight
}];

const sorting = {
sort: {
field: 'humanFriendlyWeight',
direction: 'asc',
}
};

return (
<EuiInMemoryTable
items={data}
columns={columns}
pagination={false}
sorting={sorting}
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from 'react';
import {
EuiCode
} from '../../../../../src/components';
import { GuideSectionTypes } from '../../../components';
import { renderToHtml } from '../../../services';

import { Table } from './in_memory_custom_sorting';
import { propsInfo } from './props_info';

const source = require('!!raw-loader!./in_memory_custom_sorting');
const html = renderToHtml(Table);

export const customSortingSection = {
title: 'In-Memory Table - Custom sort values',
source: [
{
type: GuideSectionTypes.JS,
code: source,
}, {
type: GuideSectionTypes.HTML,
code: html,
}
],
text: (
<div>
<p>
Sometimes the value displayed in a column is not appropriate to use
for sorting, such as pre-formatting values to be human-readable. In these
cases it&apos;s possible to pass the <EuiCode>sortable</EuiCode> prop as
a function instead of <EuiCode>true</EuiCode> or <EuiCode>false</EuiCode>.
The function is used to extract or calculate the intended sort value
for each <EuiCode>item</EuiCode>.
</p>
</div>
),
props: propsInfo,
demo: <Table/>
};
1 change: 1 addition & 0 deletions src-docs/src/views/tables/in_memory/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export { section } from './in_memory_section';
export { selectionSection } from './in_memory_selection_section';
export { searchSection } from './in_memory_search_section';
export { searchCallbackSection } from './in_memory_search_callback_section';
export { customSortingSection } from './in_memory_custom_sorting_section';
2 changes: 2 additions & 0 deletions src-docs/src/views/tables/tables_example.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
selectionSection as inMemorySelectionSection,
searchSection as inMemorySearchSection,
searchCallbackSection as inMemorySearchCallbackSection,
customSortingSection as inMemoryCustomSortingSection,
} from './in_memory';
import { section as customSection } from './custom';
import { section as mobileSection } from './mobile';
Expand Down Expand Up @@ -53,6 +54,7 @@ export const TableExample = {
inMemorySelectionSection,
inMemorySearchSection,
inMemorySearchCallbackSection,
inMemoryCustomSortingSection,
mobileSection,
customSection,
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ exports[`EuiInMemoryTable empty array 1`] = `
"description": "description",
"field": "name",
"name": "Name",
"sortable": false,
},
]
}
Expand Down Expand Up @@ -76,6 +77,7 @@ exports[`EuiInMemoryTable with items 1`] = `
"description": "description",
"field": "name",
"name": "Name",
"sortable": false,
},
]
}
Expand Down Expand Up @@ -112,6 +114,7 @@ exports[`EuiInMemoryTable with items and expanded item 1`] = `
"description": "description",
"field": "name",
"name": "Name",
"sortable": false,
},
]
}
Expand Down Expand Up @@ -156,6 +159,7 @@ exports[`EuiInMemoryTable with items and message - expecting to show the items 1
"description": "description",
"field": "name",
"name": "Name",
"sortable": false,
},
]
}
Expand Down Expand Up @@ -192,6 +196,7 @@ exports[`EuiInMemoryTable with message 1`] = `
"description": "description",
"field": "name",
"name": "Name",
"sortable": false,
},
]
}
Expand All @@ -213,6 +218,7 @@ exports[`EuiInMemoryTable with message and loading 1`] = `
"description": "description",
"field": "name",
"name": "Name",
"sortable": false,
},
]
}
Expand All @@ -235,6 +241,7 @@ exports[`EuiInMemoryTable with pagination 1`] = `
"description": "description",
"field": "name",
"name": "Name",
"sortable": false,
},
]
}
Expand Down Expand Up @@ -279,6 +286,7 @@ exports[`EuiInMemoryTable with pagination and default page size 1`] = `
"description": "description",
"field": "name",
"name": "Name",
"sortable": false,
},
]
}
Expand Down Expand Up @@ -327,6 +335,7 @@ exports[`EuiInMemoryTable with pagination and selection 1`] = `
"description": "description",
"field": "name",
"name": "Name",
"sortable": false,
},
]
}
Expand Down Expand Up @@ -381,6 +390,7 @@ exports[`EuiInMemoryTable with pagination, default page size and error 1`] = `
"description": "description",
"field": "name",
"name": "Name",
"sortable": false,
},
]
}
Expand Down Expand Up @@ -501,6 +511,7 @@ exports[`EuiInMemoryTable with pagination, selection, sorting and simple search
},
],
"name": "Actions",
"sortable": false,
},
]
}
Expand Down Expand Up @@ -573,6 +584,7 @@ exports[`EuiInMemoryTable with pagination, selection, sorting and a single recor
},
],
"name": "Actions",
"sortable": false,
},
]
}
Expand Down Expand Up @@ -728,6 +740,7 @@ exports[`EuiInMemoryTable with pagination, selection, sorting and configured sea
},
],
"name": "Actions",
"sortable": false,
},
]
}
Expand Down
7 changes: 4 additions & 3 deletions src/components/basic_table/basic_table.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,14 +83,14 @@ const SupportedItemActionType = PropTypes.oneOfType([
CustomItemActionType
]);

const ActionsColumnType = PropTypes.shape({
export const ActionsColumnType = PropTypes.shape({
actions: PropTypes.arrayOf(SupportedItemActionType).isRequired,
name: PropTypes.string,
description: PropTypes.string,
width: PropTypes.string
});

export const FieldDataColumnType = PropTypes.shape({
export const FieldDataColumnTypeShape = {
field: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
description: PropTypes.string,
Expand All @@ -100,7 +100,8 @@ export const FieldDataColumnType = PropTypes.shape({
align: PropTypes.oneOf([LEFT_ALIGNMENT, RIGHT_ALIGNMENT]),
truncateText: PropTypes.bool,
render: PropTypes.func // ((value, record) => PropTypes.node (also see [services/value_renderer] for basic implementations)
});
};
export const FieldDataColumnType = PropTypes.shape(FieldDataColumnTypeShape);

export const ComputedColumnType = PropTypes.shape({
render: PropTypes.func.isRequired, // (record) => PropTypes.node
Expand Down
44 changes: 40 additions & 4 deletions src/components/basic_table/in_memory_table.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import {
EuiBasicTable,
ColumnType,
SelectionType,
ItemIdType,
FieldDataColumnTypeShape,
ComputedColumnType,
ActionsColumnType,
} from './basic_table';
import {
defaults as paginationBarDefaults
Expand All @@ -18,6 +20,16 @@ import {
} from '../search_bar';
import { EuiSpacer } from '../spacer/spacer';

// same as ColumnType from EuiBasicTable, but need to modify the `sortable` type
const ColumnType = PropTypes.oneOfType([
PropTypes.shape({
...FieldDataColumnTypeShape,
sortable: PropTypes.oneOfType([PropTypes.bool, PropTypes.func])
}),
ComputedColumnType,
ActionsColumnType
]);

const InMemoryTablePropTypes = {
columns: PropTypes.arrayOf(ColumnType).isRequired,
items: PropTypes.array,
Expand Down Expand Up @@ -225,6 +237,24 @@ export class EuiInMemoryTable extends Component {
}, { strict: true, fields: {} });
}

getItemSorter() {
const {
sortField,
sortDirection
} = this.state;

const { columns } = this.props;

const sortColumn = columns.find(({ field }) => field === sortField);
const { sortable } = sortColumn;

if (typeof sortable === 'function') {
return Comparators.value(sortable, Comparators.default(sortDirection));
}

return Comparators.property(sortField, Comparators.default(sortDirection));
}

getItems() {
const { prevProps: { items } } = this.state;

Expand All @@ -238,15 +268,14 @@ export class EuiInMemoryTable extends Component {
const {
query,
sortField,
sortDirection,
pageIndex,
pageSize,
} = this.state;

const matchingItems = query ? EuiSearchBar.Query.execute(query, items) : items;

const sortedItems =
sortField ? matchingItems.sort(Comparators.property(sortField, Comparators.default(sortDirection))) : matchingItems;
sortField ? matchingItems.sort(this.getItemSorter()) : matchingItems;

const visibleItems = pageSize ? (() => {
const startIndex = pageIndex * pageSize;
Expand Down Expand Up @@ -310,13 +339,20 @@ export class EuiInMemoryTable extends Component {

const searchBar = this.renderSearchBar();

// EuiInMemoryTable's column type supports sortable as a function, but
// EuiBasicTable requires those functions to be cast to a boolean
const mappedColumns = columns.map(column => ({
...column,
sortable: !!column.sortable
}));

const table = (
<EuiBasicTable
items={items}
itemId={itemId}
rowProps={rowProps}
cellProps={cellProps}
columns={columns}
columns={mappedColumns}
pagination={pagination}
sorting={sorting}
selection={selection}
Expand Down
36 changes: 36 additions & 0 deletions src/components/basic_table/in_memory_table.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -593,4 +593,40 @@ describe('EuiInMemoryTable', () => {
expect(component.find('.testTable EuiTableRow').length).toBe(1);
});
});

describe('custom column sorting', () => {
it('calls the sortable function and uses its return value for sorting', () => {
const props = {
...requiredProps,
items: [
{ id: 7, name: 'Alfred' },
{ id: 3, name: 'Betty' },
{ id: 5, name: 'Charlie' }
],
itemId: 'id',
columns: [
{
field: 'name',
name: 'Name',
sortable: ({ id }) => id
}
],
sorting: {
sort: {
field: 'name',
direction: 'asc',
}
}
};
const component = mount(
<EuiInMemoryTable {...props} />
);

expect(component.find('EuiBasicTable').props().items).toEqual([
{ id: 3, name: 'Betty' },
{ id: 5, name: 'Charlie' },
{ id: 7, name: 'Alfred' }
]);
});
});
});

0 comments on commit 97df3ce

Please sign in to comment.