Skip to content

Commit

Permalink
feat(react-grid): provide the custom data accessors capability (#264)
Browse files Browse the repository at this point in the history
Fixes #176
  • Loading branch information
SergeyAlexeev committed Aug 21, 2017
1 parent dbf19cc commit 5f699bf
Show file tree
Hide file tree
Showing 44 changed files with 1,019 additions and 97 deletions.
12 changes: 12 additions & 0 deletions packages/dx-grid-core/src/plugins/editing-state/computeds.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,15 @@ export const addedRowsByIds = (addedRows, rowIds) => {
});
return result;
};

export const computedCreateRowChange = (columns) => {
const map = columns.reduce((acc, column) => {
if (column.createRowChange) {
acc[column.name] = column.createRowChange;
}
return acc;
}, {});

return (row, columnName, value) =>
(map[columnName] ? map[columnName](row, value, columnName) : { [columnName]: value });
};
28 changes: 28 additions & 0 deletions packages/dx-grid-core/src/plugins/editing-state/computeds.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
changedRowsByIds,
addedRowsByIds,
computedCreateRowChange,
} from './computeds';

describe('EditingState computeds', () => {
Expand Down Expand Up @@ -33,4 +34,31 @@ describe('EditingState computeds', () => {
]);
});
});
describe('#computedCreateRowChange', () => {
it('should create a row change', () => {
const rows = [
{ a: 1, b: 1 },
{ a: 2, b: 2 },
];
const columns = [
{ name: 'a' },
{ name: 'b' },
];
const createRowChange = computedCreateRowChange(columns);
const change = createRowChange(rows[1], columns[1].name, 3);

expect(change).toEqual({ b: 3 });
});

it('should create a row change by using a custom function within column config', () => {
const rows = [{ a: 1 }];
const createRowChangeMock = jest.fn();
const columns = [{ name: 'a', createRowChange: createRowChangeMock }];

const createRowChange = computedCreateRowChange(columns);
createRowChange(rows[0], columns[0].name, 3);

expect(createRowChangeMock).toBeCalledWith(rows[0], 3, columns[0].name);
});
});
});
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
const toString = value => String(value).toLowerCase();

const applyRowFilter = (row, filter) =>
toString(row[filter.columnName]).indexOf(toString(filter.value)) > -1;
const applyFilter = (filter, value) => (toString(value).indexOf(toString(filter.value)) > -1);

