Development/sorted classes and style (#136)

* fix missing defaultSorted props for default sort sample

* implement customized classes for sorted header

* [test] test for sorted header classes

* implement customized style for sorted header

* [test] test for sorted header style

* update document

* add missing props check and fix typo

* seperate sorting style and header into two props

* [test] add test case if column.headerStyle and column.headerClasses were defined

* implement customized header style and classes in column level

* [test] test for customized header style and classes in column level

* [DOC] document for customized classes and styles

* sample for customized classes and styles

* typo fix for document

* tuning the wording for test and documents
This commit is contained in:
chunming 2017-12-10 13:53:03 +08:00 committed by Allen
parent 574a3146fc
commit 00f1105c8d
11 changed files with 431 additions and 11 deletions

View File

@ -25,6 +25,8 @@ Available properties in a column object:
* [headerEvents](#headerEvents)
* [headerAlign](#headerAlign)
* [headerAttrs](#headerAttrs)
* [headerSortingClasses](#headerSortingClasses)
* [headerSortingStyle](#headerSortingStyle)
* [editable](#editable)
* [validator](#validator)
* [editCellStyle](#editCellStyle)
@ -419,6 +421,35 @@ A new `Object` will be the result of element headerAttrs.
> Same as [column.attrs](#attrs), it has lower priority and will be
> overwrited when other props related to HTML attributes were given.
### <a name='headerSortingClasses'>headerSortingClasses - [String | Function]</a>
`headerSortingClasses` allows to customize `class` for header cell when this column is sorting.
```js
const headerSortingClasses = 'demo-sorting';
```
Furthermore, it also accepts a callback which takes **4** arguments and `String` is expected to return:
```js
const headerSortingClasses = (column, sortOrder, isLastSorting, colIndex) => { ... }
```
* `column`: The value of current column.
* `sortOrder`: The order of current sorting
* `isLastSorting`: Is the last one of sorted columns.
* `colIndex`: The index of the current column being processed in BootstrapTable.
### <a name='headerSortingStyle'>headerSortingStyle - [Object | Function]</a>
It's similiar to [headerSortingClasses](#headerSortingClasses). It allows to customize the style of header cell when this column is sorting. A style `Object` and `callback` are acceptable. `callback` takes **4** arguments and an `Object` is expected to return:
```js
const sortingHeaderStyle = {
backgroundColor: 'red'
};
```
## <a name='editable'>column.editable - [Bool | Function]</a>
`column.editable` default is true, means every column is editable if you configure [`cellEdit`](./README.md#cellEdit). But you can disable some columns editable via setting `false`.

View File

@ -41,7 +41,17 @@ const columns = [{
sort: true
}];
<BootstrapTable keyField='id' data={ products } columns={ columns } />
const defaultSorted = [{
dataField: 'name',
order: 'desc'
}];
<BootstrapTable
keyField="id"
data={ products }
columns={ columns }
defaultSorted={ defaultSorted }
/>
`;

View File

@ -0,0 +1,61 @@
/* eslint
no-unused-vars: 0
arrow-body-style: 0
*/
import React from 'react';
import BootstrapTable from 'react-bootstrap-table2';
import Code from 'components/common/code-block';
import { productsGenerator } from 'utils/common';
const products = productsGenerator();
const headerSortingClasses = (column, sortOrder, isLastSorting, colIndex) => (
sortOrder === 'asc' ? 'demo-sorting-asc' : 'demo-sorting-desc'
);
const columns = [{
dataField: 'id',
text: 'Product ID',
sort: true,
headerSortingClasses
}, {
dataField: 'name',
text: 'Product Name',
sort: true,
headerSortingClasses
}, {
dataField: 'price',
text: 'Product Price'
}];
const sourceCode = `\
const headerSortingClasses = (column, sortOrder, isLastSorting, colIndex) => (
sortOrder === 'asc' ? 'demo-sorting-asc' : 'demo-sorting-desc'
);
const columns = [{
dataField: 'id',
text: 'Product ID',
sort: true,
headerSortingClasses
}, {
dataField: 'name',
text: 'Product Name',
sort: true,
headerSortingClasses
}, {
dataField: 'price',
text: 'Product Price'
}];
<BootstrapTable keyField="id" data={ products } columns={ columns } />
`;
export default () => (
<div>
<BootstrapTable keyField="id" data={ products } columns={ columns } />
<Code>{ sourceCode }</Code>
</div>
);

View File

@ -0,0 +1,54 @@
/* eslint no-unused-vars: 0 */
import React from 'react';
import BootstrapTable from 'react-bootstrap-table2';
import Code from 'components/common/code-block';
import { productsGenerator } from 'utils/common';
const products = productsGenerator();
const headerSortingStyle = { backgroundColor: '#c8e6c9' };
const columns = [{
dataField: 'id',
text: 'Product ID',
sort: true,
headerSortingStyle
}, {
dataField: 'name',
text: 'Product Name',
sort: true,
headerSortingStyle
}, {
dataField: 'price',
text: 'Product Price'
}];
const sourceCode = `\
const headerSortingStyle = { backgroundColor: '#c8e6c9' };
const columns = [{
dataField: 'id',
text: 'Product ID',
sort: true,
headerSortingStyle
}, {
dataField: 'name',
text: 'Product Name',
sort: true,
headerSortingStyle
}, {
dataField: 'price',
text: 'Product Price'
}];
<BootstrapTable keyField="id" data={ products } columns={ columns } />
`;
export default () => (
<div>
<BootstrapTable keyField="id" data={ products } columns={ columns } />
<Code>{ sourceCode }</Code>
</div>
);

View File

@ -41,6 +41,8 @@ import RowEventTable from 'examples/rows/row-event';
import EnableSortTable from 'examples/sort/enable-sort-table';
import DefaultSortTable from 'examples/sort/default-sort-table';
import CustomSortTable from 'examples/sort/custom-sort-table';
import HeaderSortingClassesTable from 'examples/sort/header-sorting-classes';
import HeaderSortingStyleTable from 'examples/sort/header-sorting-style';
// cell editing
import ClickToEditTable from 'examples/cell-edit/click-to-edit-table';
@ -127,7 +129,9 @@ storiesOf('Work on Rows', module)
storiesOf('Sort Table', module)
.add('Enable Sort', () => <EnableSortTable />)
.add('Default Sort Table', () => <DefaultSortTable />)
.add('Custom Sort Fuction', () => <CustomSortTable />);
.add('Custom Sort Fuction', () => <CustomSortTable />)
.add('Custom Classes on Sorting Header Column', () => <HeaderSortingClassesTable />)
.add('Custom Style on Sorting Header Column', () => <HeaderSortingStyleTable />);
storiesOf('Cell Editing', module)
.add('Click to Edit', () => <ClickToEditTable />)

View File

@ -10,4 +10,5 @@ $grey-900: #212121;
$pink-500: #E91E63;
$green-lighten-2: #81c784;
$green-lighten-4: #c8e6c9;
$light-blue: #00BFFF;
$markdown-color: #f6f8fa;

View File

@ -0,0 +1,8 @@
.demo-sorting,
.demo-sorting-asc {
background-color: $green-lighten-2;
}
.demo-sorting-desc {
background-color: $light-blue;
}

View File

@ -8,4 +8,5 @@
@import "cell-edit/index";
@import "row-selection/index";
@import "rows/index";
@import "loading-overlay/index";
@import "sort/index";
@import "loading-overlay/index";

View File

@ -15,8 +15,10 @@ const HeaderCell = (props) => {
index,
onSort,
sorting,
sortOrder
sortOrder,
isLastSorting
} = props;
const {
text,
sort,
@ -27,7 +29,9 @@ const HeaderCell = (props) => {
headerEvents,
headerClasses,
headerStyle,
headerAttrs
headerAttrs,
headerSortingClasses,
headerSortingStyle
} = column;
const cellAttrs = {
@ -35,10 +39,10 @@ const HeaderCell = (props) => {
...headerEvents
};
const children = headerFormatter ? headerFormatter(column, index) : text;
const cellClasses = _.isFunction(headerClasses) ? headerClasses(column, index) : headerClasses;
let cellStyle = {};
let sortSymbol;
let cellClasses = _.isFunction(headerClasses) ? headerClasses(column, index) : headerClasses;
if (headerStyle) {
cellStyle = _.isFunction(headerStyle) ? headerStyle(column, index) : headerStyle;
@ -56,10 +60,6 @@ const HeaderCell = (props) => {
cellStyle.display = 'none';
}
if (cellClasses) cellAttrs.className = cellClasses;
if (!_.isEmptyObject(cellStyle)) cellAttrs.style = cellStyle;
if (sort) {
const customClick = cellAttrs.onClick;
cellAttrs.onClick = (e) => {
@ -70,11 +70,30 @@ const HeaderCell = (props) => {
if (sorting) {
sortSymbol = <SortCaret order={ sortOrder } />;
// append customized classes or style if table was sorting based on the current column.
cellClasses = cs(
cellClasses,
_.isFunction(headerSortingClasses)
? headerSortingClasses(column, sortOrder, isLastSorting, index)
: headerSortingClasses
);
cellStyle = {
...cellStyle,
..._.isFunction(headerSortingStyle)
? headerSortingStyle(column, sortOrder, isLastSorting, index)
: headerSortingStyle
};
} else {
sortSymbol = <SortSymbol />;
}
}
if (cellClasses) cellAttrs.className = cs(cellAttrs.className, cellClasses);
if (!_.isEmptyObject(cellStyle)) cellAttrs.style = cellStyle;
return (
<th { ...cellAttrs }>
{ children }{ sortSymbol }
@ -112,7 +131,8 @@ HeaderCell.propTypes = {
index: PropTypes.number.isRequired,
onSort: PropTypes.func,
sorting: PropTypes.bool,
sortOrder: PropTypes.oneOf([Const.SORT_ASC, Const.SORT_DESC])
sortOrder: PropTypes.oneOf([Const.SORT_ASC, Const.SORT_DESC]),
isLastSorting: PropTypes.bool
};
export default HeaderCell;

View File

@ -27,6 +27,8 @@ const Header = (props) => {
{
columns.map((column, i) => {
const currSort = column.dataField === sortField;
const isLastSorting = column.dataField === sortField;
return (
<HeaderCell
index={ i }
@ -35,6 +37,7 @@ const Header = (props) => {
onSort={ onSort }
sorting={ currSort }
sortOrder={ sortOrder }
isLastSorting={ isLastSorting }
/>);
})
}

View File

@ -436,6 +436,233 @@ describe('HeaderCell', () => {
});
});
});
describe('when headerSortingClasses is defined ', () => {
const classes = 'foo';
const order = Const.SORT_DESC;
describe('if headerSortingClasses is a string', () => {
beforeEach(() => {
column = { ...column, headerSortingClasses: classes };
wrapper = shallow(
<HeaderCell
column={ column }
index={ index }
sorting
sortOrder={ order }
/>);
});
it('should append classes correctly', () => {
expect(wrapper.length).toBe(1);
expect(wrapper.hasClass(classes)).toBe(true);
});
it('should have sortable class on header cell', () => {
expect(wrapper.hasClass('sortable')).toBe(true);
});
});
describe('if headerSortingClasses is a function', () => {
let classesCallBack;
let isLastSorting;
beforeEach(() => {
classesCallBack = sinon.stub()
.withArgs(column, order, isLastSorting, index)
.returns(classes);
column = { ...column, headerSortingClasses: classesCallBack };
wrapper = shallow(
<HeaderCell
column={ column }
index={ index }
sorting
sortOrder={ order }
/>);
});
it('should append classes correctly', () => {
expect(wrapper.length).toBe(1);
expect(wrapper.hasClass(classes)).toBe(true);
});
it('should have sortable class on header cell', () => {
expect(wrapper.hasClass('sortable')).toBe(true);
});
it('should call custom class function with correct params', () => {
expect(classesCallBack.callCount).toBe(1);
expect(classesCallBack.calledWith(column, order, isLastSorting, index)).toBe(true);
});
describe('when the field is last sorting', () => {
it('should call custom classes function with isLastSorting being true', () => {
isLastSorting = true;
classesCallBack.reset();
wrapper = shallow(
<HeaderCell
column={ column }
index={ index }
sorting
sortOrder={ order }
isLastSorting
/>);
expect(classesCallBack.callCount).toBe(1);
expect(classesCallBack.calledWith(column, order, isLastSorting, index)).toBe(true);
});
});
});
describe('if column.headerClasses is defined as well', () => {
it('should keep both classes', () => {
column = {
...column,
headerClasses: 'td-test-class',
headerSortingClasses: classes
};
wrapper = shallow(
<HeaderCell
column={ column }
index={ index }
sorting
sortOrder={ order }
/>
);
expect(wrapper.length).toBe(1);
expect(wrapper.hasClass('sortable')).toBe(true);
expect(wrapper.hasClass(classes)).toBe(true);
expect(wrapper.hasClass(column.headerClasses)).toBe(true);
});
});
});
describe('when headerSortingStyle is defined', () => {
const style = { backgroundColor: 'red' };
const order = Const.SORT_DESC;
describe('if headerSortingStyle is an object', () => {
beforeEach(() => {
column = { ...column, headerSortingStyle: style };
wrapper = shallow(
<HeaderCell
column={ column }
index={ index }
sorting
sortOrder={ order }
/>
);
});
it('should append style correctly', () => {
expect(wrapper.length).toBe(1);
expect(wrapper.find('th').prop('style')).toEqual(style);
});
});
describe('if headerSortingStyle is a function', () => {
let styleCallBack;
let isLastSorting;
beforeEach(() => {
styleCallBack = sinon.stub()
.withArgs(column, order, isLastSorting, index)
.returns(style);
column = { ...column, headerSortingStyle: styleCallBack };
wrapper = shallow(
<HeaderCell
column={ column }
index={ index }
sorting
sortOrder={ order }
/>);
});
it('should append style correctly', () => {
expect(wrapper.length).toBe(1);
expect(wrapper.find('th').prop('style')).toEqual(style);
});
it('should call custom style function with correct params', () => {
expect(styleCallBack.callCount).toBe(1);
expect(styleCallBack.calledWith(column, order, isLastSorting, index)).toBe(true);
});
describe('when the field is last sorting', () => {
it('should call custom classes function with isLastSorting being true', () => {
isLastSorting = true;
styleCallBack.reset();
wrapper = shallow(
<HeaderCell
column={ column }
index={ index }
sorting
sortOrder={ order }
isLastSorting
/>);
expect(styleCallBack.callCount).toBe(1);
expect(styleCallBack.calledWith(column, order, isLastSorting, index)).toBe(true);
});
});
});
describe('if column.headerStyle was defined as well', () => {
it('should keep both styles', () => {
column = {
...column,
headerStyle: { opacity: '1' },
headerSortingStyle: style
};
wrapper = shallow(
<HeaderCell
column={ column }
index={ index }
sorting
sortOrder={ order }
/>
);
expect(wrapper.length).toBe(1);
expect(wrapper.find('th').prop('style')).toEqual(expect.objectContaining({
...style,
...column.headerStyle
}));
});
it('headerSortingStyle should have higher priority', () => {
column = {
...column,
headerStyle: { backgroundColor: 'green' },
headerSortingStyle: style
};
wrapper = shallow(
<HeaderCell
column={ column }
index={ index }
sorting
sortOrder={ order }
/>
);
expect(wrapper.length).toBe(1);
expect(wrapper.find('th').prop('style')).toEqual(expect.objectContaining({
...style
}));
expect(wrapper.find('th').prop('style')).not.toEqual(expect.objectContaining({
...column.headerStyle
}));
});
});
});
});
describe('when column.headerEvents prop is defined and have custom onClick', () => {