export const filteredRows = (rows, filters, filterFn = applyRowFilter) => {
export const filteredRows = (rows, filters, getCellData, userFilterFn) => {
if (!filters.length) return rows;

const filterFn = userFilterFn ||
((row, filter) => applyFilter(filter, getCellData(row, filter.columnName)));

return rows.filter(
row => filters.reduce(
(accumulator, filter) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,25 @@ import {
describe('FilteringState computeds', () => {
describe('#filteredRows', () => {
const rows = [
{ a: 1, b: 1 },
{ a: 1, b: 2 },
{ a: 2, b: 1 },
{ a: 2, b: 2 },
{ a: 1, b: 1 },
{ a: 1, b: 2 },
{ a: 2, b: 1 },
{ a: 2, b: 2 },
];

const getCellData = (row, columnName) => row[columnName];

it('should not touch rows if no filters specified', () => {
const filters = [];

const filtered = filteredRows(rows, filters);
const filtered = filteredRows(rows, filters, getCellData);
expect(filtered).toBe(rows);
});

it('can filter by one field', () => {
const filters = [{ columnName: 'a', value: 1 }];

const filtered = filteredRows(rows, filters);
const filtered = filteredRows(rows, filters, getCellData);
expect(filtered).toEqual([
{ a: 1, b: 1 },
{ a: 1, b: 2 },
Expand All @@ -31,7 +33,7 @@ describe('FilteringState computeds', () => {
it('can filter by several fields', () => {
const filters = [{ columnName: 'a', value: 1 }, { columnName: 'b', value: 2 }];

const filtered = filteredRows(rows, filters);
const filtered = filteredRows(rows, filters, getCellData);
expect(filtered).toEqual([
{ a: 1, b: 2 },
]);
Expand Down
6 changes: 3 additions & 3 deletions packages/dx-grid-core/src/plugins/grouping-local/computeds.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { GROUP_KEY_SEPARATOR } from '../grouping-state/constants';

export const groupedRows = (rows, grouping) => {
export const groupedRows = (rows, grouping, getCellData) => {
if (!grouping.length) return rows;

const groups = rows
.reduce((acc, row) => {
const value = row[grouping[0].columnName];
const value = getCellData(row, grouping[0].columnName);
const key = String(value);
const sameKeyItems = acc.get(key);
if (!sameKeyItems) {
Expand All @@ -20,7 +20,7 @@ export const groupedRows = (rows, grouping) => {
return [...groups.values()]
.map(([value, items]) => ({
value,
items: groupedRows(items, nestedGrouping),
items: groupedRows(items, nestedGrouping, getCellData),
}));
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ describe('GroupingPlugin computeds', () => {
{ a: 2, b: 1 },
{ a: 2, b: 2 },
];
const getCellData = (row, columnName) => row[columnName];

const firstLevelGroupings = [{ columnName: 'a' }];
const firstLevelGroupedRows = [{
Expand Down Expand Up @@ -57,12 +58,12 @@ describe('GroupingPlugin computeds', () => {

describe('#groupedRows', () => {
it('can group by one column', () => {
expect(groupedRows(rowsSource, firstLevelGroupings))
expect(groupedRows(rowsSource, firstLevelGroupings, getCellData))
.toEqual(firstLevelGroupedRows);
});

it('can group by several columns', () => {
expect(groupedRows(rowsSource, secondLevelGroupings))
expect(groupedRows(rowsSource, secondLevelGroupings, getCellData))
.toEqual(secondLevelGroupedRows);
});
});
Expand Down
14 changes: 8 additions & 6 deletions packages/dx-grid-core/src/plugins/sorting-state/computeds.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
import mergeSort from '../../utils/merge-sort';

const createSortingCompare = (sorting, compareEqual) => (a, b) => {
const sortColumn = sorting.columnName;
const createSortingCompare = (sorting, compareEqual, getCellData) => (a, b) => {
const inverse = sorting.direction === 'desc';
const columnName = sorting.columnName;
const aValue = getCellData(a, columnName);
const bValue = getCellData(b, columnName);

if (a[sortColumn] === b[sortColumn]) {
if (aValue === bValue) {
return (compareEqual && compareEqual(a, b)) || 0;
}

return (a[sortColumn] < b[sortColumn]) ^ inverse ? -1 : 1; // eslint-disable-line no-bitwise
return (aValue < bValue) ^ inverse ? -1 : 1; // eslint-disable-line no-bitwise
};

export const sortedRows = (rows, sorting) => {
export const sortedRows = (rows, sorting, getCellData) => {
if (!sorting.length) return rows;

const compare = Array.from(sorting)
.reverse()
.reduce((prevCompare, columnSorting) =>
createSortingCompare(columnSorting, prevCompare), () => 0);
createSortingCompare(columnSorting, prevCompare, getCellData), () => 0);

return mergeSort(Array.from(rows), compare);
};
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,19 @@ describe('SortingState computeds', () => {
{ a: 1, b: 2 },
];

const getCellData = (row, columnName) => row[columnName];

it('does not mutate rows if no sorting specified', () => {
const sorting = [];

const sorted = sortedRows(rows, sorting);
const sorted = sortedRows(rows, sorting, getCellData);
expect(sorted).toBe(rows);
});

it('can sort ascending by one column', () => {
const sorting = [{ columnName: 'a', direction: 'asc' }];

const sorted = sortedRows(rows, sorting);
const sorted = sortedRows(rows, sorting, getCellData);
expect(sorted).toEqual([
{ a: 1, b: 1 },
{ a: 1, b: 2 },
Expand All @@ -35,7 +37,7 @@ describe('SortingState computeds', () => {
it('can sort descending by one column', () => {
const sorting = [{ columnName: 'a', direction: 'desc' }];

const sorted = sortedRows(rows, sorting);
const sorted = sortedRows(rows, sorting, getCellData);
expect(sorted).toEqual([
{ a: 2, b: 2 },
{ a: 2, b: 1 },
Expand All @@ -47,7 +49,7 @@ describe('SortingState computeds', () => {
it('can sort by several columns', () => {
const sorting = [{ columnName: 'a', direction: 'asc' }, { columnName: 'b', direction: 'asc' }];

const sorted = sortedRows(rows, sorting);
const sorted = sortedRows(rows, sorting, getCellData);
expect(sorted).toEqual([
{ a: 1, b: 1 },
{ a: 1, b: 2 },
Expand All @@ -59,7 +61,7 @@ describe('SortingState computeds', () => {
it('can sort by several columns with different directions', () => {
const sorting = [{ columnName: 'a', direction: 'asc' }, { columnName: 'b', direction: 'desc' }];

const sorted = sortedRows(rows, sorting);
const sorted = sortedRows(rows, sorting, getCellData);
expect(sorted).toEqual([
{ a: 1, b: 2 },
{ a: 1, b: 1 },
Expand All @@ -72,7 +74,7 @@ describe('SortingState computeds', () => {
const immutableRows = Immutable(rows);
const immutableSorting = Immutable([{ columnName: 'a', direction: 'desc' }]);

const sorted = sortedRows(immutableRows, immutableSorting);
const sorted = sortedRows(immutableRows, immutableSorting, getCellData);
expect(sorted).toEqual([
{ a: 2, b: 2 },
{ a: 2, b: 1 },
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import React from 'react';
import {
EditingState,
} from '@devexpress/dx-react-grid';
import {
Grid,
TableView,
TableHeaderRow,
TableEditRow,
TableEditColumn,
} from '@devexpress/dx-react-grid-bootstrap3';

import {
generateRows,
defaultNestedColumnValues,
} from '../../demo-data/generator';

export default class Demo extends React.PureComponent {
constructor(props) {
super(props);

this.state = {
columns: [
{
name: 'firstName',
title: 'First Name',
getCellData: row => (row.user ? row.user.firstName : undefined),
createRowChange: (row, value) => ({
user: {
...row.user,
firstName: value,
},
}),
},
{
name: 'lastName',
title: 'Last Name',
getCellData: row => (row.user ? row.user.lastName : undefined),
createRowChange: (row, value) => ({
user: {
...row.user,
lastName: value,
},
}),
},
{
name: 'car',
title: 'Car',
getCellData: row => (row.car ? row.car.model : undefined),
createRowChange: (row, value) => ({
car: {
model: value,
},
}),
},
{ name: 'position', title: 'Position' },
{ name: 'city', title: 'City' },
],
rows: generateRows({
columnValues: { id: ({ index }) => index, ...defaultNestedColumnValues },
length: 14,
}),
};

this.commitChanges = ({ added, changed, deleted }) => {
let rows = this.state.rows;
if (added) {
const startingAddedId = (rows.length - 1) > 0 ? rows[rows.length - 1].id + 1 : 0;
rows = [
...rows,
...added.map((row, index) => ({
id: startingAddedId + index,
...row,
})),
];
}
if (changed) {
rows = rows.map(row => (changed[row.id] ? { ...row, ...changed[row.id] } : row));
}
if (deleted) {
const deletedSet = new Set(deleted);
rows = rows.filter(row => !deletedSet.has(row.id));
}
this.setState({ rows });
};
}
render() {
const { rows, columns } = this.state;

return (
<Grid
rows={rows}
columns={columns}
getRowId={row => row.id}
>
<EditingState
onCommitChanges={this.commitChanges}
/>
<TableView />
<TableHeaderRow />
<TableEditRow />
<TableEditColumn
allowAdding
allowEditing
allowDeleting
/>
</Grid>
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react';
import { mount } from 'enzyme';
import CustomDataAccessorsInColumns from './custom-data-accessors-in-columns';

describe('BS3: custom data accessors in columns demo', () => {
it('should work', () => {
mount(
<CustomDataAccessorsInColumns />,
);
});
});
Loading

0 comments on commit 5f699bf

Please sign in to comment.